mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
# Backport This will backport the following commits from `main` to `8.18`: - [[Security Assistant] Fix Knowledge Base API (#211367)](https://github.com/elastic/kibana/pull/211367) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Patryk Kopyciński","email":"contact@patrykkopycinski.com"},"sourceCommit":{"committedDate":"2025-02-25T23:00:00Z","message":"[Security Assistant] Fix Knowledge Base API (#211367)\n\n## Summary\n\nFixes bugs related to Security Assistant Knowledge Base API\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: Hannah Mudge <Heenawter@users.noreply.github.com>\nCo-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com>\nCo-authored-by: Davis Plumlee <56367316+dplumlee@users.noreply.github.com>\nCo-authored-by: Jatin Kathuria <jatin.kathuria@elastic.co>\nCo-authored-by: Chris Cowan <chris@elastic.co>\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\nCo-authored-by: Arturo Lidueña <arturo.liduena@elastic.co>\nCo-authored-by: Jon <jon@elastic.co>\nCo-authored-by: Rodney Norris <rodney.norris@elastic.co>\nCo-authored-by: Elena Shostak <165678770+elena-shostak@users.noreply.github.com>\nCo-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>\nCo-authored-by: Irene Blanco <irene.blanco@elastic.co>\nCo-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com>\nCo-authored-by: Carlos Crespo <crespocarlos@users.noreply.github.com>","sha":"c822109a492fe4dcf38ca5aa6d87b2a95bf075c4","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","v9.0.0","Feature:Security Assistant","Team:Security Generative AI","backport:version","v8.18.0","v9.1.0"],"title":"[Security Assistant] Fix Knowledge Base API","number":211367,"url":"https://github.com/elastic/kibana/pull/211367","mergeCommit":{"message":"[Security Assistant] Fix Knowledge Base API (#211367)\n\n## Summary\n\nFixes bugs related to Security Assistant Knowledge Base API\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: Hannah Mudge <Heenawter@users.noreply.github.com>\nCo-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com>\nCo-authored-by: Davis Plumlee <56367316+dplumlee@users.noreply.github.com>\nCo-authored-by: Jatin Kathuria <jatin.kathuria@elastic.co>\nCo-authored-by: Chris Cowan <chris@elastic.co>\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\nCo-authored-by: Arturo Lidueña <arturo.liduena@elastic.co>\nCo-authored-by: Jon <jon@elastic.co>\nCo-authored-by: Rodney Norris <rodney.norris@elastic.co>\nCo-authored-by: Elena Shostak <165678770+elena-shostak@users.noreply.github.com>\nCo-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>\nCo-authored-by: Irene Blanco <irene.blanco@elastic.co>\nCo-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com>\nCo-authored-by: Carlos Crespo <crespocarlos@users.noreply.github.com>","sha":"c822109a492fe4dcf38ca5aa6d87b2a95bf075c4"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/211367","number":211367,"mergeCommit":{"message":"[Security Assistant] Fix Knowledge Base API (#211367)\n\n## Summary\n\nFixes bugs related to Security Assistant Knowledge Base API\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\nCo-authored-by: Hannah Mudge <Heenawter@users.noreply.github.com>\nCo-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com>\nCo-authored-by: Davis Plumlee <56367316+dplumlee@users.noreply.github.com>\nCo-authored-by: Jatin Kathuria <jatin.kathuria@elastic.co>\nCo-authored-by: Chris Cowan <chris@elastic.co>\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>\nCo-authored-by: Arturo Lidueña <arturo.liduena@elastic.co>\nCo-authored-by: Jon <jon@elastic.co>\nCo-authored-by: Rodney Norris <rodney.norris@elastic.co>\nCo-authored-by: Elena Shostak <165678770+elena-shostak@users.noreply.github.com>\nCo-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>\nCo-authored-by: Irene Blanco <irene.blanco@elastic.co>\nCo-authored-by: Cauê Marcondes <55978943+cauemarcondes@users.noreply.github.com>\nCo-authored-by: Carlos Crespo <crespocarlos@users.noreply.github.com>","sha":"c822109a492fe4dcf38ca5aa6d87b2a95bf075c4"}}]}] BACKPORT--> --------- Co-authored-by: Patryk Kopyciński <contact@patrykkopycinski.com>
This commit is contained in:
parent
22bfe943cd
commit
ee44e4d0fb
82 changed files with 1085 additions and 178 deletions
|
@ -36382,7 +36382,7 @@ paths:
|
|||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Security_AI_Assistant_API_KnowledgeBaseEntryUpdateProps'
|
||||
$ref: '#/components/schemas/Security_AI_Assistant_API_KnowledgeBaseEntryUpdateRouteProps'
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
|
@ -42773,6 +42773,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: Whether this Knowledge Base Entry is global, defaults to false
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -42787,6 +42790,7 @@ components:
|
|||
required:
|
||||
- name
|
||||
- namespace
|
||||
- global
|
||||
- users
|
||||
- $ref: '#/components/schemas/Security_AI_Assistant_API_ResponseFields'
|
||||
- $ref: '#/components/schemas/Security_AI_Assistant_API_DocumentEntryResponseFields'
|
||||
|
@ -42794,6 +42798,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: Whether this Knowledge Base Entry is global, defaults to false
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -42821,8 +42828,7 @@ components:
|
|||
type: object
|
||||
properties:
|
||||
kbResource:
|
||||
description: Knowledge Base resource name for grouping entries, e.g. 'esql', 'lens-docs', etc
|
||||
type: string
|
||||
$ref: '#/components/schemas/Security_AI_Assistant_API_KnowledgeBaseResource'
|
||||
source:
|
||||
description: Source document name or filepath
|
||||
type: string
|
||||
|
@ -42847,6 +42853,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: Whether this Knowledge Base Entry is global, defaults to false
|
||||
type: boolean
|
||||
id:
|
||||
$ref: '#/components/schemas/Security_AI_Assistant_API_NonEmptyString'
|
||||
name:
|
||||
|
@ -42927,6 +42936,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: Whether this Knowledge Base Entry is global, defaults to false
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -42941,6 +42953,7 @@ components:
|
|||
required:
|
||||
- name
|
||||
- namespace
|
||||
- global
|
||||
- users
|
||||
- $ref: '#/components/schemas/Security_AI_Assistant_API_ResponseFields'
|
||||
- $ref: '#/components/schemas/Security_AI_Assistant_API_IndexEntryResponseFields'
|
||||
|
@ -42948,6 +42961,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: Whether this Knowledge Base Entry is global, defaults to false
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -43007,6 +43023,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: Whether this Knowledge Base Entry is global, defaults to false
|
||||
type: boolean
|
||||
id:
|
||||
$ref: '#/components/schemas/Security_AI_Assistant_API_NonEmptyString'
|
||||
name:
|
||||
|
@ -43186,6 +43205,18 @@ components:
|
|||
- $ref: '#/components/schemas/Security_AI_Assistant_API_IndexEntryUpdateFields'
|
||||
discriminator:
|
||||
propertyName: type
|
||||
Security_AI_Assistant_API_KnowledgeBaseEntryUpdateRouteProps:
|
||||
anyOf:
|
||||
- $ref: '#/components/schemas/Security_AI_Assistant_API_DocumentEntryCreateFields'
|
||||
- $ref: '#/components/schemas/Security_AI_Assistant_API_IndexEntryCreateFields'
|
||||
discriminator:
|
||||
propertyName: type
|
||||
Security_AI_Assistant_API_KnowledgeBaseResource:
|
||||
description: Knowledge Base resource name for grouping entries, e.g. 'security_labs', 'user', etc
|
||||
enum:
|
||||
- security_labs
|
||||
- user
|
||||
type: string
|
||||
Security_AI_Assistant_API_KnowledgeBaseResponse:
|
||||
description: AI assistant KnowledgeBase.
|
||||
type: object
|
||||
|
|
|
@ -20468,7 +20468,7 @@ paths:
|
|||
content:
|
||||
application/json; Elastic-Api-Version=2023-10-31:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Security_AI_Assistant_API_KnowledgeBaseEntryUpdateProps'
|
||||
$ref: '#/components/schemas/Security_AI_Assistant_API_KnowledgeBaseEntryUpdateRouteProps'
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
|
@ -31033,6 +31033,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: Whether this Knowledge Base Entry is global, defaults to false
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -31047,6 +31050,7 @@ components:
|
|||
required:
|
||||
- name
|
||||
- namespace
|
||||
- global
|
||||
- users
|
||||
- $ref: '#/components/schemas/Security_AI_Assistant_API_ResponseFields'
|
||||
- $ref: '#/components/schemas/Security_AI_Assistant_API_DocumentEntryResponseFields'
|
||||
|
@ -31054,6 +31058,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: Whether this Knowledge Base Entry is global, defaults to false
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -31081,8 +31088,7 @@ components:
|
|||
type: object
|
||||
properties:
|
||||
kbResource:
|
||||
description: Knowledge Base resource name for grouping entries, e.g. 'esql', 'lens-docs', etc
|
||||
type: string
|
||||
$ref: '#/components/schemas/Security_AI_Assistant_API_KnowledgeBaseResource'
|
||||
source:
|
||||
description: Source document name or filepath
|
||||
type: string
|
||||
|
@ -31107,6 +31113,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: Whether this Knowledge Base Entry is global, defaults to false
|
||||
type: boolean
|
||||
id:
|
||||
$ref: '#/components/schemas/Security_AI_Assistant_API_NonEmptyString'
|
||||
name:
|
||||
|
@ -31187,6 +31196,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: Whether this Knowledge Base Entry is global, defaults to false
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -31201,6 +31213,7 @@ components:
|
|||
required:
|
||||
- name
|
||||
- namespace
|
||||
- global
|
||||
- users
|
||||
- $ref: '#/components/schemas/Security_AI_Assistant_API_ResponseFields'
|
||||
- $ref: '#/components/schemas/Security_AI_Assistant_API_IndexEntryResponseFields'
|
||||
|
@ -31208,6 +31221,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: Whether this Knowledge Base Entry is global, defaults to false
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -31267,6 +31283,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: Whether this Knowledge Base Entry is global, defaults to false
|
||||
type: boolean
|
||||
id:
|
||||
$ref: '#/components/schemas/Security_AI_Assistant_API_NonEmptyString'
|
||||
name:
|
||||
|
@ -31446,6 +31465,18 @@ components:
|
|||
- $ref: '#/components/schemas/Security_AI_Assistant_API_IndexEntryUpdateFields'
|
||||
discriminator:
|
||||
propertyName: type
|
||||
Security_AI_Assistant_API_KnowledgeBaseEntryUpdateRouteProps:
|
||||
anyOf:
|
||||
- $ref: '#/components/schemas/Security_AI_Assistant_API_DocumentEntryCreateFields'
|
||||
- $ref: '#/components/schemas/Security_AI_Assistant_API_IndexEntryCreateFields'
|
||||
discriminator:
|
||||
propertyName: type
|
||||
Security_AI_Assistant_API_KnowledgeBaseResource:
|
||||
description: Knowledge Base resource name for grouping entries, e.g. 'security_labs', 'user', etc
|
||||
enum:
|
||||
- security_labs
|
||||
- user
|
||||
type: string
|
||||
Security_AI_Assistant_API_KnowledgeBaseResponse:
|
||||
description: AI assistant KnowledgeBase.
|
||||
type: object
|
||||
|
|
|
@ -746,7 +746,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/KnowledgeBaseEntryUpdateProps'
|
||||
$ref: '#/components/schemas/KnowledgeBaseEntryUpdateRouteProps'
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
|
@ -1301,6 +1301,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: 'Whether this Knowledge Base Entry is global, defaults to false'
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -1317,6 +1320,7 @@ components:
|
|||
required:
|
||||
- name
|
||||
- namespace
|
||||
- global
|
||||
- users
|
||||
- $ref: '#/components/schemas/ResponseFields'
|
||||
- $ref: '#/components/schemas/DocumentEntryResponseFields'
|
||||
|
@ -1324,6 +1328,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: 'Whether this Knowledge Base Entry is global, defaults to false'
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -1353,10 +1360,7 @@ components:
|
|||
type: object
|
||||
properties:
|
||||
kbResource:
|
||||
description: >-
|
||||
Knowledge Base resource name for grouping entries, e.g. 'esql',
|
||||
'lens-docs', etc
|
||||
type: string
|
||||
$ref: '#/components/schemas/KnowledgeBaseResource'
|
||||
source:
|
||||
description: Source document name or filepath
|
||||
type: string
|
||||
|
@ -1381,6 +1385,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: 'Whether this Knowledge Base Entry is global, defaults to false'
|
||||
type: boolean
|
||||
id:
|
||||
$ref: '#/components/schemas/NonEmptyString'
|
||||
name:
|
||||
|
@ -1463,6 +1470,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: 'Whether this Knowledge Base Entry is global, defaults to false'
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -1479,6 +1489,7 @@ components:
|
|||
required:
|
||||
- name
|
||||
- namespace
|
||||
- global
|
||||
- users
|
||||
- $ref: '#/components/schemas/ResponseFields'
|
||||
- $ref: '#/components/schemas/IndexEntryResponseFields'
|
||||
|
@ -1486,6 +1497,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: 'Whether this Knowledge Base Entry is global, defaults to false'
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -1553,6 +1567,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: 'Whether this Knowledge Base Entry is global, defaults to false'
|
||||
type: boolean
|
||||
id:
|
||||
$ref: '#/components/schemas/NonEmptyString'
|
||||
name:
|
||||
|
@ -1736,6 +1753,20 @@ components:
|
|||
- $ref: '#/components/schemas/IndexEntryUpdateFields'
|
||||
discriminator:
|
||||
propertyName: type
|
||||
KnowledgeBaseEntryUpdateRouteProps:
|
||||
anyOf:
|
||||
- $ref: '#/components/schemas/DocumentEntryCreateFields'
|
||||
- $ref: '#/components/schemas/IndexEntryCreateFields'
|
||||
discriminator:
|
||||
propertyName: type
|
||||
KnowledgeBaseResource:
|
||||
description: >-
|
||||
Knowledge Base resource name for grouping entries, e.g. 'security_labs',
|
||||
'user', etc
|
||||
enum:
|
||||
- security_labs
|
||||
- user
|
||||
type: string
|
||||
KnowledgeBaseResponse:
|
||||
description: AI assistant KnowledgeBase.
|
||||
type: object
|
||||
|
|
|
@ -746,7 +746,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/KnowledgeBaseEntryUpdateProps'
|
||||
$ref: '#/components/schemas/KnowledgeBaseEntryUpdateRouteProps'
|
||||
required: true
|
||||
responses:
|
||||
'200':
|
||||
|
@ -1301,6 +1301,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: 'Whether this Knowledge Base Entry is global, defaults to false'
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -1317,6 +1320,7 @@ components:
|
|||
required:
|
||||
- name
|
||||
- namespace
|
||||
- global
|
||||
- users
|
||||
- $ref: '#/components/schemas/ResponseFields'
|
||||
- $ref: '#/components/schemas/DocumentEntryResponseFields'
|
||||
|
@ -1324,6 +1328,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: 'Whether this Knowledge Base Entry is global, defaults to false'
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -1353,10 +1360,7 @@ components:
|
|||
type: object
|
||||
properties:
|
||||
kbResource:
|
||||
description: >-
|
||||
Knowledge Base resource name for grouping entries, e.g. 'esql',
|
||||
'lens-docs', etc
|
||||
type: string
|
||||
$ref: '#/components/schemas/KnowledgeBaseResource'
|
||||
source:
|
||||
description: Source document name or filepath
|
||||
type: string
|
||||
|
@ -1381,6 +1385,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: 'Whether this Knowledge Base Entry is global, defaults to false'
|
||||
type: boolean
|
||||
id:
|
||||
$ref: '#/components/schemas/NonEmptyString'
|
||||
name:
|
||||
|
@ -1463,6 +1470,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: 'Whether this Knowledge Base Entry is global, defaults to false'
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -1479,6 +1489,7 @@ components:
|
|||
required:
|
||||
- name
|
||||
- namespace
|
||||
- global
|
||||
- users
|
||||
- $ref: '#/components/schemas/ResponseFields'
|
||||
- $ref: '#/components/schemas/IndexEntryResponseFields'
|
||||
|
@ -1486,6 +1497,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: 'Whether this Knowledge Base Entry is global, defaults to false'
|
||||
type: boolean
|
||||
name:
|
||||
description: Name of the Knowledge Base Entry
|
||||
type: string
|
||||
|
@ -1553,6 +1567,9 @@ components:
|
|||
allOf:
|
||||
- type: object
|
||||
properties:
|
||||
global:
|
||||
description: 'Whether this Knowledge Base Entry is global, defaults to false'
|
||||
type: boolean
|
||||
id:
|
||||
$ref: '#/components/schemas/NonEmptyString'
|
||||
name:
|
||||
|
@ -1736,6 +1753,20 @@ components:
|
|||
- $ref: '#/components/schemas/IndexEntryUpdateFields'
|
||||
discriminator:
|
||||
propertyName: type
|
||||
KnowledgeBaseEntryUpdateRouteProps:
|
||||
anyOf:
|
||||
- $ref: '#/components/schemas/DocumentEntryCreateFields'
|
||||
- $ref: '#/components/schemas/IndexEntryCreateFields'
|
||||
discriminator:
|
||||
propertyName: type
|
||||
KnowledgeBaseResource:
|
||||
description: >-
|
||||
Knowledge Base resource name for grouping entries, e.g. 'security_labs',
|
||||
'user', etc
|
||||
enum:
|
||||
- security_labs
|
||||
- user
|
||||
type: string
|
||||
KnowledgeBaseResponse:
|
||||
description: AI assistant KnowledgeBase.
|
||||
type: object
|
||||
|
|
|
@ -48,15 +48,20 @@ export const KnowledgeBaseEntryErrorSchema = z
|
|||
})
|
||||
.strict();
|
||||
|
||||
/**
|
||||
* Knowledge Base resource name for grouping entries, e.g. 'security_labs', 'user', etc
|
||||
*/
|
||||
export type KnowledgeBaseResource = z.infer<typeof KnowledgeBaseResource>;
|
||||
export const KnowledgeBaseResource = z.enum(['security_labs', 'user']);
|
||||
export type KnowledgeBaseResourceEnum = typeof KnowledgeBaseResource.enum;
|
||||
export const KnowledgeBaseResourceEnum = KnowledgeBaseResource.enum;
|
||||
|
||||
/**
|
||||
* Metadata about a Knowledge Base Entry
|
||||
*/
|
||||
export type Metadata = z.infer<typeof Metadata>;
|
||||
export const Metadata = z.object({
|
||||
/**
|
||||
* Knowledge Base resource name for grouping entries, e.g. 'esql', 'lens-docs', etc
|
||||
*/
|
||||
kbResource: z.string(),
|
||||
kbResource: KnowledgeBaseResource,
|
||||
/**
|
||||
* Source document name or filepath
|
||||
*/
|
||||
|
@ -96,6 +101,10 @@ export const BaseDefaultableFields = z.object({
|
|||
* Kibana Space, defaults to 'default' space
|
||||
*/
|
||||
namespace: z.string().optional(),
|
||||
/**
|
||||
* Whether this Knowledge Base Entry is global, defaults to false
|
||||
*/
|
||||
global: z.boolean().optional(),
|
||||
/**
|
||||
* Users who have access to the Knowledge Base Entry, defaults to current user. Empty array provides access to all users.
|
||||
*/
|
||||
|
@ -153,10 +162,7 @@ export const DocumentEntryRequiredFields = z.object({
|
|||
* Entry type
|
||||
*/
|
||||
type: z.literal('document'),
|
||||
/**
|
||||
* Knowledge Base resource name for grouping entries, e.g. 'esql', 'lens-docs', etc
|
||||
*/
|
||||
kbResource: z.string(),
|
||||
kbResource: KnowledgeBaseResource,
|
||||
/**
|
||||
* Source document name or filepath
|
||||
*/
|
||||
|
@ -253,6 +259,12 @@ export const KnowledgeBaseEntryUpdateProps = z.discriminatedUnion('type', [
|
|||
IndexEntryUpdateFields,
|
||||
]);
|
||||
|
||||
export type KnowledgeBaseEntryUpdateRouteProps = z.infer<typeof KnowledgeBaseEntryUpdateRouteProps>;
|
||||
export const KnowledgeBaseEntryUpdateRouteProps = z.discriminatedUnion('type', [
|
||||
DocumentEntryCreateFields,
|
||||
IndexEntryCreateFields,
|
||||
]);
|
||||
|
||||
export type KnowledgeBaseEntryResponse = z.infer<typeof KnowledgeBaseEntryResponse>;
|
||||
export const KnowledgeBaseEntryResponse = z.discriminatedUnion('type', [DocumentEntry, IndexEntry]);
|
||||
|
||||
|
|
|
@ -50,8 +50,7 @@ components:
|
|||
- "required"
|
||||
properties:
|
||||
kbResource:
|
||||
type: string
|
||||
description: Knowledge Base resource name for grouping entries, e.g. 'esql', 'lens-docs', etc
|
||||
$ref: "#/components/schemas/KnowledgeBaseResource"
|
||||
source:
|
||||
type: string
|
||||
description: Source document name or filepath
|
||||
|
@ -95,6 +94,9 @@ components:
|
|||
namespace:
|
||||
type: string
|
||||
description: Kibana Space, defaults to 'default' space
|
||||
global:
|
||||
type: boolean
|
||||
description: Whether this Knowledge Base Entry is global, defaults to false
|
||||
users:
|
||||
type: array
|
||||
description: Users who have access to the Knowledge Base Entry, defaults to current user. Empty array provides access to all users.
|
||||
|
@ -164,6 +166,13 @@ components:
|
|||
- $ref: "#/components/schemas/BaseResponseProps"
|
||||
- $ref: "#/components/schemas/ResponseFields"
|
||||
|
||||
KnowledgeBaseResource:
|
||||
description: Knowledge Base resource name for grouping entries, e.g. 'security_labs', 'user', etc
|
||||
type: string
|
||||
enum:
|
||||
- security_labs
|
||||
- user
|
||||
|
||||
###########
|
||||
# Document Knowledge Base Entry
|
||||
###########
|
||||
|
@ -180,8 +189,7 @@ components:
|
|||
enum: [document]
|
||||
description: Entry type
|
||||
kbResource:
|
||||
type: string
|
||||
description: Knowledge Base resource name for grouping entries, e.g. 'esql', 'lens-docs', etc
|
||||
$ref: "#/components/schemas/KnowledgeBaseResource"
|
||||
source:
|
||||
type: string
|
||||
description: Source document name or filepath
|
||||
|
@ -308,6 +316,14 @@ components:
|
|||
- $ref: "#/components/schemas/DocumentEntryUpdateFields"
|
||||
- $ref: "#/components/schemas/IndexEntryUpdateFields"
|
||||
|
||||
# Don't allow passing id to the update route body
|
||||
KnowledgeBaseEntryUpdateRouteProps:
|
||||
discriminator:
|
||||
propertyName: type
|
||||
anyOf:
|
||||
- $ref: "#/components/schemas/DocumentEntryCreateFields"
|
||||
- $ref: "#/components/schemas/IndexEntryCreateFields"
|
||||
|
||||
KnowledgeBaseEntryResponse:
|
||||
discriminator:
|
||||
propertyName: type
|
||||
|
|
|
@ -19,7 +19,7 @@ import { z } from '@kbn/zod';
|
|||
import {
|
||||
KnowledgeBaseEntryCreateProps,
|
||||
KnowledgeBaseEntryResponse,
|
||||
KnowledgeBaseEntryUpdateProps,
|
||||
KnowledgeBaseEntryUpdateRouteProps,
|
||||
DeleteResponseFields,
|
||||
} from './common_attributes.gen';
|
||||
import { NonEmptyString } from '../../common_attributes.gen';
|
||||
|
@ -83,7 +83,7 @@ export type UpdateKnowledgeBaseEntryRequestParamsInput = z.input<
|
|||
export type UpdateKnowledgeBaseEntryRequestBody = z.infer<
|
||||
typeof UpdateKnowledgeBaseEntryRequestBody
|
||||
>;
|
||||
export const UpdateKnowledgeBaseEntryRequestBody = KnowledgeBaseEntryUpdateProps;
|
||||
export const UpdateKnowledgeBaseEntryRequestBody = KnowledgeBaseEntryUpdateRouteProps;
|
||||
export type UpdateKnowledgeBaseEntryRequestBodyInput = z.input<
|
||||
typeof UpdateKnowledgeBaseEntryRequestBody
|
||||
>;
|
||||
|
|
|
@ -81,7 +81,8 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: './common_attributes.schema.yaml#/components/schemas/KnowledgeBaseEntryUpdateProps'
|
||||
$ref: './common_attributes.schema.yaml#/components/schemas/KnowledgeBaseEntryUpdateRouteProps'
|
||||
|
||||
responses:
|
||||
200:
|
||||
description: Successful request returning the updated Knowledge Base Entry
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
KnowledgeBaseEntryResponse,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { z } from '@kbn/zod';
|
||||
import { isArray } from 'lodash';
|
||||
|
||||
export const isSystemEntry = (
|
||||
entry: KnowledgeBaseEntryResponse
|
||||
|
@ -25,7 +26,8 @@ export const isSystemEntry = (
|
|||
|
||||
export const isGlobalEntry = (
|
||||
entry: KnowledgeBaseEntryResponse
|
||||
): entry is KnowledgeBaseEntryResponse => entry.users != null && !entry.users.length;
|
||||
): entry is KnowledgeBaseEntryResponse =>
|
||||
entry.global ?? (isArray(entry.users) && !entry.users.length);
|
||||
|
||||
export const isKnowledgeBaseEntryCreateProps = (
|
||||
entry: unknown
|
||||
|
|
|
@ -85,6 +85,7 @@ describe('KnowledgeBaseSettingsManagement', () => {
|
|||
createdBy: 'u_user_id_1',
|
||||
updatedAt: '2024-10-23T17:33:15.933Z',
|
||||
updatedBy: 'u_user_id_1',
|
||||
global: false,
|
||||
users: [{ name: 'Test User 1' }],
|
||||
name: 'Test Entry 1',
|
||||
namespace: 'default',
|
||||
|
@ -99,6 +100,7 @@ describe('KnowledgeBaseSettingsManagement', () => {
|
|||
createdBy: 'u_user_id_2',
|
||||
updatedAt: '2024-10-25T09:55:56.596Z',
|
||||
updatedBy: 'u_user_id_2',
|
||||
global: true,
|
||||
users: [],
|
||||
name: 'Test Entry 2',
|
||||
namespace: 'default',
|
||||
|
@ -114,6 +116,7 @@ describe('KnowledgeBaseSettingsManagement', () => {
|
|||
createdBy: 'u_user_id_1',
|
||||
updatedAt: '2024-10-25T09:55:56.596Z',
|
||||
updatedBy: 'u_user_id_1',
|
||||
global: false,
|
||||
users: [{ name: 'Test User 1' }],
|
||||
name: 'Test Entry 3',
|
||||
namespace: 'default',
|
||||
|
@ -129,6 +132,7 @@ describe('KnowledgeBaseSettingsManagement', () => {
|
|||
createdBy: 'u_user_id_3',
|
||||
updatedAt: '2024-10-23T17:33:15.933Z',
|
||||
updatedBy: 'u_user_id_3',
|
||||
global: true,
|
||||
users: [],
|
||||
name: 'Test Entry 4',
|
||||
namespace: 'default',
|
||||
|
@ -232,6 +236,7 @@ describe('KnowledgeBaseSettingsManagement', () => {
|
|||
createdBy: 'u_user_id_1',
|
||||
updatedAt: '2024-10-23T17:33:15.933Z',
|
||||
updatedBy: 'u_user_id_1',
|
||||
global: false,
|
||||
users: [{ name: 'Test User 1' }],
|
||||
name: 'A',
|
||||
namespace: 'default',
|
||||
|
@ -246,6 +251,7 @@ describe('KnowledgeBaseSettingsManagement', () => {
|
|||
createdBy: 'u_user_id_2',
|
||||
updatedAt: '2024-10-25T09:55:56.596Z',
|
||||
updatedBy: 'u_user_id_2',
|
||||
global: true,
|
||||
users: [],
|
||||
name: 'b',
|
||||
namespace: 'default',
|
||||
|
@ -261,6 +267,7 @@ describe('KnowledgeBaseSettingsManagement', () => {
|
|||
createdBy: 'u_user_id_2',
|
||||
updatedAt: '2024-10-25T09:55:56.596Z',
|
||||
updatedBy: 'u_user_id_2',
|
||||
global: true,
|
||||
users: [],
|
||||
name: 'B',
|
||||
namespace: 'default',
|
||||
|
@ -276,6 +283,7 @@ describe('KnowledgeBaseSettingsManagement', () => {
|
|||
createdBy: 'u_user_id_1',
|
||||
updatedAt: '2024-10-25T09:55:56.596Z',
|
||||
updatedBy: 'u_user_id_1',
|
||||
global: false,
|
||||
users: [{ name: 'Test User 1' }],
|
||||
name: 'a',
|
||||
namespace: 'default',
|
||||
|
@ -468,7 +476,7 @@ describe('KnowledgeBaseSettingsManagement', () => {
|
|||
});
|
||||
expect(mockCreateEntry).toHaveBeenCalledTimes(0);
|
||||
expect(mockUpdateEntry).toHaveBeenCalledWith([{ ...mockData[0], name: updatedName }]);
|
||||
});
|
||||
}, 100000000);
|
||||
|
||||
it('does not create a duplicate index entry when switching sharing option twice', async () => {
|
||||
(useFlyoutModalVisibility as jest.Mock).mockReturnValue({
|
||||
|
|
|
@ -54,7 +54,7 @@ export const documentEntry: EsDocumentEntry = {
|
|||
namespace: 'default',
|
||||
semantic_text: 'test',
|
||||
type: 'document',
|
||||
kb_resource: 'test',
|
||||
kb_resource: 'user',
|
||||
required: true,
|
||||
source: 'test',
|
||||
text: 'test',
|
||||
|
@ -105,7 +105,7 @@ export const getCreateKnowledgeBaseEntrySchemaMock = (
|
|||
source: 'test',
|
||||
text: 'test',
|
||||
name: 'test',
|
||||
kbResource: 'test',
|
||||
kbResource: 'user',
|
||||
...restProps,
|
||||
};
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ export const getUpdateKnowledgeBaseEntrySchemaMock = (
|
|||
type: 'document',
|
||||
source: 'test',
|
||||
text: 'test',
|
||||
kbResource: 'test',
|
||||
kbResource: 'user',
|
||||
id: entryId,
|
||||
});
|
||||
|
||||
|
@ -146,7 +146,7 @@ export const getKnowledgeBaseEntryMock = (
|
|||
type: 'document',
|
||||
text: 'test',
|
||||
source: 'test',
|
||||
kbResource: 'test',
|
||||
kbResource: 'user',
|
||||
required: true,
|
||||
}
|
||||
): KnowledgeBaseEntryResponse => ({
|
||||
|
@ -157,6 +157,7 @@ export const getKnowledgeBaseEntryMock = (
|
|||
createdAt: '2020-04-20T15:25:31.830Z',
|
||||
updatedAt: '2020-04-20T15:25:31.830Z',
|
||||
namespace: 'default',
|
||||
global: false,
|
||||
users: [
|
||||
{
|
||||
name: 'my_username',
|
||||
|
|
|
@ -132,6 +132,15 @@ export const getPostEvaluateRequest = ({ body }: { body: PostEvaluateRequestBody
|
|||
path: ELASTIC_AI_ASSISTANT_EVALUATE_URL,
|
||||
});
|
||||
|
||||
export const getKnowledgeBaseEntryGetRequest = (
|
||||
id: string = '04128c15-0d1b-4716-a4c5-46997ac7f3bd'
|
||||
) =>
|
||||
requestMock.create({
|
||||
method: 'get',
|
||||
path: ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BY_ID,
|
||||
params: { id },
|
||||
});
|
||||
|
||||
export const getKnowledgeBaseEntryFindRequest = () =>
|
||||
requestMock.create({
|
||||
method: 'get',
|
||||
|
|
|
@ -79,7 +79,7 @@ describe('createKnowledgeBaseEntry', () => {
|
|||
source: 'test',
|
||||
text: 'test',
|
||||
name: 'test',
|
||||
kb_resource: 'test',
|
||||
kb_resource: 'user',
|
||||
required: false,
|
||||
vector: undefined,
|
||||
},
|
||||
|
|
|
@ -47,7 +47,6 @@ export const createKnowledgeBaseEntry = async ({
|
|||
user,
|
||||
knowledgeBaseEntry,
|
||||
logger,
|
||||
global = false,
|
||||
telemetry,
|
||||
}: CreateKnowledgeBaseEntryParams): Promise<KnowledgeBaseEntryResponse | null> => {
|
||||
const createdAt = new Date().toISOString();
|
||||
|
@ -56,7 +55,6 @@ export const createKnowledgeBaseEntry = async ({
|
|||
spaceId,
|
||||
user,
|
||||
entry: knowledgeBaseEntry as unknown as KnowledgeBaseEntryCreateProps,
|
||||
global,
|
||||
});
|
||||
const telemetryPayload = {
|
||||
entryType: body.type,
|
||||
|
@ -112,14 +110,12 @@ interface TransformToUpdateSchemaProps {
|
|||
user: AuthenticatedUser;
|
||||
updatedAt: string;
|
||||
entry: KnowledgeBaseEntryUpdateProps;
|
||||
global?: boolean;
|
||||
}
|
||||
|
||||
export const transformToUpdateSchema = ({
|
||||
user,
|
||||
updatedAt,
|
||||
entry,
|
||||
global = false,
|
||||
}: TransformToUpdateSchemaProps): UpdateKnowledgeBaseEntrySchema => {
|
||||
const base = {
|
||||
id: entry.id,
|
||||
|
@ -127,7 +123,8 @@ export const transformToUpdateSchema = ({
|
|||
updated_by: user.profile_uid ?? 'unknown',
|
||||
name: entry.name,
|
||||
type: entry.type,
|
||||
users: global
|
||||
global: entry.global,
|
||||
users: entry.global
|
||||
? []
|
||||
: [
|
||||
{
|
||||
|
@ -142,6 +139,7 @@ export const transformToUpdateSchema = ({
|
|||
return {
|
||||
...base,
|
||||
...restEntry,
|
||||
users: restEntry.users ?? base.users,
|
||||
query_description: queryDescription,
|
||||
input_schema:
|
||||
entry.inputSchema?.map((schema) => ({
|
||||
|
@ -177,7 +175,6 @@ interface TransformToCreateSchemaProps {
|
|||
spaceId: string;
|
||||
user: AuthenticatedUser;
|
||||
entry: KnowledgeBaseEntryCreateProps;
|
||||
global?: boolean;
|
||||
}
|
||||
|
||||
export const transformToCreateSchema = ({
|
||||
|
@ -185,7 +182,6 @@ export const transformToCreateSchema = ({
|
|||
spaceId,
|
||||
user,
|
||||
entry,
|
||||
global = false,
|
||||
}: TransformToCreateSchemaProps): CreateKnowledgeBaseEntrySchema => {
|
||||
const base = {
|
||||
'@timestamp': createdAt,
|
||||
|
@ -196,7 +192,8 @@ export const transformToCreateSchema = ({
|
|||
name: entry.name,
|
||||
namespace: spaceId,
|
||||
type: entry.type,
|
||||
users: global
|
||||
global: entry.global,
|
||||
users: entry.global
|
||||
? []
|
||||
: [
|
||||
{
|
||||
|
@ -211,6 +208,7 @@ export const transformToCreateSchema = ({
|
|||
return {
|
||||
...base,
|
||||
...restEntry,
|
||||
users: restEntry.users ?? base.users,
|
||||
query_description: queryDescription,
|
||||
input_schema:
|
||||
entry.inputSchema?.map((schema) => ({
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
} from '../../lib/langchain/content_loaders/security_labs_loader';
|
||||
import { DynamicStructuredTool } from '@langchain/core/tools';
|
||||
import { newContentReferencesStoreMock } from '@kbn/elastic-assistant-common/impl/content_references/content_references_store/__mocks__/content_references_store.mock';
|
||||
import { KnowledgeBaseResource } from '@kbn/elastic-assistant-common';
|
||||
jest.mock('../../lib/langchain/content_loaders/security_labs_loader');
|
||||
jest.mock('p-retry');
|
||||
const date = '2023-03-28T22:27:28.159Z';
|
||||
|
@ -334,7 +335,7 @@ describe('AIAssistantKnowledgeBaseDataClient', () => {
|
|||
const documents = [
|
||||
{
|
||||
pageContent: 'Document 1',
|
||||
metadata: { kbResource: 'user', source: 'user', required: false },
|
||||
metadata: { kbResource: KnowledgeBaseResource.enum.user, source: 'user', required: false },
|
||||
},
|
||||
];
|
||||
it('should add documents to the knowledge base', async () => {
|
||||
|
@ -412,7 +413,7 @@ describe('AIAssistantKnowledgeBaseDataClient', () => {
|
|||
|
||||
expect(results).toHaveLength(1);
|
||||
expect(results[0].pageContent).toBe('test');
|
||||
expect(results[0].metadata.kbResource).toBe('test');
|
||||
expect(results[0].metadata.kbResource).toBe('user');
|
||||
});
|
||||
|
||||
it('should swallow errors during search', async () => {
|
||||
|
@ -505,7 +506,10 @@ describe('AIAssistantKnowledgeBaseDataClient', () => {
|
|||
mockOptions.manageGlobalKnowledgeBaseAIAssistant = false;
|
||||
|
||||
await expect(
|
||||
client.createKnowledgeBaseEntry({ telemetry, knowledgeBaseEntry, global: true })
|
||||
client.createKnowledgeBaseEntry({
|
||||
telemetry,
|
||||
knowledgeBaseEntry: { ...knowledgeBaseEntry, global: true },
|
||||
})
|
||||
).rejects.toThrow('User lacks privileges to create global knowledge base entries');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -53,6 +53,7 @@ import {
|
|||
} from './helpers';
|
||||
import {
|
||||
getKBUserFilter,
|
||||
isGlobalEntry,
|
||||
validateDocumentsModification,
|
||||
} from '../../routes/knowledge_base/entries/utils';
|
||||
import {
|
||||
|
@ -434,8 +435,8 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
|
|||
kbResource: doc.metadata.kbResource ?? 'unknown',
|
||||
required: doc.metadata.required ?? false,
|
||||
source: doc.metadata.source ?? 'unknown',
|
||||
global,
|
||||
},
|
||||
global,
|
||||
});
|
||||
}),
|
||||
authenticatedUser,
|
||||
|
@ -658,11 +659,9 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
|
|||
auditLogger,
|
||||
knowledgeBaseEntry,
|
||||
telemetry,
|
||||
global = false,
|
||||
}: {
|
||||
auditLogger?: AuditLogger;
|
||||
knowledgeBaseEntry: KnowledgeBaseEntryCreateProps;
|
||||
global?: boolean;
|
||||
telemetry: AnalyticsServiceSetup;
|
||||
}): Promise<KnowledgeBaseEntryResponse | null> => {
|
||||
const authenticatedUser = this.options.currentUser;
|
||||
|
@ -673,7 +672,7 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
|
|||
);
|
||||
}
|
||||
|
||||
if (global && !this.options.manageGlobalKnowledgeBaseAIAssistant) {
|
||||
if (isGlobalEntry(knowledgeBaseEntry) && !this.options.manageGlobalKnowledgeBaseAIAssistant) {
|
||||
throw new Error('User lacks privileges to create global knowledge base entries');
|
||||
}
|
||||
|
||||
|
@ -690,7 +689,6 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
|
|||
spaceId: this.spaceId,
|
||||
user: authenticatedUser,
|
||||
knowledgeBaseEntry,
|
||||
global,
|
||||
telemetry,
|
||||
});
|
||||
};
|
||||
|
@ -704,12 +702,14 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
|
|||
public updateKnowledgeBaseEntry = async ({
|
||||
auditLogger,
|
||||
knowledgeBaseEntry,
|
||||
telemetry,
|
||||
}: {
|
||||
auditLogger?: AuditLogger;
|
||||
knowledgeBaseEntry: KnowledgeBaseEntryUpdateProps;
|
||||
telemetry: AnalyticsServiceSetup;
|
||||
}): Promise<{
|
||||
errors: BulkOperationError[];
|
||||
updatedEntry: KnowledgeBaseEntryResponse;
|
||||
updatedEntry: KnowledgeBaseEntryResponse | null | undefined;
|
||||
}> => {
|
||||
const authenticatedUser = this.options.currentUser;
|
||||
|
||||
|
@ -734,7 +734,6 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
|
|||
user: authenticatedUser,
|
||||
updatedAt: changedAt,
|
||||
entry: knowledgeBaseEntry,
|
||||
global: knowledgeBaseEntry.users != null && knowledgeBaseEntry.users.length === 0,
|
||||
}),
|
||||
],
|
||||
getUpdateScript: (entry: UpdateKnowledgeBaseEntrySchema) => getUpdateScript({ entry }),
|
||||
|
|
|
@ -22,6 +22,7 @@ describe('transforms', () => {
|
|||
id: '1',
|
||||
createdAt: documentEntry.created_at,
|
||||
createdBy: documentEntry.created_by,
|
||||
global: false,
|
||||
updatedAt: documentEntry.updated_at,
|
||||
updatedBy: documentEntry.updated_by,
|
||||
type: documentEntry.type,
|
||||
|
@ -47,6 +48,7 @@ describe('transforms', () => {
|
|||
id: documentEntry.id,
|
||||
createdAt: documentEntry.created_at,
|
||||
createdBy: documentEntry.created_by,
|
||||
global: false,
|
||||
updatedAt: documentEntry.updated_at,
|
||||
updatedBy: documentEntry.updated_by,
|
||||
type: documentEntry.type,
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
IndexEntry,
|
||||
IndexEntryType,
|
||||
KnowledgeBaseEntryResponse,
|
||||
KnowledgeBaseResource,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { EsKnowledgeBaseEntrySchema, LegacyEsKnowledgeBaseEntrySchema } from './types';
|
||||
|
||||
|
@ -49,6 +50,7 @@ const transformEsSchemaToEntry = (
|
|||
createdBy: esKbEntry.created_by,
|
||||
updatedAt: esKbEntry.updated_at,
|
||||
updatedBy: esKbEntry.updated_by,
|
||||
global: !esKbEntry.users?.length,
|
||||
users:
|
||||
esKbEntry.users?.map((user) => ({
|
||||
id: user.id,
|
||||
|
@ -79,6 +81,7 @@ const transformEsSchemaToEntry = (
|
|||
createdBy: esKbEntry.created_by,
|
||||
updatedAt: esKbEntry.updated_at,
|
||||
updatedBy: esKbEntry.updated_by,
|
||||
global: !esKbEntry.users?.length,
|
||||
users:
|
||||
esKbEntry.users?.map((user) => ({
|
||||
id: user.id,
|
||||
|
@ -116,6 +119,7 @@ const getDocumentEntryFromLegacyKbEntry = (
|
|||
createdBy: legacyEsKbDoc.created_by,
|
||||
updatedAt: legacyEsKbDoc.updated_at,
|
||||
updatedBy: legacyEsKbDoc.updated_by,
|
||||
global: !legacyEsKbDoc.users?.length,
|
||||
users:
|
||||
legacyEsKbDoc.users?.map((user) => ({
|
||||
id: user.id,
|
||||
|
@ -125,7 +129,7 @@ const getDocumentEntryFromLegacyKbEntry = (
|
|||
name: legacyEsKbDoc.text,
|
||||
namespace: legacyEsKbDoc.namespace,
|
||||
type: DocumentEntryType.value,
|
||||
kbResource: legacyEsKbDoc.metadata?.kbResource ?? 'unknown',
|
||||
kbResource: (legacyEsKbDoc.metadata?.kbResource as KnowledgeBaseResource) ?? 'user',
|
||||
source: legacyEsKbDoc.metadata?.source ?? 'unknown',
|
||||
required: legacyEsKbDoc.metadata?.required ?? false,
|
||||
text: legacyEsKbDoc.text,
|
||||
|
|
|
@ -5,7 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { DocumentEntryType, IndexEntryType } from '@kbn/elastic-assistant-common';
|
||||
import type {
|
||||
DocumentEntryType,
|
||||
IndexEntryType,
|
||||
KnowledgeBaseResource,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
|
||||
export type EsKnowledgeBaseEntrySchema = EsDocumentEntry | EsIndexEntry;
|
||||
|
||||
|
@ -23,7 +27,7 @@ export interface EsDocumentEntry {
|
|||
name: string;
|
||||
namespace: string;
|
||||
type: DocumentEntryType;
|
||||
kb_resource: string;
|
||||
kb_resource: KnowledgeBaseResource;
|
||||
required: boolean;
|
||||
source: string;
|
||||
text: string;
|
||||
|
|
|
@ -47,7 +47,7 @@ describe('Perform bulk action route', () => {
|
|||
docs_deleted: [],
|
||||
errors: [],
|
||||
});
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser1);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
|
||||
bulkActionAnonymizationFieldsRoute(server.router, logger);
|
||||
});
|
||||
|
||||
|
|
|
@ -165,7 +165,7 @@ export const bulkActionAnonymizationFieldsRoute = (
|
|||
try {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
// Perform license and authenticated user checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
|
|
@ -33,7 +33,7 @@ describe('Find user anonymization fields route', () => {
|
|||
clients.elasticAssistant.getAIAssistantAnonymizationFieldsDataClient.findDocuments.mockResolvedValue(
|
||||
Promise.resolve(getFindAnonymizationFieldsResultWithSingleHit())
|
||||
);
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue({
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue({
|
||||
username: 'my_username',
|
||||
authentication_realm: {
|
||||
type: 'my_realm_type',
|
||||
|
@ -41,7 +41,7 @@ describe('Find user anonymization fields route', () => {
|
|||
},
|
||||
} as AuthenticatedUser);
|
||||
logger = loggingSystemMock.createLogger();
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser1);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
|
||||
findAnonymizationFieldsRoute(server.router, logger);
|
||||
});
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ export const findAnonymizationFieldsRoute = (
|
|||
const { query } = request;
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
// Perform license and authenticated user checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
|
|
@ -84,7 +84,7 @@ const mockCurrentAd = transformESSearchToAttackDiscovery(getAttackDiscoverySearc
|
|||
describe('getAttackDiscoveryRoute', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser);
|
||||
context.elasticAssistant.getAttackDiscoveryDataClient.mockResolvedValue(mockDataClient);
|
||||
|
||||
getAttackDiscoveryRoute(server.router);
|
||||
|
@ -105,7 +105,7 @@ describe('getAttackDiscoveryRoute', () => {
|
|||
});
|
||||
|
||||
it('should handle missing authenticated user', async () => {
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(null);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce(null);
|
||||
const response = await server.inject(
|
||||
getAttackDiscoveryRequest('connector-id'),
|
||||
requestContextMock.convertContext(context)
|
||||
|
|
|
@ -51,7 +51,7 @@ export const getAttackDiscoveryRoute = (router: IRouter<ElasticAssistantRequestH
|
|||
try {
|
||||
const dataClient = await assistantContext.getAttackDiscoveryDataClient();
|
||||
|
||||
const authenticatedUser = assistantContext.getCurrentUser();
|
||||
const authenticatedUser = await assistantContext.getCurrentUser();
|
||||
const connectorId = decodeURIComponent(request.params.connectorId);
|
||||
if (authenticatedUser == null) {
|
||||
return resp.error({
|
||||
|
|
|
@ -51,7 +51,7 @@ describe('cancelAttackDiscoveryRoute', () => {
|
|||
...mockCurrentAd,
|
||||
status: 'canceled',
|
||||
});
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser);
|
||||
context.elasticAssistant.getAttackDiscoveryDataClient.mockResolvedValue(mockDataClient);
|
||||
|
||||
cancelAttackDiscoveryRoute(server.router);
|
||||
|
@ -70,7 +70,7 @@ describe('cancelAttackDiscoveryRoute', () => {
|
|||
});
|
||||
|
||||
it('should handle missing authenticated user', async () => {
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(null);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce(null);
|
||||
const response = await server.inject(
|
||||
getCancelAttackDiscoveryRequest('connector-id'),
|
||||
requestContextMock.convertContext(context)
|
||||
|
|
|
@ -57,7 +57,7 @@ export const cancelAttackDiscoveryRoute = (
|
|||
try {
|
||||
const dataClient = await assistantContext.getAttackDiscoveryDataClient();
|
||||
|
||||
const authenticatedUser = assistantContext.getCurrentUser();
|
||||
const authenticatedUser = await assistantContext.getCurrentUser();
|
||||
const connectorId = decodeURIComponent(request.params.connectorId);
|
||||
if (authenticatedUser == null) {
|
||||
return resp.error({
|
||||
|
|
|
@ -72,7 +72,7 @@ const runningAd = {
|
|||
describe('postAttackDiscoveryRoute', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser);
|
||||
context.elasticAssistant.getAttackDiscoveryDataClient.mockResolvedValue(mockDataClient);
|
||||
context.elasticAssistant.actions = actionsMock.createStart();
|
||||
postAttackDiscoveryRoute(server.router);
|
||||
|
@ -93,7 +93,7 @@ describe('postAttackDiscoveryRoute', () => {
|
|||
});
|
||||
|
||||
it('should handle missing authenticated user', async () => {
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(null);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce(null);
|
||||
const response = await server.inject(
|
||||
postAttackDiscoveryRequest(mockRequestBody),
|
||||
requestContextMock.convertContext(context)
|
||||
|
|
|
@ -73,7 +73,7 @@ export const postAttackDiscoveryRoute = (
|
|||
const actions = (await context.elasticAssistant).actions;
|
||||
const actionsClient = await actions.getActionsClientWithRequest(request);
|
||||
const dataClient = await assistantContext.getAttackDiscoveryDataClient();
|
||||
const authenticatedUser = assistantContext.getCurrentUser();
|
||||
const authenticatedUser = await assistantContext.getCurrentUser();
|
||||
if (authenticatedUser == null) {
|
||||
return resp.error({
|
||||
body: `Authenticated user not found`,
|
||||
|
|
|
@ -77,7 +77,7 @@ export const chatCompleteRoute = (
|
|||
(await ctx.elasticAssistant.llmTasks.retrieveDocumentationAvailable()) ?? false;
|
||||
|
||||
// Perform license and authenticated user checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
|
|
@ -63,7 +63,7 @@ describe('getDefendInsightRoute', () => {
|
|||
mockDataClient = getDefaultDataClient();
|
||||
mockCurrentInsight = transformESSearchToDefendInsights(getDefendInsightsSearchEsMock())[0];
|
||||
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser);
|
||||
context.elasticAssistant.getDefendInsightsDataClient.mockResolvedValue(mockDataClient);
|
||||
getDefendInsightRoute(server.router);
|
||||
(updateDefendInsightLastViewedAt as jest.Mock).mockResolvedValue(mockCurrentInsight);
|
||||
|
@ -109,7 +109,7 @@ describe('getDefendInsightRoute', () => {
|
|||
});
|
||||
|
||||
it('should handle missing authenticated user', async () => {
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValueOnce(null);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce(null);
|
||||
const response = await server.inject(
|
||||
getDefendInsightRequest('insight-id1'),
|
||||
requestContextMock.convertContext(context)
|
||||
|
|
|
@ -72,7 +72,7 @@ export const getDefendInsightRoute = (router: IRouter<ElasticAssistantRequestHan
|
|||
}
|
||||
|
||||
const dataClient = await assistantContext.getDefendInsightsDataClient();
|
||||
const authenticatedUser = assistantContext.getCurrentUser();
|
||||
const authenticatedUser = await assistantContext.getCurrentUser();
|
||||
if (authenticatedUser == null) {
|
||||
return resp.error({
|
||||
body: `Authenticated user not found`,
|
||||
|
|
|
@ -63,7 +63,7 @@ describe('getDefendInsightsRoute', () => {
|
|||
mockDataClient = getDefaultDataClient();
|
||||
mockCurrentInsights = transformESSearchToDefendInsights(getDefendInsightsSearchEsMock());
|
||||
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser);
|
||||
context.elasticAssistant.getDefendInsightsDataClient.mockResolvedValue(mockDataClient);
|
||||
getDefendInsightsRoute(server.router);
|
||||
(updateDefendInsightsLastViewedAt as jest.Mock).mockResolvedValue(mockCurrentInsights);
|
||||
|
@ -109,7 +109,7 @@ describe('getDefendInsightsRoute', () => {
|
|||
});
|
||||
|
||||
it('should handle missing authenticated user', async () => {
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValueOnce(null);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce(null);
|
||||
const response = await server.inject(
|
||||
getDefendInsightsRequest({ connector_id: 'connector-id1' }),
|
||||
requestContextMock.convertContext(context)
|
||||
|
|
|
@ -75,7 +75,7 @@ export const getDefendInsightsRoute = (router: IRouter<ElasticAssistantRequestHa
|
|||
|
||||
const dataClient = await assistantContext.getDefendInsightsDataClient();
|
||||
|
||||
const authenticatedUser = assistantContext.getCurrentUser();
|
||||
const authenticatedUser = await assistantContext.getCurrentUser();
|
||||
if (authenticatedUser == null) {
|
||||
return resp.error({
|
||||
body: `Authenticated user not found`,
|
||||
|
|
|
@ -106,7 +106,7 @@ describe('postDefendInsightsRoute', () => {
|
|||
});
|
||||
(isDefendInsightsEnabled as jest.Mock).mockResolvedValue(true);
|
||||
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser);
|
||||
context.elasticAssistant.getDefendInsightsDataClient.mockResolvedValue(mockDataClient);
|
||||
context.elasticAssistant.actions = actionsMock.createStart();
|
||||
|
||||
|
@ -141,7 +141,7 @@ describe('postDefendInsightsRoute', () => {
|
|||
});
|
||||
|
||||
it('should handle missing authenticated user', async () => {
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValueOnce(null);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce(null);
|
||||
const response = await server.inject(
|
||||
postDefendInsightsRequest(mockRequestBody),
|
||||
requestContextMock.convertContext(context)
|
||||
|
|
|
@ -102,7 +102,7 @@ export const postDefendInsightsRoute = (router: IRouter<ElasticAssistantRequestH
|
|||
const actions = assistantContext.actions;
|
||||
const actionsClient = await actions.getActionsClientWithRequest(request);
|
||||
const dataClient = await assistantContext.getDefendInsightsDataClient();
|
||||
const authenticatedUser = assistantContext.getCurrentUser();
|
||||
const authenticatedUser = await assistantContext.getCurrentUser();
|
||||
if (authenticatedUser == null) {
|
||||
return resp.error({
|
||||
body: `Authenticated user not found`,
|
||||
|
|
|
@ -49,7 +49,7 @@ export const getEvaluateRoute = (router: IRouter<ElasticAssistantRequestHandlerC
|
|||
const logger = assistantContext.logger.get('evaluate');
|
||||
|
||||
// Perform license, authenticated user and evaluation FF checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
capability: 'assistantModelEvaluation',
|
||||
context: ctx,
|
||||
request,
|
||||
|
|
|
@ -46,7 +46,7 @@ describe('Post Evaluate Route', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser);
|
||||
|
||||
postEvaluateRoute(server.router, mockGetElser);
|
||||
});
|
||||
|
|
|
@ -103,7 +103,7 @@ export const postEvaluateRoute = (
|
|||
const savedObjectsClient = ctx.elasticAssistant.savedObjectsClient;
|
||||
|
||||
// Perform license, authenticated user and evaluation FF checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
capability: 'assistantModelEvaluation',
|
||||
context: ctx,
|
||||
request,
|
||||
|
|
|
@ -439,12 +439,12 @@ type PerformChecks =
|
|||
isSuccess: false;
|
||||
response: IKibanaResponse;
|
||||
};
|
||||
export const performChecks = ({
|
||||
export const performChecks = async ({
|
||||
capability,
|
||||
context,
|
||||
request,
|
||||
response,
|
||||
}: PerformChecksParams): PerformChecks => {
|
||||
}: PerformChecksParams): Promise<PerformChecks> => {
|
||||
const assistantResponse = buildResponse(response);
|
||||
|
||||
if (!hasAIAssistantLicense(context.licensing.license)) {
|
||||
|
@ -458,7 +458,7 @@ export const performChecks = ({
|
|||
};
|
||||
}
|
||||
|
||||
const currentUser = context.elasticAssistant.getCurrentUser();
|
||||
const currentUser = await context.elasticAssistant.getCurrentUser();
|
||||
|
||||
if (currentUser == null) {
|
||||
return {
|
||||
|
|
|
@ -156,9 +156,9 @@ describe('Bulk actions knowledge base entry route', () => {
|
|||
);
|
||||
});
|
||||
test('handles all three bulk update actions at once', async () => {
|
||||
clients.elasticAssistant.getAIAssistantKnowledgeBaseDataClient.findDocuments
|
||||
.mockResolvedValueOnce(Promise.resolve(getEmptyFindResult()))
|
||||
.mockResolvedValue(Promise.resolve(getFindKnowledgeBaseEntriesResultWithSingleHit()));
|
||||
clients.elasticAssistant.getAIAssistantKnowledgeBaseDataClient.findDocuments.mockResolvedValue(
|
||||
Promise.resolve(getFindKnowledgeBaseEntriesResultWithSingleHit())
|
||||
);
|
||||
const response = await server.inject(
|
||||
getBulkActionKnowledgeBaseEntryRequest({
|
||||
create: [getCreateKnowledgeBaseEntrySchemaMock()],
|
||||
|
@ -201,7 +201,7 @@ describe('Bulk actions knowledge base entry route', () => {
|
|||
);
|
||||
});
|
||||
test('returns 401 Unauthorized when request context getCurrentUser is not defined', async () => {
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValueOnce(null);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce(null);
|
||||
const response = await server.inject(
|
||||
getBulkActionKnowledgeBaseEntryRequest({
|
||||
create: [getCreateKnowledgeBaseEntrySchemaMock()],
|
||||
|
@ -214,11 +214,9 @@ describe('Bulk actions knowledge base entry route', () => {
|
|||
|
||||
describe('unhappy paths', () => {
|
||||
test('catches error if creation throws', async () => {
|
||||
clients.elasticAssistant.getAIAssistantKnowledgeBaseDataClient.findDocuments.mockImplementation(
|
||||
async () => {
|
||||
throw new Error('Test error');
|
||||
}
|
||||
);
|
||||
mockBulk.mockImplementationOnce(async () => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
const response = await server.inject(
|
||||
getBulkActionKnowledgeBaseEntryRequest({
|
||||
create: [getCreateKnowledgeBaseEntrySchemaMock()],
|
||||
|
|
|
@ -48,7 +48,7 @@ import {
|
|||
transformToCreateSchema,
|
||||
transformToUpdateSchema,
|
||||
} from '../../../ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry';
|
||||
import { validateDocumentsModification } from './utils';
|
||||
import { isGlobalEntry, validateDocumentsModification } from './utils';
|
||||
|
||||
export interface BulkOperationError {
|
||||
message: string;
|
||||
|
@ -200,7 +200,7 @@ export const bulkActionKnowledgeBaseEntriesRoute = (router: ElasticAssistantPlug
|
|||
const logger = ctx.elasticAssistant.logger;
|
||||
|
||||
// Perform license, authenticated user and FF checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
@ -241,28 +241,10 @@ export const bulkActionKnowledgeBaseEntriesRoute = (router: ElasticAssistantPlug
|
|||
if (body.create && body.create.length > 0) {
|
||||
// RBAC validation
|
||||
body.create.forEach((entry) => {
|
||||
const isGlobal = entry.users != null && entry.users.length === 0;
|
||||
if (isGlobal && !manageGlobalKnowledgeBaseAIAssistant) {
|
||||
if (isGlobalEntry(entry) && !manageGlobalKnowledgeBaseAIAssistant) {
|
||||
throw new Error(`User lacks privileges to create global knowledge base entries`);
|
||||
}
|
||||
});
|
||||
|
||||
const result = await kbDataClient?.findDocuments<EsKnowledgeBaseEntrySchema>({
|
||||
perPage: 100,
|
||||
page: 1,
|
||||
filter: `users:{ id: "${authenticatedUser?.profile_uid}" }`,
|
||||
fields: [],
|
||||
});
|
||||
if (result?.data != null && result.total > 0) {
|
||||
return assistantResponse.error({
|
||||
statusCode: 409,
|
||||
body: `Knowledge Base Entry id's: "${transformESSearchToKnowledgeBaseEntry(
|
||||
result.data
|
||||
)
|
||||
.map((c) => c.id)
|
||||
.join(',')}" already exists`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await validateDocumentsModification(
|
||||
|
@ -293,7 +275,6 @@ export const bulkActionKnowledgeBaseEntriesRoute = (router: ElasticAssistantPlug
|
|||
spaceId,
|
||||
user: authenticatedUser,
|
||||
entry,
|
||||
global: entry.users != null && entry.users.length === 0,
|
||||
})
|
||||
),
|
||||
documentsToDelete: body.delete?.ids,
|
||||
|
@ -302,7 +283,6 @@ export const bulkActionKnowledgeBaseEntriesRoute = (router: ElasticAssistantPlug
|
|||
user: authenticatedUser,
|
||||
updatedAt: changedAt,
|
||||
entry,
|
||||
global: entry.users != null && entry.users.length === 0,
|
||||
})
|
||||
),
|
||||
getUpdateScript: (entry: UpdateKnowledgeBaseEntrySchema) => getUpdateScript({ entry }),
|
||||
|
|
|
@ -38,7 +38,7 @@ describe('Create knowledge base entry route', () => {
|
|||
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
|
||||
elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse())
|
||||
);
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser1);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
|
||||
createKnowledgeBaseEntryRoute(server.router);
|
||||
});
|
||||
|
||||
|
@ -52,7 +52,7 @@ describe('Create knowledge base entry route', () => {
|
|||
});
|
||||
|
||||
test('returns 401 Unauthorized when request context getCurrentUser is not defined', async () => {
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValueOnce(null);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce(null);
|
||||
const response = await server.inject(
|
||||
getCreateKnowledgeBaseEntryRequest(),
|
||||
requestContextMock.convertContext(context)
|
||||
|
|
|
@ -48,7 +48,7 @@ export const createKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRout
|
|||
const logger = ctx.elasticAssistant.logger;
|
||||
|
||||
// Perform license, authenticated user and FF checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
@ -61,8 +61,10 @@ export const createKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRout
|
|||
|
||||
logger.debug(() => `Creating KB Entry:\n${JSON.stringify(request.body)}`);
|
||||
const createResponse = await kbDataClient?.createKnowledgeBaseEntry({
|
||||
knowledgeBaseEntry: request.body,
|
||||
global: request.body.users != null && request.body.users.length === 0,
|
||||
knowledgeBaseEntry: {
|
||||
...request.body,
|
||||
...(request.body.global ? { users: [] } : {}),
|
||||
},
|
||||
auditLogger: ctx.elasticAssistant.auditLogger,
|
||||
telemetry: ctx.elasticAssistant.telemetry,
|
||||
});
|
||||
|
|
|
@ -33,7 +33,7 @@ describe('Delete knowledge base entry route', () => {
|
|||
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
|
||||
elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse())
|
||||
);
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser1);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
|
||||
deleteKnowledgeBaseEntryRoute(server.router);
|
||||
});
|
||||
|
||||
|
@ -47,7 +47,7 @@ describe('Delete knowledge base entry route', () => {
|
|||
});
|
||||
|
||||
test('returns 401 Unauthorized when request context getCurrentUser is not defined', async () => {
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValueOnce(null);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce(null);
|
||||
const response = await server.inject(
|
||||
getDeleteKnowledgeBaseEntryRequest({ id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd' }),
|
||||
requestContextMock.convertContext(context)
|
||||
|
|
|
@ -52,7 +52,7 @@ export const deleteKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRout
|
|||
const logger = ctx.elasticAssistant.logger;
|
||||
|
||||
// Perform license, authenticated user and FF checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('Find Knowledge Base Entries route', () => {
|
|||
beforeEach(() => {
|
||||
server = serverMock.create();
|
||||
({ clients, context } = requestContextMock.createTools());
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser);
|
||||
clients.elasticAssistant.getAIAssistantKnowledgeBaseDataClient.findDocuments.mockResolvedValue(
|
||||
Promise.resolve(getFindKnowledgeBaseEntriesResultWithSingleHit())
|
||||
);
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_FIND,
|
||||
FindKnowledgeBaseEntriesRequestQuery,
|
||||
FindKnowledgeBaseEntriesResponse,
|
||||
KnowledgeBaseResource,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common';
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
|
@ -59,7 +60,7 @@ export const findKnowledgeBaseEntriesRoute = (router: ElasticAssistantPluginRout
|
|||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
|
||||
// Perform license, authenticated user and FF checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
@ -107,7 +108,7 @@ export const findKnowledgeBaseEntriesRoute = (router: ElasticAssistantPluginRout
|
|||
const systemEntries = [
|
||||
{
|
||||
bucketId: 'securityLabsId',
|
||||
kbResource: SECURITY_LABS_RESOURCE,
|
||||
kbResource: SECURITY_LABS_RESOURCE as KnowledgeBaseResource,
|
||||
name: 'Security Labs',
|
||||
required: true,
|
||||
},
|
||||
|
@ -138,6 +139,7 @@ export const findKnowledgeBaseEntriesRoute = (router: ElasticAssistantPluginRout
|
|||
createdBy: entry.created_by,
|
||||
updatedAt: entry.updated_at,
|
||||
updatedBy: entry.updated_by,
|
||||
global: true,
|
||||
users: [],
|
||||
name,
|
||||
namespace: entry.namespace,
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 { getKnowledgeBaseEntryGetRequest } from '../../../__mocks__/request';
|
||||
import { serverMock } from '../../../__mocks__/server';
|
||||
import { requestContextMock } from '../../../__mocks__/request_context';
|
||||
import {
|
||||
getEmptyFindResult,
|
||||
getFindKnowledgeBaseEntriesResultWithSingleHit,
|
||||
} from '../../../__mocks__/response';
|
||||
import { getKnowledgeBaseEntryRoute } from './get_route';
|
||||
import type { AuthenticatedUser } from '@kbn/core-security-common';
|
||||
const mockUser = {
|
||||
username: 'my_username',
|
||||
authentication_realm: {
|
||||
type: 'my_realm_type',
|
||||
name: 'my_realm_name',
|
||||
},
|
||||
} as AuthenticatedUser;
|
||||
|
||||
describe('Get Knowledge Base Entry route', () => {
|
||||
let server: ReturnType<typeof serverMock.create>;
|
||||
let { clients, context } = requestContextMock.createTools();
|
||||
beforeEach(() => {
|
||||
server = serverMock.create();
|
||||
({ clients, context } = requestContextMock.createTools());
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser);
|
||||
getKnowledgeBaseEntryRoute(server.router);
|
||||
});
|
||||
|
||||
describe('status codes', () => {
|
||||
test('returns 200 if entry exists', async () => {
|
||||
clients.elasticAssistant.getAIAssistantKnowledgeBaseDataClient.findDocuments.mockResolvedValue(
|
||||
Promise.resolve(getFindKnowledgeBaseEntriesResultWithSingleHit())
|
||||
);
|
||||
const response = await server.inject(
|
||||
getKnowledgeBaseEntryGetRequest('04128c15-0d1b-4716-a4c5-46997ac7f3bd'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
});
|
||||
|
||||
test('returns 404 if entry does not exists', async () => {
|
||||
clients.elasticAssistant.getAIAssistantKnowledgeBaseDataClient.findDocuments.mockResolvedValue(
|
||||
Promise.resolve(getEmptyFindResult())
|
||||
);
|
||||
const response = await server.inject(
|
||||
getKnowledgeBaseEntryGetRequest('04128c15-0d1b-4716-a4c5-46997ac7f3bd'),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(404);
|
||||
});
|
||||
|
||||
test('catches error if search throws error', async () => {
|
||||
clients.elasticAssistant.getAIAssistantKnowledgeBaseDataClient.findDocuments.mockImplementation(
|
||||
async () => {
|
||||
throw new Error('Test error');
|
||||
}
|
||||
);
|
||||
const response = await server.inject(
|
||||
getKnowledgeBaseEntryGetRequest(),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: 'Test error',
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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 { IKibanaResponse } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
|
||||
import {
|
||||
API_VERSIONS,
|
||||
ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BY_ID,
|
||||
ReadKnowledgeBaseEntryRequestParams,
|
||||
ReadKnowledgeBaseEntryResponse,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common';
|
||||
import { ElasticAssistantPluginRouter } from '../../../types';
|
||||
import { buildResponse } from '../../utils';
|
||||
|
||||
import { performChecks } from '../../helpers';
|
||||
import { transformESSearchToKnowledgeBaseEntry } from '../../../ai_assistant_data_clients/knowledge_base/transforms';
|
||||
import { EsKnowledgeBaseEntrySchema } from '../../../ai_assistant_data_clients/knowledge_base/types';
|
||||
import { getKBUserFilter } from './utils';
|
||||
|
||||
export const getKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRouter) => {
|
||||
router.versioned
|
||||
.get({
|
||||
access: 'public',
|
||||
path: ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BY_ID,
|
||||
security: {
|
||||
authz: {
|
||||
requiredPrivileges: ['elasticAssistant'],
|
||||
},
|
||||
},
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: API_VERSIONS.public.v1,
|
||||
validate: {
|
||||
request: {
|
||||
params: buildRouteValidationWithZod(ReadKnowledgeBaseEntryRequestParams),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (
|
||||
context,
|
||||
request,
|
||||
response
|
||||
): Promise<IKibanaResponse<ReadKnowledgeBaseEntryResponse>> => {
|
||||
const assistantResponse = buildResponse(response);
|
||||
try {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
|
||||
// Perform license, authenticated user and FF checks
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
});
|
||||
if (!checkResponse.isSuccess) {
|
||||
return checkResponse.response;
|
||||
}
|
||||
|
||||
const kbDataClient = await ctx.elasticAssistant.getAIAssistantKnowledgeBaseDataClient();
|
||||
const currentUser = checkResponse.currentUser;
|
||||
const userFilter = getKBUserFilter(currentUser);
|
||||
const systemFilter = ` AND _id: "${request.params.id}"`;
|
||||
|
||||
const result = await kbDataClient?.findDocuments<EsKnowledgeBaseEntrySchema>({
|
||||
perPage: 1,
|
||||
page: 1,
|
||||
sortField: 'created_at',
|
||||
sortOrder: 'desc',
|
||||
filter: `${userFilter}${systemFilter}`,
|
||||
fields: ['*'],
|
||||
});
|
||||
|
||||
if (!result?.data?.hits.hits.length) {
|
||||
return response.notFound();
|
||||
}
|
||||
|
||||
return response.ok({
|
||||
body: transformESSearchToKnowledgeBaseEntry(result.data)[0],
|
||||
});
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
return assistantResponse.error({
|
||||
body: error.message,
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -45,7 +45,7 @@ describe('Update knowledge base entry route', () => {
|
|||
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
|
||||
elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse())
|
||||
);
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser1);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
|
||||
updateKnowledgeBaseEntryRoute(server.router);
|
||||
});
|
||||
|
||||
|
@ -66,7 +66,7 @@ describe('Update knowledge base entry route', () => {
|
|||
});
|
||||
|
||||
test('returns 401 Unauthorized when request context getCurrentUser is not defined', async () => {
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValueOnce(null);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce(null);
|
||||
const response = await server.inject(
|
||||
getUpdateKnowledgeBaseEntryRequest({
|
||||
params: { id: '1' },
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/schemas/common';
|
||||
import {
|
||||
KnowledgeBaseEntryResponse,
|
||||
KnowledgeBaseEntryUpdateProps,
|
||||
KnowledgeBaseEntryUpdateRouteProps,
|
||||
} from '@kbn/elastic-assistant-common/impl/schemas/knowledge_base/entries/common_attributes.gen';
|
||||
import { ElasticAssistantPluginRouter } from '../../../types';
|
||||
import { buildResponse } from '../../utils';
|
||||
|
@ -39,7 +39,7 @@ export const updateKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRout
|
|||
validate: {
|
||||
request: {
|
||||
params: buildRouteValidationWithZod(UpdateKnowledgeBaseEntryRequestParams),
|
||||
body: buildRouteValidationWithZod(KnowledgeBaseEntryUpdateProps),
|
||||
body: buildRouteValidationWithZod(KnowledgeBaseEntryUpdateRouteProps),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -50,7 +50,7 @@ export const updateKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRout
|
|||
const logger = ctx.elasticAssistant.logger;
|
||||
|
||||
// Perform license, authenticated user and FF checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
@ -63,8 +63,13 @@ export const updateKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRout
|
|||
|
||||
const kbDataClient = await ctx.elasticAssistant.getAIAssistantKnowledgeBaseDataClient();
|
||||
const updateResponse = await kbDataClient?.updateKnowledgeBaseEntry({
|
||||
knowledgeBaseEntry: { ...request.body, id: request.params.id },
|
||||
knowledgeBaseEntry: {
|
||||
...request.body,
|
||||
id: request.params.id,
|
||||
...(request.body.global ? { users: [] } : {}),
|
||||
},
|
||||
auditLogger: ctx.elasticAssistant.auditLogger,
|
||||
telemetry: ctx.elasticAssistant.telemetry,
|
||||
});
|
||||
|
||||
if (updateResponse?.updatedEntry) {
|
||||
|
@ -74,7 +79,7 @@ export const updateKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRout
|
|||
}
|
||||
|
||||
return assistantResponse.error({
|
||||
body: updateResponse?.errors?.[0].message ?? `Knowledge Base Entry was not created`,
|
||||
body: updateResponse?.errors?.[0].message ?? `Knowledge Base Entry was not updated`,
|
||||
statusCode: 400,
|
||||
});
|
||||
} catch (err) {
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
*/
|
||||
|
||||
import { AuthenticatedUser } from '@kbn/core-security-common';
|
||||
import {
|
||||
KnowledgeBaseEntryCreateProps,
|
||||
KnowledgeBaseEntryResponse,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { isArray } from 'lodash';
|
||||
import { AIAssistantKnowledgeBaseDataClient } from '../../../ai_assistant_data_clients/knowledge_base';
|
||||
import { transformESSearchToKnowledgeBaseEntry } from '../../../ai_assistant_data_clients/knowledge_base/transforms';
|
||||
import { EsKnowledgeBaseEntrySchema } from '../../../ai_assistant_data_clients/knowledge_base/types';
|
||||
|
@ -28,6 +33,9 @@ export const getKBUserFilter = (user: AuthenticatedUser | null) => {
|
|||
return `(${globalFilter}${userFilter})`;
|
||||
};
|
||||
|
||||
export const isGlobalEntry = (entry: KnowledgeBaseEntryResponse | KnowledgeBaseEntryCreateProps) =>
|
||||
entry.global ?? (isArray(entry.users) && !entry.users.length);
|
||||
|
||||
export const validateDocumentsModification = async (
|
||||
kbDataClient: AIAssistantKnowledgeBaseDataClient | null,
|
||||
authenticatedUser: AuthenticatedUser | null,
|
||||
|
@ -50,8 +58,7 @@ export const validateDocumentsModification = async (
|
|||
const availableEntries = entries ? transformESSearchToKnowledgeBaseEntry(entries.data) : [];
|
||||
availableEntries.forEach((entry) => {
|
||||
// RBAC validation
|
||||
const isGlobal = entry.users != null && entry.users.length === 0;
|
||||
if (isGlobal && !manageGlobalKnowledgeBaseAIAssistant) {
|
||||
if (isGlobalEntry(entry) && !manageGlobalKnowledgeBaseAIAssistant) {
|
||||
throw new Error(`User lacks privileges to ${operation} global knowledge base entries`);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -27,7 +27,7 @@ describe('Get Knowledge Base Status Route', () => {
|
|||
beforeEach(() => {
|
||||
server = serverMock.create();
|
||||
({ context } = requestContextMock.createTools());
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser);
|
||||
context.elasticAssistant.getAIAssistantKnowledgeBaseDataClient = jest.fn().mockResolvedValue({
|
||||
getKnowledgeBaseDocuments: jest.fn().mockResolvedValue([]),
|
||||
indexTemplateAndPattern: {
|
||||
|
|
|
@ -30,7 +30,7 @@ describe('Post Knowledge Base Route', () => {
|
|||
beforeEach(() => {
|
||||
server = serverMock.create();
|
||||
({ context } = requestContextMock.createTools());
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser);
|
||||
context.elasticAssistant.getAIAssistantKnowledgeBaseDataClient = jest.fn().mockResolvedValue({
|
||||
setupKnowledgeBase: jest.fn(),
|
||||
indexTemplateAndPattern: {
|
||||
|
|
|
@ -69,7 +69,7 @@ export const postActionsConnectorExecuteRoute = (
|
|||
let onLlmResponse;
|
||||
|
||||
try {
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
|
|
@ -43,7 +43,7 @@ describe('Perform bulk action route', () => {
|
|||
docs_deleted: [],
|
||||
errors: [],
|
||||
});
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser1);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
|
||||
bulkPromptsRoute(server.router, logger);
|
||||
});
|
||||
|
||||
|
|
|
@ -159,7 +159,7 @@ export const bulkPromptsRoute = (router: ElasticAssistantPluginRouter, logger: L
|
|||
try {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
// Perform license and authenticated user checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
|
|
@ -33,7 +33,7 @@ describe('Find user prompts route', () => {
|
|||
clients.elasticAssistant.getAIAssistantPromptsDataClient.findDocuments.mockResolvedValue(
|
||||
Promise.resolve(getFindPromptsResultWithSingleHit())
|
||||
);
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue({
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce({
|
||||
username: 'my_username',
|
||||
authentication_realm: {
|
||||
type: 'my_realm_type',
|
||||
|
@ -41,7 +41,7 @@ describe('Find user prompts route', () => {
|
|||
},
|
||||
} as AuthenticatedUser);
|
||||
logger = loggingSystemMock.createLogger();
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser1);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
|
||||
findPromptsRoute(server.router, logger);
|
||||
});
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ export const findPromptsRoute = (router: ElasticAssistantPluginRouter, logger: L
|
|||
const { query } = request;
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
// Perform license and authenticated user checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
|
|
@ -38,6 +38,9 @@ import {
|
|||
getDefendInsightsRoute,
|
||||
postDefendInsightsRoute,
|
||||
} from './defend_insights';
|
||||
import { deleteKnowledgeBaseEntryRoute } from './knowledge_base/entries/delete_route';
|
||||
import { updateKnowledgeBaseEntryRoute } from './knowledge_base/entries/update_route';
|
||||
import { getKnowledgeBaseEntryRoute } from './knowledge_base/entries/get_route';
|
||||
|
||||
export const registerRoutes = (
|
||||
router: ElasticAssistantPluginRouter,
|
||||
|
@ -71,8 +74,11 @@ export const registerRoutes = (
|
|||
postKnowledgeBaseRoute(router);
|
||||
|
||||
// Knowledge Base Entries
|
||||
getKnowledgeBaseEntryRoute(router);
|
||||
findKnowledgeBaseEntriesRoute(router);
|
||||
createKnowledgeBaseEntryRoute(router);
|
||||
updateKnowledgeBaseEntryRoute(router);
|
||||
deleteKnowledgeBaseEntryRoute(router);
|
||||
bulkActionKnowledgeBaseEntriesRoute(router);
|
||||
|
||||
// Actions Connector Execute (LLM Wrapper)
|
||||
|
|
|
@ -56,7 +56,26 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
const getSpaceId = (): string =>
|
||||
startPlugins.spaces?.spacesService?.getSpaceId(request) || DEFAULT_NAMESPACE_STRING;
|
||||
|
||||
const getCurrentUser = () => coreContext.security.authc.getCurrentUser();
|
||||
const getCurrentUser = async () => {
|
||||
let contextUser = coreContext.security.authc.getCurrentUser();
|
||||
|
||||
if (contextUser && !contextUser?.profile_uid) {
|
||||
try {
|
||||
const users = await coreContext.elasticsearch.client.asCurrentUser.security.getUser({
|
||||
username: contextUser.username,
|
||||
with_profile_uid: true,
|
||||
});
|
||||
|
||||
if (users[contextUser.username].profile_uid) {
|
||||
contextUser = { ...contextUser, profile_uid: users[contextUser.username].profile_uid };
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`Failed to get user profile_uid: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
return contextUser;
|
||||
};
|
||||
|
||||
return {
|
||||
core: coreContext,
|
||||
|
@ -86,7 +105,7 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
// Note: modelIdOverride is used here to enable setting up the KB using a different ELSER model, which
|
||||
// is necessary for testing purposes (`pt_tiny_elser`).
|
||||
getAIAssistantKnowledgeBaseDataClient: memoize(async (params) => {
|
||||
const currentUser = getCurrentUser();
|
||||
const currentUser = await getCurrentUser();
|
||||
|
||||
const { securitySolutionAssistant } = await coreStart.capabilities.resolveCapabilities(
|
||||
request,
|
||||
|
@ -105,8 +124,8 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
});
|
||||
}),
|
||||
|
||||
getAttackDiscoveryDataClient: memoize(() => {
|
||||
const currentUser = getCurrentUser();
|
||||
getAttackDiscoveryDataClient: memoize(async () => {
|
||||
const currentUser = await getCurrentUser();
|
||||
return this.assistantService.createAttackDiscoveryDataClient({
|
||||
spaceId: getSpaceId(),
|
||||
licensing: context.licensing,
|
||||
|
@ -115,8 +134,8 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
});
|
||||
}),
|
||||
|
||||
getDefendInsightsDataClient: memoize(() => {
|
||||
const currentUser = getCurrentUser();
|
||||
getDefendInsightsDataClient: memoize(async () => {
|
||||
const currentUser = await getCurrentUser();
|
||||
return this.assistantService.createDefendInsightsDataClient({
|
||||
spaceId: getSpaceId(),
|
||||
licensing: context.licensing,
|
||||
|
@ -125,8 +144,8 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
});
|
||||
}),
|
||||
|
||||
getAIAssistantPromptsDataClient: memoize(() => {
|
||||
const currentUser = getCurrentUser();
|
||||
getAIAssistantPromptsDataClient: memoize(async () => {
|
||||
const currentUser = await getCurrentUser();
|
||||
return this.assistantService.createAIAssistantPromptsDataClient({
|
||||
spaceId: getSpaceId(),
|
||||
licensing: context.licensing,
|
||||
|
@ -135,8 +154,8 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
});
|
||||
}),
|
||||
|
||||
getAIAssistantAnonymizationFieldsDataClient: memoize(() => {
|
||||
const currentUser = getCurrentUser();
|
||||
getAIAssistantAnonymizationFieldsDataClient: memoize(async () => {
|
||||
const currentUser = await getCurrentUser();
|
||||
return this.assistantService.createAIAssistantAnonymizationFieldsDataClient({
|
||||
spaceId: getSpaceId(),
|
||||
licensing: context.licensing,
|
||||
|
@ -146,7 +165,7 @@ export class RequestContextFactory implements IRequestContextFactory {
|
|||
}),
|
||||
|
||||
getAIAssistantConversationsDataClient: memoize(async (params) => {
|
||||
const currentUser = getCurrentUser();
|
||||
const currentUser = await getCurrentUser();
|
||||
return this.assistantService.createAIAssistantConversationsDataClient({
|
||||
spaceId: getSpaceId(),
|
||||
licensing: context.licensing,
|
||||
|
|
|
@ -32,7 +32,7 @@ describe('Append conversation messages route', () => {
|
|||
clients.elasticAssistant.getAIAssistantConversationsDataClient.appendConversationMessages.mockResolvedValue(
|
||||
getConversationMock(getQueryConversationParams())
|
||||
); // successful append
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser1);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
|
||||
|
||||
appendConversationMessageRoute(server.router);
|
||||
});
|
||||
|
|
|
@ -45,7 +45,7 @@ export const appendConversationMessageRoute = (router: ElasticAssistantPluginRou
|
|||
const { id } = request.params;
|
||||
try {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
|
|
@ -47,7 +47,7 @@ describe('Perform bulk action route', () => {
|
|||
docs_deleted: [],
|
||||
errors: [],
|
||||
});
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser1);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
|
||||
bulkActionConversationsRoute(server.router, logger);
|
||||
});
|
||||
|
||||
|
|
|
@ -158,7 +158,7 @@ export const bulkActionConversationsRoute = (
|
|||
request.events.completed$.subscribe(() => abortController.abort());
|
||||
try {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
|
|
@ -38,7 +38,7 @@ describe('Create conversation route', () => {
|
|||
context.core.elasticsearch.client.asCurrentUser.search.mockResolvedValue(
|
||||
elasticsearchClientMock.createSuccessTransportRequestPromise(getBasicEmptySearchResponse())
|
||||
);
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser1);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
|
||||
createConversationRoute(server.router);
|
||||
});
|
||||
|
||||
|
@ -52,7 +52,7 @@ describe('Create conversation route', () => {
|
|||
});
|
||||
|
||||
test('returns 401 Unauthorized when request context getCurrentUser is not defined', async () => {
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValueOnce(null);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce(null);
|
||||
const response = await server.inject(
|
||||
getCreateConversationRequest(),
|
||||
requestContextMock.convertContext(context)
|
||||
|
|
|
@ -44,7 +44,7 @@ export const createConversationRoute = (router: ElasticAssistantPluginRouter): v
|
|||
try {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
// Perform license and authenticated user checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
|
|
@ -28,7 +28,7 @@ describe('Delete conversation route', () => {
|
|||
clients.elasticAssistant.getAIAssistantConversationsDataClient.getConversation.mockResolvedValue(
|
||||
getConversationMock(getQueryConversationParams())
|
||||
);
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser1);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
|
||||
deleteConversationRoute(server.router);
|
||||
});
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ export const deleteConversationRoute = (router: ElasticAssistantPluginRouter) =>
|
|||
const { id } = request.params;
|
||||
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
|
|
@ -22,7 +22,7 @@ describe('Find user conversations route', () => {
|
|||
clients.elasticAssistant.getAIAssistantConversationsDataClient.findDocuments.mockResolvedValue(
|
||||
Promise.resolve(getFindConversationsResultWithSingleHit())
|
||||
);
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue({
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce({
|
||||
username: 'my_username',
|
||||
authentication_realm: {
|
||||
type: 'my_realm_type',
|
||||
|
|
|
@ -49,7 +49,7 @@ export const findUserConversationsRoute = (router: ElasticAssistantPluginRouter)
|
|||
const { query } = request;
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
// Perform license and authenticated user checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
@ -59,7 +59,7 @@ export const findUserConversationsRoute = (router: ElasticAssistantPluginRouter)
|
|||
}
|
||||
|
||||
const dataClient = await ctx.elasticAssistant.getAIAssistantConversationsDataClient();
|
||||
const currentUser = checkResponse.currentUser;
|
||||
const currentUser = await checkResponse.currentUser;
|
||||
|
||||
const additionalFilter = query.filter ? ` AND ${query.filter}` : '';
|
||||
const userFilter = currentUser?.username
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('Read conversation route', () => {
|
|||
clients.elasticAssistant.getAIAssistantConversationsDataClient.getConversation.mockResolvedValue(
|
||||
getConversationMock(getQueryConversationParams())
|
||||
);
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser1);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
|
||||
readConversationRoute(server.router);
|
||||
});
|
||||
|
||||
|
@ -68,7 +68,7 @@ describe('Read conversation route', () => {
|
|||
});
|
||||
|
||||
test('returns 401 Unauthorized when request context getCurrentUser is not defined', async () => {
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValueOnce(null);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValueOnce(null);
|
||||
const response = await server.inject(
|
||||
getConversationReadRequest(),
|
||||
requestContextMock.convertContext(context)
|
||||
|
|
|
@ -45,7 +45,7 @@ export const readConversationRoute = (router: ElasticAssistantPluginRouter) => {
|
|||
|
||||
try {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('Update conversation route', () => {
|
|||
getConversationMock(getQueryConversationParams())
|
||||
); // successful update
|
||||
|
||||
context.elasticAssistant.getCurrentUser.mockReturnValue(mockUser1);
|
||||
context.elasticAssistant.getCurrentUser.mockResolvedValue(mockUser1);
|
||||
updateConversationRoute(server.router);
|
||||
});
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ export const updateConversationRoute = (router: ElasticAssistantPluginRouter) =>
|
|||
try {
|
||||
const ctx = await context.resolve(['core', 'elasticAssistant', 'licensing']);
|
||||
// Perform license and authenticated user checks
|
||||
const checkResponse = performChecks({
|
||||
const checkResponse = await performChecks({
|
||||
context: ctx,
|
||||
request,
|
||||
response,
|
||||
|
|
|
@ -134,7 +134,7 @@ export interface ElasticAssistantApiRequestHandlerContext {
|
|||
logger: Logger;
|
||||
getServerBasePath: () => string;
|
||||
getSpaceId: () => string;
|
||||
getCurrentUser: () => AuthenticatedUser | null;
|
||||
getCurrentUser: () => Promise<AuthenticatedUser | null>;
|
||||
getAIAssistantConversationsDataClient: (
|
||||
params?: GetAIAssistantConversationsDataClientParams
|
||||
) => Promise<AIAssistantConversationsDataClient | null>;
|
||||
|
|
|
@ -24,6 +24,9 @@ import {
|
|||
bulkActionKnowledgeBaseEntries,
|
||||
bulkActionKnowledgeBaseEntriesForUser,
|
||||
} from '../utils/bulk_actions_entry';
|
||||
import { getEntry } from '../utils/get_entry';
|
||||
import { deleteEntry } from '../utils/delete_entry';
|
||||
import { updateEntry } from '../utils/update_entry';
|
||||
|
||||
export default ({ getService }: FtrProviderContext) => {
|
||||
const supertest = getService('supertest');
|
||||
|
@ -194,6 +197,333 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Get Entry', () => {
|
||||
it('should see other users global entries', async () => {
|
||||
const users = [secOnlySpacesAll];
|
||||
|
||||
const createdEntries = await Promise.all(
|
||||
users.map((user) =>
|
||||
createEntryForUser({
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: globalDocumentEntry,
|
||||
user,
|
||||
})
|
||||
)
|
||||
);
|
||||
const entry = await getEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
params: { id: createdEntries[0].id },
|
||||
log,
|
||||
});
|
||||
|
||||
expect(removeServerGeneratedProperties(entry)).toEqual(globalDocumentEntry);
|
||||
});
|
||||
|
||||
it('should not see other users private entries', async () => {
|
||||
const users = [secOnlySpacesAll];
|
||||
|
||||
const createdEntries = await Promise.all(
|
||||
users.map((user) =>
|
||||
createEntryForUser({
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: documentEntry,
|
||||
user,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
let entry;
|
||||
|
||||
try {
|
||||
entry = await getEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
params: { id: createdEntries[0].id },
|
||||
log,
|
||||
});
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (e) {}
|
||||
|
||||
expect(entry).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Update Entries', () => {
|
||||
it('should update own document entry', async () => {
|
||||
const entry = await createEntry({ supertest, log, entry: documentEntry });
|
||||
const updatedDocumentEntry = {
|
||||
id: entry.id,
|
||||
...documentEntry,
|
||||
text: 'This is a sample of updated document entry',
|
||||
};
|
||||
const response = await updateEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: updatedDocumentEntry,
|
||||
});
|
||||
|
||||
const expectedDocumentEntry = {
|
||||
...documentEntry,
|
||||
users: [{ name: 'elastic' }],
|
||||
text: 'This is a sample of updated document entry',
|
||||
};
|
||||
|
||||
expect(response).toMatchObject(expectedDocumentEntry);
|
||||
});
|
||||
|
||||
it('should not update private document entry created by another user', async () => {
|
||||
const entry = await createEntryForUser({
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: documentEntry,
|
||||
user: secOnlySpacesAll,
|
||||
});
|
||||
|
||||
const updatedDocumentEntry = {
|
||||
id: entry.id,
|
||||
...documentEntry,
|
||||
text: 'This is a sample of updated document entry',
|
||||
};
|
||||
const response = await updateEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: updatedDocumentEntry,
|
||||
expectedHttpCode: 500,
|
||||
});
|
||||
expect(response).toEqual({
|
||||
status_code: 500,
|
||||
message: `Could not find documents to update: ${entry.id}.`,
|
||||
});
|
||||
});
|
||||
|
||||
it('should update own global document entry', async () => {
|
||||
const entry = await createEntry({ supertest, log, entry: globalDocumentEntry });
|
||||
const updatedDocumentEntry = {
|
||||
id: entry.id,
|
||||
...globalDocumentEntry,
|
||||
text: 'This is a sample of updated global document entry',
|
||||
};
|
||||
const response = await updateEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: updatedDocumentEntry,
|
||||
});
|
||||
|
||||
const expectedDocumentEntry = {
|
||||
...globalDocumentEntry,
|
||||
text: 'This is a sample of updated global document entry',
|
||||
};
|
||||
|
||||
expect(response).toMatchObject(expectedDocumentEntry);
|
||||
});
|
||||
|
||||
it('should update own global document entry and make it private', async () => {
|
||||
const entry = await createEntry({ supertest, log, entry: globalDocumentEntry });
|
||||
const updatedDocumentEntry = {
|
||||
id: entry.id,
|
||||
...globalDocumentEntry,
|
||||
global: false,
|
||||
text: 'This is a sample of updated global document entry',
|
||||
};
|
||||
const response = await updateEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: updatedDocumentEntry,
|
||||
});
|
||||
|
||||
const expectedDocumentEntry = {
|
||||
...globalDocumentEntry,
|
||||
users: [{ name: 'elastic' }],
|
||||
global: false,
|
||||
text: 'This is a sample of updated global document entry',
|
||||
};
|
||||
|
||||
expect(response).toMatchObject(expectedDocumentEntry);
|
||||
});
|
||||
|
||||
it('should update global document entry created by another user', async () => {
|
||||
const entry = await createEntryForUser({
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: globalDocumentEntry,
|
||||
user: secOnlySpacesAll,
|
||||
});
|
||||
const updatedDocumentEntry = {
|
||||
id: entry.id,
|
||||
...globalDocumentEntry,
|
||||
text: 'This is a sample of updated global document entry',
|
||||
};
|
||||
const response = await updateEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: updatedDocumentEntry,
|
||||
});
|
||||
|
||||
const expectedDocumentEntry = {
|
||||
...globalDocumentEntry,
|
||||
text: 'This is a sample of updated global document entry',
|
||||
};
|
||||
|
||||
expect(response).toMatchObject(expectedDocumentEntry);
|
||||
});
|
||||
|
||||
it('should update own private document even if user does not have `manage_global_knowledge_base` privileges', async () => {
|
||||
const entry = await createEntryForUser({
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: documentEntry,
|
||||
user: secOnlySpacesAllAssistantMinimalAll,
|
||||
});
|
||||
|
||||
const updatedDocumentEntry = {
|
||||
id: entry.id,
|
||||
...documentEntry,
|
||||
text: 'This is a sample of updated document entry',
|
||||
};
|
||||
const response = await updateEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: updatedDocumentEntry,
|
||||
user: secOnlySpacesAllAssistantMinimalAll,
|
||||
});
|
||||
|
||||
const expectedDocumentEntry = {
|
||||
...documentEntry,
|
||||
users: [{ name: secOnlySpacesAllAssistantMinimalAll.username }],
|
||||
text: 'This is a sample of updated document entry',
|
||||
};
|
||||
|
||||
expect(response).toMatchObject(expectedDocumentEntry);
|
||||
});
|
||||
|
||||
it('should not update global document if user does not have `manage_global_knowledge_base` privileges', async () => {
|
||||
const entry = await createEntry({ supertest, log, entry: globalDocumentEntry });
|
||||
const updatedDocumentEntry = {
|
||||
id: entry.id,
|
||||
...globalDocumentEntry,
|
||||
text: 'This is a sample of updated global document entry',
|
||||
};
|
||||
const response = await updateEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: updatedDocumentEntry,
|
||||
user: secOnlySpacesAllAssistantMinimalAll,
|
||||
expectedHttpCode: 500,
|
||||
});
|
||||
expect(response).toEqual({
|
||||
status_code: 500,
|
||||
message: 'User lacks privileges to update global knowledge base entries',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delete Entries', () => {
|
||||
it('should delete own document entry', async () => {
|
||||
const entry = await createEntry({ supertest, log, entry: documentEntry });
|
||||
const response = await deleteEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
params: { id: entry.id },
|
||||
});
|
||||
|
||||
expect(response.id).toEqual(entry.id);
|
||||
});
|
||||
|
||||
it('should not delete private document entry created by another user', async () => {
|
||||
const entry = await createEntryForUser({
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: documentEntry,
|
||||
user: secOnlySpacesAll,
|
||||
});
|
||||
const response = await deleteEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
params: { id: entry.id },
|
||||
expectedHttpCode: 500,
|
||||
});
|
||||
expect(response).toEqual({
|
||||
status_code: 500,
|
||||
message: `Could not find documents to delete: ${entry.id}.`,
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete own global document entry', async () => {
|
||||
const entry = await createEntry({ supertest, log, entry: globalDocumentEntry });
|
||||
const response = await deleteEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
params: { id: entry.id },
|
||||
});
|
||||
|
||||
expect(response.id).toEqual(entry.id);
|
||||
});
|
||||
|
||||
it('should delete global document entry created by another user', async () => {
|
||||
const entry = await createEntryForUser({
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: globalDocumentEntry,
|
||||
user: secOnlySpacesAll,
|
||||
});
|
||||
const response = await deleteEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
params: { id: entry.id },
|
||||
});
|
||||
|
||||
expect(response.id).toEqual(entry.id);
|
||||
});
|
||||
|
||||
it('should delete own private document even if user does not have `manage_global_knowledge_base` privileges', async () => {
|
||||
const entry = await createEntryForUser({
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry: documentEntry,
|
||||
user: secOnlySpacesAllAssistantMinimalAll,
|
||||
});
|
||||
const response = await deleteEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
params: { id: entry.id },
|
||||
user: secOnlySpacesAllAssistantMinimalAll,
|
||||
});
|
||||
|
||||
expect(response.id).toEqual(entry.id);
|
||||
});
|
||||
|
||||
it('should not delete global document if user does not have `manage_global_knowledge_base` privileges', async () => {
|
||||
const entry = await createEntry({ supertest, log, entry: globalDocumentEntry });
|
||||
const response = await deleteEntry({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
params: { id: entry.id },
|
||||
user: secOnlySpacesAllAssistantMinimalAll,
|
||||
expectedHttpCode: 500,
|
||||
});
|
||||
expect(response).toEqual({
|
||||
status_code: 500,
|
||||
message: 'User lacks privileges to delete global knowledge base entries',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Bulk Actions', () => {
|
||||
describe('General', () => {
|
||||
it(`should throw an error for more than ${KNOWLEDGE_BASE_ENTRIES_TABLE_MAX_PAGE_SIZE} actions`, async () => {
|
||||
|
|
|
@ -20,12 +20,14 @@ export const documentEntry: DocumentEntryCreateFields = {
|
|||
kbResource: 'user',
|
||||
namespace: 'default',
|
||||
text: 'This is a sample document entry',
|
||||
global: false,
|
||||
users: undefined,
|
||||
};
|
||||
|
||||
export const globalDocumentEntry: DocumentEntryCreateFields = {
|
||||
...documentEntry,
|
||||
name: 'Sample Global Document Entry',
|
||||
global: true,
|
||||
users: [],
|
||||
};
|
||||
|
||||
|
@ -38,4 +40,5 @@ export const indexEntry: IndexEntryCreateFields = {
|
|||
description: 'This is a sample index entry',
|
||||
queryDescription: 'Use sample-field to search in sample-index',
|
||||
users: undefined,
|
||||
global: false,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import type SuperTest from 'supertest';
|
||||
import {
|
||||
API_VERSIONS,
|
||||
ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BY_ID,
|
||||
DeleteKnowledgeBaseEntryRequestParamsInput,
|
||||
DeleteKnowledgeBaseEntryResponse,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import type { User } from './auth/types';
|
||||
|
||||
import { routeWithNamespace } from '../../../../../../common/utils/security_solution';
|
||||
|
||||
/**
|
||||
* Delete Knowledge Base Entry
|
||||
* @param supertest The supertest deps
|
||||
* @param supertestWithoutAuth The supertest deps
|
||||
* @param log The tooling logger
|
||||
* @param params Params for delete API (optional)
|
||||
* @param space The Kibana Space to delete entries in (optional)
|
||||
* @param user The user to perform search on behalf of
|
||||
*/
|
||||
|
||||
export const deleteEntry = async ({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
user,
|
||||
params,
|
||||
space,
|
||||
expectedHttpCode = 200,
|
||||
}: {
|
||||
supertest: SuperTest.Agent;
|
||||
supertestWithoutAuth: SuperTest.Agent;
|
||||
log: ToolingLog;
|
||||
user?: User;
|
||||
params: DeleteKnowledgeBaseEntryRequestParamsInput;
|
||||
space?: string;
|
||||
expectedHttpCode?: number;
|
||||
}): Promise<DeleteKnowledgeBaseEntryResponse> => {
|
||||
const route = routeWithNamespace(
|
||||
ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BY_ID,
|
||||
space
|
||||
).replace('{id}', params.id);
|
||||
let request = (user ? supertestWithoutAuth : supertest)
|
||||
.delete(route)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.public.v1);
|
||||
|
||||
if (user) {
|
||||
request = request.auth(user.username, user.password);
|
||||
}
|
||||
|
||||
const response = await request.send().expect(expectedHttpCode);
|
||||
|
||||
if (response.status !== expectedHttpCode) {
|
||||
throw new Error(
|
||||
`Unexpected non ${expectedHttpCode} ok when attempting to delete entry: ${JSON.stringify(
|
||||
response.status
|
||||
)},${JSON.stringify(response, null, 4)}`
|
||||
);
|
||||
} else {
|
||||
return response.body;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import type SuperTest from 'supertest';
|
||||
import {
|
||||
ReadKnowledgeBaseEntryResponse,
|
||||
API_VERSIONS,
|
||||
ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BY_ID,
|
||||
ReadKnowledgeBaseEntryRequestParamsInput,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import type { User } from './auth/types';
|
||||
|
||||
import { routeWithNamespace } from '../../../../../../common/utils/security_solution';
|
||||
|
||||
/**
|
||||
* Get Knowledge Base Entry
|
||||
* @param supertest The supertest deps
|
||||
* @param supertestWithoutAuth The supertest deps
|
||||
* @param log The tooling logger
|
||||
* @param params Params for find API (optional)
|
||||
* @param space The Kibana Space to find entries in (optional)
|
||||
* @param user The user to perform search on behalf of
|
||||
*/
|
||||
|
||||
export const getEntry = async ({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
user,
|
||||
params,
|
||||
space,
|
||||
}: {
|
||||
supertest: SuperTest.Agent;
|
||||
supertestWithoutAuth: SuperTest.Agent;
|
||||
log: ToolingLog;
|
||||
user?: User;
|
||||
params: ReadKnowledgeBaseEntryRequestParamsInput;
|
||||
space?: string;
|
||||
}): Promise<ReadKnowledgeBaseEntryResponse> => {
|
||||
const route = routeWithNamespace(
|
||||
ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BY_ID,
|
||||
space
|
||||
).replace('{id}', params.id);
|
||||
const request = user ? supertestWithoutAuth.auth(user.username, user.password) : supertest;
|
||||
const response = await request
|
||||
.get(route)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.public.v1)
|
||||
.send();
|
||||
if (response.status !== 200) {
|
||||
throw new Error(
|
||||
`Unexpected non 200 ok when attempting to find entries: ${JSON.stringify(
|
||||
response.status
|
||||
)},${JSON.stringify(response, null, 4)}`
|
||||
);
|
||||
} else {
|
||||
return response.body;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import type SuperTest from 'supertest';
|
||||
import {
|
||||
API_VERSIONS,
|
||||
ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BY_ID,
|
||||
KnowledgeBaseEntryUpdateProps,
|
||||
KnowledgeBaseEntryResponse,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import type { User } from './auth/types';
|
||||
|
||||
import { routeWithNamespace } from '../../../../../../common/utils/security_solution';
|
||||
|
||||
/**
|
||||
* Updates a Knowledge Base Entry
|
||||
* @param supertest The supertest deps
|
||||
* @param supertestWithoutAuth The supertest deps
|
||||
* @param log The tooling logger
|
||||
* @param entry The entry to create
|
||||
* @param space The Kibana Space to create the entry in (optional)
|
||||
* @param expectedHttpCode The expected http status code (optional)
|
||||
*/
|
||||
export const updateEntry = async ({
|
||||
supertest,
|
||||
supertestWithoutAuth,
|
||||
log,
|
||||
entry,
|
||||
space,
|
||||
user,
|
||||
expectedHttpCode = 200,
|
||||
}: {
|
||||
supertest: SuperTest.Agent;
|
||||
supertestWithoutAuth: SuperTest.Agent;
|
||||
log: ToolingLog;
|
||||
entry: KnowledgeBaseEntryUpdateProps;
|
||||
space?: string;
|
||||
user?: User;
|
||||
expectedHttpCode?: number;
|
||||
}): Promise<KnowledgeBaseEntryResponse> => {
|
||||
const route = routeWithNamespace(
|
||||
ELASTIC_AI_ASSISTANT_KNOWLEDGE_BASE_ENTRIES_URL_BY_ID,
|
||||
space
|
||||
).replace('{id}', entry.id);
|
||||
|
||||
let request = (user ? supertestWithoutAuth : supertest)
|
||||
.put(route)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, API_VERSIONS.public.v1);
|
||||
|
||||
if (user) {
|
||||
request = request.auth(user.username, user.password);
|
||||
}
|
||||
|
||||
const response = await request.send(entry).expect(expectedHttpCode);
|
||||
|
||||
return response.body;
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue