From 64c10fa55364659e5377b8b0f2526b1b4d0bf279 Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Mon, 2 Dec 2024 09:27:08 -0500 Subject: [PATCH 01/11] [Obs AI Assistant] Manual migration from access tags to authz for obs-ai-assistant routes --- .../server/routes/chat/route.ts | 24 +++++--- .../server/routes/connectors/route.ts | 6 +- .../server/routes/conversations/route.ts | 36 +++++++---- .../server/routes/functions/route.ts | 18 ++++-- .../server/routes/knowledge_base/route.ts | 60 +++++++++++++------ .../server/routes/types.ts | 1 - 6 files changed, 97 insertions(+), 48 deletions(-) diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts index e80e6fa156b0..5ef72dc8e7b5 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/chat/route.ts @@ -126,8 +126,10 @@ async function initializeChatRequest({ const chatRoute = createObservabilityAIAssistantServerRoute({ endpoint: 'POST /internal/observability_ai_assistant/chat', - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, params: t.type({ body: t.intersection([ @@ -174,8 +176,10 @@ const chatRoute = createObservabilityAIAssistantServerRoute({ const chatRecallRoute = createObservabilityAIAssistantServerRoute({ endpoint: 'POST /internal/observability_ai_assistant/chat/recall', - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, params: t.type({ body: t.type({ @@ -282,8 +286,10 @@ async function chatComplete( const chatCompleteRoute = createObservabilityAIAssistantServerRoute({ endpoint: 'POST /internal/observability_ai_assistant/chat/complete', - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, params: chatCompleteInternalRt, handler: async (resources): Promise => { @@ -293,8 +299,10 @@ const chatCompleteRoute = createObservabilityAIAssistantServerRoute({ const publicChatCompleteRoute = createObservabilityAIAssistantServerRoute({ endpoint: 'POST /api/observability_ai_assistant/chat/complete 2023-10-31', - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, params: chatCompletePublicRt, handler: async (resources): Promise => { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/connectors/route.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/connectors/route.ts index 24d63d3f7fa0..80bc877e6f5f 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/connectors/route.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/connectors/route.ts @@ -10,8 +10,10 @@ import { createObservabilityAIAssistantServerRoute } from '../create_observabili const listConnectorsRoute = createObservabilityAIAssistantServerRoute({ endpoint: 'GET /internal/observability_ai_assistant/connectors', - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise => { const { request, plugins } = resources; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/conversations/route.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/conversations/route.ts index 7e59be004cac..e320376bc735 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/conversations/route.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/conversations/route.ts @@ -17,8 +17,10 @@ const getConversationRoute = createObservabilityAIAssistantServerRoute({ conversationId: t.string, }), }), - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise => { const { service, request, params } = resources; @@ -40,8 +42,10 @@ const findConversationsRoute = createObservabilityAIAssistantServerRoute({ query: t.string, }), }), - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise<{ conversations: Conversation[] }> => { const { service, request, params } = resources; @@ -63,8 +67,10 @@ const createConversationRoute = createObservabilityAIAssistantServerRoute({ conversation: conversationCreateRt, }), }), - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise => { const { service, request, params } = resources; @@ -89,8 +95,10 @@ const updateConversationRoute = createObservabilityAIAssistantServerRoute({ conversation: conversationUpdateRt, }), }), - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise => { const { service, request, params } = resources; @@ -115,8 +123,10 @@ const updateConversationTitle = createObservabilityAIAssistantServerRoute({ title: t.string, }), }), - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise => { const { service, request, params } = resources; @@ -143,8 +153,10 @@ const deleteConversationRoute = createObservabilityAIAssistantServerRoute({ conversationId: t.string, }), }), - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise => { const { service, request, params } = resources; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/functions/route.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/functions/route.ts index 1571487765c0..c5f571769dfb 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/functions/route.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/functions/route.ts @@ -22,8 +22,10 @@ const getFunctionsRoute = createObservabilityAIAssistantServerRoute({ scopes: t.union([t.array(assistantScopeType), assistantScopeType]), }), }), - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async ( resources @@ -97,8 +99,10 @@ const functionRecallRoute = createObservabilityAIAssistantServerRoute({ }), ]), }), - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async ( resources @@ -132,8 +136,10 @@ const functionSummariseRoute = createObservabilityAIAssistantServerRoute({ labels: t.record(t.string, t.string), }), }), - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise => { const client = await resources.service.getClient({ request: resources.request }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/knowledge_base/route.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/knowledge_base/route.ts index 37e9248a0c62..4ff94393bc52 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/knowledge_base/route.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/knowledge_base/route.ts @@ -20,8 +20,10 @@ import { Instruction, KnowledgeBaseEntry, KnowledgeBaseEntryRole } from '../../. const getKnowledgeBaseStatus = createObservabilityAIAssistantServerRoute({ endpoint: 'GET /internal/observability_ai_assistant/kb/status', - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async ({ service, @@ -54,11 +56,15 @@ const setupKnowledgeBase = createObservabilityAIAssistantServerRoute({ }), }), options: { - tags: ['access:ai_assistant'], timeout: { idleSocket: moment.duration(20, 'minutes').asMilliseconds(), }, }, + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, + }, handler: async (resources): Promise => { const client = await resources.service.getClient({ request: resources.request }); @@ -74,8 +80,10 @@ const setupKnowledgeBase = createObservabilityAIAssistantServerRoute({ const resetKnowledgeBase = createObservabilityAIAssistantServerRoute({ endpoint: 'POST /internal/observability_ai_assistant/kb/reset', - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise<{ result: string }> => { const client = await resources.service.getClient({ request: resources.request }); @@ -92,8 +100,10 @@ const resetKnowledgeBase = createObservabilityAIAssistantServerRoute({ const semanticTextMigrationKnowledgeBase = createObservabilityAIAssistantServerRoute({ endpoint: 'POST /internal/observability_ai_assistant/kb/semantic_text_migration', - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise => { const client = await resources.service.getClient({ request: resources.request }); @@ -108,8 +118,10 @@ const semanticTextMigrationKnowledgeBase = createObservabilityAIAssistantServerR const getKnowledgeBaseUserInstructions = createObservabilityAIAssistantServerRoute({ endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async ( resources @@ -137,8 +149,10 @@ const saveKnowledgeBaseUserInstruction = createObservabilityAIAssistantServerRou public: toBooleanRt, }), }), - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise => { const client = await resources.service.getClient({ request: resources.request }); @@ -156,8 +170,10 @@ const saveKnowledgeBaseUserInstruction = createObservabilityAIAssistantServerRou const getKnowledgeBaseEntries = createObservabilityAIAssistantServerRoute({ endpoint: 'GET /internal/observability_ai_assistant/kb/entries', - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, params: t.type({ query: t.type({ @@ -207,8 +223,10 @@ const saveKnowledgeBaseEntry = createObservabilityAIAssistantServerRoute({ params: t.type({ body: knowledgeBaseEntryRt, }), - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise => { const client = await resources.service.getClient({ request: resources.request }); @@ -238,8 +256,10 @@ const deleteKnowledgeBaseEntry = createObservabilityAIAssistantServerRoute({ entryId: t.string, }), }), - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise => { const client = await resources.service.getClient({ request: resources.request }); @@ -259,8 +279,10 @@ const importKnowledgeBaseEntries = createObservabilityAIAssistantServerRoute({ entries: t.array(knowledgeBaseEntryRt), }), }), - options: { - tags: ['access:ai_assistant'], + security: { + authz: { + requiredPrivileges: ['ai_assistant'], + }, }, handler: async (resources): Promise => { const client = await resources.service.getClient({ request: resources.request }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/types.ts index 645da146dfb8..38a8f0632920 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/types.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/server/routes/types.ts @@ -85,5 +85,4 @@ export interface ObservabilityAIAssistantRouteCreateOptions { payload?: number; idleSocket?: number; }; - tags: Array<'access:ai_assistant'>; } From e21599562074312a2c3faaf925c47f7673dc1149 Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Tue, 3 Dec 2024 14:37:52 -0500 Subject: [PATCH 02/11] [Obs AI Assistant] Add API tests to ensure correct privileges are respected --- .../common/resolve_endpoint.ts | 14 ++++ .../common/users/delete_user.ts | 16 ++++ .../common/users/roles.ts | 21 ++++- .../common/users/users.ts | 29 ++++++- .../tests/chat/chat.spec.ts | 41 +++++++++ .../tests/complete/complete.spec.ts | 43 +++++++++- .../tests/connectors/connectors.spec.ts | 35 +++++++- .../tests/conversations/conversations.spec.ts | 83 +++++++++++++++++++ .../tests/index.ts | 6 ++ .../knowledge_base/knowledge_base.spec.ts | 83 +++++++++++++++++++ .../knowledge_base_setup.spec.ts | 28 ++++++- .../knowledge_base_status.spec.ts | 27 +++++- .../knowledge_base_user_instructions.spec.ts | 63 +++++++++++++- .../trial/tests/obs_alert_details_context.ts | 2 +- .../ilm_migration_apis.ts | 2 +- 15 files changed, 478 insertions(+), 15 deletions(-) create mode 100644 x-pack/test/observability_ai_assistant_api_integration/common/resolve_endpoint.ts create mode 100644 x-pack/test/observability_ai_assistant_api_integration/common/users/delete_user.ts diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/resolve_endpoint.ts b/x-pack/test/observability_ai_assistant_api_integration/common/resolve_endpoint.ts new file mode 100644 index 000000000000..c880b1a9b609 --- /dev/null +++ b/x-pack/test/observability_ai_assistant_api_integration/common/resolve_endpoint.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function resolveEndpoint(endpoint: string, pathParams: Record): string { + return Object.keys(pathParams).reduce( + (resolvedEndpoint, param) => + resolvedEndpoint.replace(`{${param}}`, encodeURIComponent(pathParams[param])), + endpoint + ); +} diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/delete_user.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/delete_user.ts new file mode 100644 index 000000000000..3908a2502a93 --- /dev/null +++ b/x-pack/test/observability_ai_assistant_api_integration/common/users/delete_user.ts @@ -0,0 +1,16 @@ +/* + * 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 { InheritedFtrProviderContext } from '../ftr_provider_context'; +import { UNAUTHORIZED_USERNAME } from './users'; + +export async function deleteUnauthorizedUser( + getService: InheritedFtrProviderContext['getService'] +) { + const security = getService('security'); + await security.user.delete(UNAUTHORIZED_USERNAME); +} diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts index ec5c9daac3ea..2b9a11e6f9a0 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts @@ -5,6 +5,8 @@ * 2.0. */ +export const AI_ASSISTANT_ROLE_NAME = 'ai_assistant_role'; + // Example role: // export const allAccessRole: Role = { // name: 'all_access', @@ -49,4 +51,21 @@ export interface Role { }; } -export const allRoles = []; +const kibanaPrivileges = [ + { + feature: { + observabilityAIAssistant: ['all'], + actions: ['read'], + }, + spaces: ['*'], + }, +]; + +export const aiAssistantRole: Role = { + name: AI_ASSISTANT_ROLE_NAME, + privileges: { + kibana: kibanaPrivileges, + }, +}; + +export const allRoles = [aiAssistantRole]; diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts index 898954a9bfb9..e98c090ffaae 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts @@ -6,10 +6,23 @@ */ import { kbnTestConfig } from '@kbn/test'; +import { AI_ASSISTANT_ROLE_NAME } from './roles'; + const password = kbnTestConfig.getUrlParts().password!; +export const UNAUTHORIZED_USERNAME = 'unauthorized_user'; +export const UNAUTHORIZED_USER_PASSWORD = 'unauthorized_password'; +export const AI_ASSISTANT_USER_NAME = 'ai_assistant_user'; +export const AI_ASSISTANT_USER_PASSWORD = `${AI_ASSISTANT_USER_NAME}-password`; + export interface User { - username: 'elastic' | 'editor' | 'viewer' | 'secondary_editor'; + username: + | 'elastic' + | 'editor' + | 'viewer' + | 'secondary_editor' + | 'unauthorized_user' + | 'ai_assistant_user'; password: string; roles: string[]; } @@ -32,4 +45,16 @@ export const viewer: User = { roles: ['viewer'], }; -export const allUsers = [editor, secondaryEditor, viewer]; +export const unauthorizedUser: User = { + username: UNAUTHORIZED_USERNAME, + password: UNAUTHORIZED_USER_PASSWORD, + roles: [], +}; + +export const aiAssistantUser: User = { + username: AI_ASSISTANT_USER_NAME, + password: AI_ASSISTANT_USER_PASSWORD, + roles: [AI_ASSISTANT_ROLE_NAME], +}; + +export const allUsers = [editor, secondaryEditor, viewer, unauthorizedUser, aiAssistantUser]; diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts index d514d6ddb702..c7d61a727770 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts @@ -11,9 +11,11 @@ import { PassThrough } from 'stream'; import { createLlmProxy, LlmProxy } from '../../common/create_llm_proxy'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; +import { aiAssistantUser, unauthorizedUser } from '../../common/users/users'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); const CHAT_API_URL = `/internal/observability_ai_assistant/chat`; @@ -183,5 +185,44 @@ export default function ApiTest({ getService }: FtrProviderContext) { `Token limit reached. Token limit is 8192, but the current conversation has 11036 tokens.` ); }); + + describe('security roles and access privileges', () => { + it('should deny access for users without the ai_assistant privilege', async () => { + await supertestWithoutAuth + .post(CHAT_API_URL) + .auth(unauthorizedUser.username, unauthorizedUser.password) + .send({ + name: 'my_api_call', + messages, + connectorId, + functions: [], + scopes: ['all'], + }) + .set('kbn-xsrf', 'true') + .expect(403) + .then(({ body }: any) => { + expect(body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: `API [POST ${CHAT_API_URL}] is unauthorized for user, this action is granted by the Kibana privileges [ai_assistant]`, + }); + }); + }); + + it('should allow access for users with the ai_assistant privilege', async () => { + await supertest + .post(CHAT_API_URL) + .auth(aiAssistantUser.username, aiAssistantUser.password) + .set('kbn-xsrf', 'true') + .send({ + name: 'my_api_call', + messages, + connectorId, + functions: [], + scopes: ['all'], + }) + .expect(200); + }); + }); }); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts index 2eb7c6f986cf..da39146ef646 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts @@ -32,14 +32,16 @@ import { getConversationUpdatedEvent, } from '../conversations/helpers'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; +import { unauthorizedUser, editor } from '../../common/users/users'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); - const COMPLETE_API_URL = `/internal/observability_ai_assistant/chat/complete`; + const COMPLETE_API_URL = '/internal/observability_ai_assistant/chat/complete'; const messages: Message[] = [ { @@ -486,5 +488,44 @@ export default function ApiTest({ getService }: FtrProviderContext) { // todo it.skip('executes a function', async () => {}); + + describe('security roles and access privileges', () => { + it('should deny access for users without the ai_assistant privilege', async () => { + await supertestWithoutAuth + .post(COMPLETE_API_URL) + .auth(unauthorizedUser.username, unauthorizedUser.password) + .send({ + messages, + connectorId, + persist: false, + screenContexts: [], + scopes: ['all'], + }) + .set('kbn-xsrf', 'true') + .expect(403) + .then(({ body }: any) => { + expect(body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: `API [POST ${COMPLETE_API_URL}] is unauthorized for user, this action is granted by the Kibana privileges [ai_assistant]`, + }); + }); + }); + + it('should allow access for users with the ai_assistant privilege', async () => { + await supertest + .post(COMPLETE_API_URL) + .auth(editor.username, editor.password) + .set('kbn-xsrf', 'true') + .send({ + messages, + connectorId, + persist: false, + screenContexts: [], + scopes: ['all'], + }) + .expect(200); + }); + }); }); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts index 41700b21555f..edccd3f6fa92 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts @@ -9,12 +9,16 @@ import expect from '@kbn/expect'; import type { Agent as SuperTestAgent } from 'supertest'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; +import { unauthorizedUser, editor } from '../../common/users/users'; export default function ApiTest({ getService }: FtrProviderContext) { const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); + const CONNECTOR_API_URL = '/internal/observability_ai_assistant/connectors'; + describe('List connectors', () => { before(async () => { await deleteAllActionConnectors(supertest); @@ -27,14 +31,14 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('Returns a 2xx for enterprise license', async () => { await observabilityAIAssistantAPIClient .editor({ - endpoint: 'GET /internal/observability_ai_assistant/connectors', + endpoint: `GET ${CONNECTOR_API_URL}`, }) .expect(200); }); it('returns an empty list of connectors', async () => { const res = await observabilityAIAssistantAPIClient.editor({ - endpoint: 'GET /internal/observability_ai_assistant/connectors', + endpoint: `GET ${CONNECTOR_API_URL}`, }); expect(res.body.length).to.be(0); @@ -44,13 +48,38 @@ export default function ApiTest({ getService }: FtrProviderContext) { const connectorId = await createProxyActionConnector({ supertest, log, port: 1234 }); const res = await observabilityAIAssistantAPIClient.editor({ - endpoint: 'GET /internal/observability_ai_assistant/connectors', + endpoint: `GET ${CONNECTOR_API_URL}`, }); expect(res.body.length).to.be(1); await deleteActionConnector({ supertest, connectorId, log }); }); + + describe('security roles and access privileges', () => { + it('should deny access for users without the ai_assistant privilege', async () => { + await supertestWithoutAuth + .get(CONNECTOR_API_URL) + .auth(unauthorizedUser.username, unauthorizedUser.password) + .set('kbn-xsrf', 'true') + .expect(403) + .then(({ body }: any) => { + expect(body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: `API [GET ${CONNECTOR_API_URL}] is unauthorized for user, this action is granted by the Kibana privileges [ai_assistant]`, + }); + }); + }); + + it('should allow access for users with the ai_assistant privilege', async () => { + await supertest + .get(CONNECTOR_API_URL) + .auth(editor.username, editor.password) + .set('kbn-xsrf', 'true') + .expect(200); + }); + }); }); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts index 71eb37d35769..23eb71fb106f 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts @@ -14,8 +14,11 @@ import { } from '@kbn/observability-ai-assistant-plugin/common/types'; import type { FtrProviderContext } from '../../common/ftr_provider_context'; import type { SupertestReturnType } from '../../common/observability_ai_assistant_api_client'; +import { unauthorizedUser } from '../../common/users/users'; +import { resolveEndpoint } from '../../common/resolve_endpoint'; export default function ApiTest({ getService }: FtrProviderContext) { + const supertestWithoutAuth = getService('supertestWithoutAuth'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); const conversationCreate: ConversationCreateRequest = { @@ -250,5 +253,85 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); }); + + describe('security roles and access privileges', () => { + let createResponse: Awaited< + SupertestReturnType<'POST /internal/observability_ai_assistant/conversation'> + >; + + before(async () => { + createResponse = await observabilityAIAssistantAPIClient + .editor({ + endpoint: 'POST /internal/observability_ai_assistant/conversation', + params: { + body: { + conversation: conversationCreate, + }, + }, + }) + .expect(200); + }); + + const routes: Array<{ + endpoint: string; + method: 'get' | 'post' | 'put' | 'delete'; + payload?: Record; + requiredPrivileges: string[]; + }> = [ + { + endpoint: '/internal/observability_ai_assistant/conversations', + method: 'post', + requiredPrivileges: ['ai_assistant'], + }, + { + endpoint: '/internal/observability_ai_assistant/conversation/{conversationId}', + method: 'put', + payload: { + path: { conversationId: 'test-id' }, + body: { conversation: conversationUpdate }, + }, + requiredPrivileges: ['ai_assistant'], + }, + { + endpoint: '/internal/observability_ai_assistant/conversation/{conversationId}', + method: 'get', + payload: { + path: { conversationId: 'test-id' }, + }, + requiredPrivileges: ['ai_assistant'], + }, + { + endpoint: '/internal/observability_ai_assistant/conversation/{conversationId}', + method: 'delete', + payload: { + path: { conversationId: 'test-id' }, + }, + requiredPrivileges: ['ai_assistant'], + }, + ]; + + routes.forEach((route) => { + const { endpoint, method, payload } = route; + + describe(`${method.toUpperCase()} ${endpoint}`, () => { + it('should deny access for unauthorized users', async () => { + const request = supertestWithoutAuth[method]( + resolveEndpoint( + endpoint, + payload?.path ? { conversationId: createResponse.body.conversation.id } : {} + ) + ) + .auth(unauthorizedUser.username, unauthorizedUser.password) + .set('kbn-xsrf', 'true'); + + if (payload?.body) { + request.send(payload.body); + } + + await request.expect(403); + }); + }); + }); + }); }); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/index.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/index.ts index e0312d2f7601..072c2857b0bc 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/index.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/index.ts @@ -7,6 +7,7 @@ import globby from 'globby'; import path from 'path'; import { createUsersAndRoles } from '../common/users/create_users_and_roles'; +// import { deleteUnauthorizedUser } from '../common/users/delete_user'; import { FtrProviderContext } from '../common/ftr_provider_context'; const cwd = path.join(__dirname); @@ -24,6 +25,11 @@ export default function observabilityAIAssistantApiIntegrationTests({ await createUsersAndRoles(getService); }); + // // Delete unauthorized user after running tests + // after(async () => { + // await deleteUnauthorizedUser(getService); + // }); + tests.forEach((testName) => { describe(testName, () => { loadTestFile(require.resolve(`./${testName}`)); diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts index 8d8c2e241768..652ca201b16e 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts @@ -15,11 +15,14 @@ import { deleteInferenceEndpoint, deleteKnowledgeBaseModel, } from './helpers'; +import { resolveEndpoint } from '../../common/resolve_endpoint'; +import { unauthorizedUser } from '../../common/users/users'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); const es = getService('es'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); describe('Knowledge base', () => { before(async () => { @@ -210,6 +213,86 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(entries[0].title).to.eql('My title b'); }); }); + + describe('security roles and access privileges', () => { + const routes: Array<{ + endpoint: string; + endpointWithParams?: string; + method: 'get' | 'post' | 'put' | 'delete'; + payload?: Record; + requiredPrivileges: string[]; + }> = [ + { + endpoint: '/internal/observability_ai_assistant/kb/entries/save', + method: 'post', + payload: { + body: { + id: 'my-doc-id-1', + title: 'My title', + text: 'My content', + }, + }, + requiredPrivileges: ['ai_assistant'], + }, + { + endpoint: '/internal/observability_ai_assistant/kb/entries', + endpointWithParams: + '/internal/observability_ai_assistant/kb/entries?query=&sortBy=title&sortDirection=asc', + method: 'get', + payload: { + query: { + query: '', + sortBy: 'title', + sortDirection: 'asc', + }, + }, + requiredPrivileges: ['ai_assistant'], + }, + { + endpoint: '/internal/observability_ai_assistant/kb/entries/{entryId}', + endpointWithParams: '/internal/observability_ai_assistant/kb/entries/my-doc-id-1', + method: 'delete', + payload: { + path: { entryId: 'my-doc-id-1' }, + }, + requiredPrivileges: ['ai_assistant'], + }, + ]; + + routes.forEach((route) => { + const { endpoint, endpointWithParams, method, payload, requiredPrivileges } = route; + + describe(`${method.toUpperCase()} ${endpoint}`, () => { + it('should deny access for unauthorized users', async () => { + const request = supertestWithoutAuth[method]( + resolveEndpoint(endpoint, payload?.path || {}) + ) + .auth(unauthorizedUser.username, unauthorizedUser.password) + .set('kbn-xsrf', 'true'); + + if (payload?.body) { + request.send(payload.body); + } + + if (payload?.query) { + request.query(payload.query); + } + + await request.expect(403).then(({ body }) => { + expect(body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: `API [${method.toUpperCase()} ${ + endpointWithParams || endpoint + }] is unauthorized for user, this action is granted by the Kibana privileges [${requiredPrivileges.join( + ', ' + )}]`, + }); + }); + }); + }); + }); + }); }); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts index 7903f4b53966..fe8fc6cc4793 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts @@ -13,18 +13,22 @@ import { TINY_ELSER, deleteInferenceEndpoint, } from './helpers'; +import { unauthorizedUser } from '../../common/users/users'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); const es = getService('es'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); + const KNOWLEDGE_BASE_SETUP_API_URL = '/internal/observability_ai_assistant/kb/setup'; + describe('/internal/observability_ai_assistant/kb/setup', () => { it('returns model info when successful', async () => { await createKnowledgeBaseModel(ml); const res = await observabilityAIAssistantAPIClient .admin({ - endpoint: 'POST /internal/observability_ai_assistant/kb/setup', + endpoint: `POST ${KNOWLEDGE_BASE_SETUP_API_URL}`, params: { query: { model_id: TINY_ELSER.id, @@ -43,7 +47,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns error message if model is not deployed', async () => { const res = await observabilityAIAssistantAPIClient .admin({ - endpoint: 'POST /internal/observability_ai_assistant/kb/setup', + endpoint: `POST ${KNOWLEDGE_BASE_SETUP_API_URL}`, params: { query: { model_id: TINY_ELSER.id, @@ -60,5 +64,25 @@ export default function ApiTest({ getService }: FtrProviderContext) { // @ts-expect-error expect(res.body.statusCode).to.be(500); }); + + describe('security roles and access privileges', () => { + it('should deny access for users without the ai_assistant privilege', async () => { + await supertestWithoutAuth + .post(KNOWLEDGE_BASE_SETUP_API_URL) + .auth(unauthorizedUser.username, unauthorizedUser.password) + .query({ + model_id: TINY_ELSER.id, + }) + .set('kbn-xsrf', 'true') + .expect(403) + .then(({ body }: any) => { + expect(body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: `API [POST ${KNOWLEDGE_BASE_SETUP_API_URL}?model_id=pt_tiny_elser] is unauthorized for user, this action is granted by the Kibana privileges [ai_assistant]`, + }); + }); + }); + }); }); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts index 8c10a6128d30..678a84e77041 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts @@ -13,11 +13,15 @@ import { TINY_ELSER, deleteInferenceEndpoint, } from './helpers'; +import { unauthorizedUser } from '../../common/users/users'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); const es = getService('es'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + const KNOWLEDGE_BASE_STATUS_API_URL = '/internal/observability_ai_assistant/kb/status'; describe('/internal/observability_ai_assistant/kb/status', () => { beforeEach(async () => { @@ -41,7 +45,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns correct status after knowledge base is setup', async () => { const res = await observabilityAIAssistantAPIClient - .editor({ endpoint: 'GET /internal/observability_ai_assistant/kb/status' }) + .editor({ endpoint: `GET ${KNOWLEDGE_BASE_STATUS_API_URL}` }) .expect(200); expect(res.body.ready).to.be(true); @@ -54,7 +58,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const res = await observabilityAIAssistantAPIClient .editor({ - endpoint: 'GET /internal/observability_ai_assistant/kb/status', + endpoint: `GET ${KNOWLEDGE_BASE_STATUS_API_URL}`, }) .expect(200); @@ -70,7 +74,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const res = await observabilityAIAssistantAPIClient .editor({ - endpoint: 'GET /internal/observability_ai_assistant/kb/status', + endpoint: `GET ${KNOWLEDGE_BASE_STATUS_API_URL}`, }) .expect(200); @@ -80,5 +84,22 @@ export default function ApiTest({ getService }: FtrProviderContext) { 'Inference endpoint not found [obs_ai_assistant_kb_inference]' ); }); + + describe('security roles and access privileges', () => { + it('should deny access for users without the ai_assistant privilege', async () => { + await supertestWithoutAuth + .get(KNOWLEDGE_BASE_STATUS_API_URL) + .auth(unauthorizedUser.username, unauthorizedUser.password) + .set('kbn-xsrf', 'true') + .expect(403) + .then(({ body }: any) => { + expect(body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: `API [GET ${KNOWLEDGE_BASE_STATUS_API_URL}] is unauthorized for user, this action is granted by the Kibana privileges [ai_assistant]`, + }); + }); + }); + }); }); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts index cde2c9e4b4a8..63cc285fddf0 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts @@ -22,11 +22,13 @@ import { import { getConversationCreatedEvent } from '../conversations/helpers'; import { LlmProxy, createLlmProxy } from '../../common/create_llm_proxy'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; -import { User } from '../../common/users/users'; +import { User, unauthorizedUser } from '../../common/users/users'; +import { resolveEndpoint } from '../../common/resolve_endpoint'; export default function ApiTest({ getService }: FtrProviderContext) { const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); const es = getService('es'); const ml = getService('ml'); const log = getService('log'); @@ -362,5 +364,64 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(res2).to.be(''); }); }); + + describe('security roles and access privileges', () => { + const routes: Array<{ + endpoint: string; + method: 'get' | 'post' | 'put' | 'delete'; + payload?: Record; + requiredPrivileges: string[]; + }> = [ + { + endpoint: '/internal/observability_ai_assistant/kb/user_instructions', + method: 'put', + payload: { + body: { + id: 'test-instruction', + text: 'Test user instruction', + public: true, + }, + }, + requiredPrivileges: ['ai_assistant'], + }, + { + endpoint: '/internal/observability_ai_assistant/kb/user_instructions', + method: 'get', + requiredPrivileges: ['ai_assistant'], + }, + ]; + + routes.forEach((route) => { + const { endpoint, method, payload, requiredPrivileges } = route; + + describe(`${method.toUpperCase()} ${endpoint}`, () => { + it('should deny access for unauthorized users', async () => { + const request = supertestWithoutAuth[method]( + resolveEndpoint(endpoint, payload?.path || {}) + ) + .auth(unauthorizedUser.username, unauthorizedUser.password) + .set('kbn-xsrf', 'true'); + + if (payload?.body) { + request.send(payload.body); + } + + if (payload?.query) { + request.query(payload.query); + } + + await request.expect(403).then(({ body }) => { + expect(body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: `API [${method.toUpperCase()} ${endpoint}] is unauthorized for user, this action is granted by the Kibana privileges [${requiredPrivileges.join( + ', ' + )}]`, + }); + }); + }); + }); + }); + }); }); } diff --git a/x-pack/test/observability_api_integration/trial/tests/obs_alert_details_context.ts b/x-pack/test/observability_api_integration/trial/tests/obs_alert_details_context.ts index 31f2bb5e3466..17c978c0e08e 100644 --- a/x-pack/test/observability_api_integration/trial/tests/obs_alert_details_context.ts +++ b/x-pack/test/observability_api_integration/trial/tests/obs_alert_details_context.ts @@ -515,7 +515,7 @@ export default function ApiTest({ getService }: ObsFtrProviderContext) { } describe('security roles and access privileges', () => { - it('is not available to unauthorized users', async () => { + it('should deny access for users without the ai_assistant privilege', async () => { const UNAUTHORIZED_USERNAME = 'UNAUTHORIZED_USER'; const UNAUTHORIZED_USER_PASSWORD = 'UNAUTHORIZED_USER_PASSWORD'; diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts b/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts index 56009ccdd8b3..2a99b0a5362a 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts @@ -184,7 +184,7 @@ export default function ({ getService }: FtrProviderContext) { expect(policy).to.eql(customLifecycle.policy); }); - it('is not available to unauthorized users', async () => { + it('should deny access for users without the ai_assistant privilege', async () => { const UNAUTHZD_TEST_USERNAME = 'UNAUTHZD_TEST_USERNAME'; const UNAUTHZD_TEST_USER_PASSWORD = 'UNAUTHZD_TEST_USER_PASSWORD'; From 7bc892b8ff623048a1ef619a692b7991a4c02cd9 Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Tue, 3 Dec 2024 14:43:55 -0500 Subject: [PATCH 03/11] [Obs AI Assistant] Remove unused code --- .../common/users/delete_user.ts | 16 ---------------- .../tests/index.ts | 6 ------ 2 files changed, 22 deletions(-) delete mode 100644 x-pack/test/observability_ai_assistant_api_integration/common/users/delete_user.ts diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/delete_user.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/delete_user.ts deleted file mode 100644 index 3908a2502a93..000000000000 --- a/x-pack/test/observability_ai_assistant_api_integration/common/users/delete_user.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { InheritedFtrProviderContext } from '../ftr_provider_context'; -import { UNAUTHORIZED_USERNAME } from './users'; - -export async function deleteUnauthorizedUser( - getService: InheritedFtrProviderContext['getService'] -) { - const security = getService('security'); - await security.user.delete(UNAUTHORIZED_USERNAME); -} diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/index.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/index.ts index 072c2857b0bc..e0312d2f7601 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/index.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/index.ts @@ -7,7 +7,6 @@ import globby from 'globby'; import path from 'path'; import { createUsersAndRoles } from '../common/users/create_users_and_roles'; -// import { deleteUnauthorizedUser } from '../common/users/delete_user'; import { FtrProviderContext } from '../common/ftr_provider_context'; const cwd = path.join(__dirname); @@ -25,11 +24,6 @@ export default function observabilityAIAssistantApiIntegrationTests({ await createUsersAndRoles(getService); }); - // // Delete unauthorized user after running tests - // after(async () => { - // await deleteUnauthorizedUser(getService); - // }); - tests.forEach((testName) => { describe(testName, () => { loadTestFile(require.resolve(`./${testName}`)); From 10486e81c65a707838b7fd0951f3276f73341b38 Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Tue, 3 Dec 2024 16:16:55 -0500 Subject: [PATCH 04/11] [Obs AI Assistant] Update tests --- .../reporting_and_security/ilm_migration_apis.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts b/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts index 2a99b0a5362a..56009ccdd8b3 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/ilm_migration_apis.ts @@ -184,7 +184,7 @@ export default function ({ getService }: FtrProviderContext) { expect(policy).to.eql(customLifecycle.policy); }); - it('should deny access for users without the ai_assistant privilege', async () => { + it('is not available to unauthorized users', async () => { const UNAUTHZD_TEST_USERNAME = 'UNAUTHZD_TEST_USERNAME'; const UNAUTHZD_TEST_USER_PASSWORD = 'UNAUTHZD_TEST_USER_PASSWORD'; From 0e575e5f134dfebf2e69718f26cafd91929c9e53 Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Tue, 3 Dec 2024 16:17:55 -0500 Subject: [PATCH 05/11] [Obs AI Assistant] Update tests --- .../trial/tests/obs_alert_details_context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/observability_api_integration/trial/tests/obs_alert_details_context.ts b/x-pack/test/observability_api_integration/trial/tests/obs_alert_details_context.ts index 17c978c0e08e..31f2bb5e3466 100644 --- a/x-pack/test/observability_api_integration/trial/tests/obs_alert_details_context.ts +++ b/x-pack/test/observability_api_integration/trial/tests/obs_alert_details_context.ts @@ -515,7 +515,7 @@ export default function ApiTest({ getService }: ObsFtrProviderContext) { } describe('security roles and access privileges', () => { - it('should deny access for users without the ai_assistant privilege', async () => { + it('is not available to unauthorized users', async () => { const UNAUTHORIZED_USERNAME = 'UNAUTHORIZED_USER'; const UNAUTHORIZED_USER_PASSWORD = 'UNAUTHORIZED_USER_PASSWORD'; From 27c538d2c93664e10eefa14b7514516fdfb9165b Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Tue, 3 Dec 2024 17:09:12 -0500 Subject: [PATCH 06/11] [Obs AI Assistant] Fix floating promises --- .../tests/conversations/conversations.spec.ts | 2 +- .../tests/knowledge_base/knowledge_base.spec.ts | 4 ++-- .../knowledge_base/knowledge_base_user_instructions.spec.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts index 23eb71fb106f..d7f9f4fe8b25 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts @@ -325,7 +325,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true'); if (payload?.body) { - request.send(payload.body); + await request.send(payload.body); } await request.expect(403); diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts index 652ca201b16e..86ebe52d3e3e 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts @@ -271,11 +271,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true'); if (payload?.body) { - request.send(payload.body); + await request.send(payload.body); } if (payload?.query) { - request.query(payload.query); + await request.query(payload.query); } await request.expect(403).then(({ body }) => { diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts index 63cc285fddf0..97e34efc84bc 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts @@ -403,11 +403,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'true'); if (payload?.body) { - request.send(payload.body); + await request.send(payload.body); } if (payload?.query) { - request.query(payload.query); + await request.query(payload.query); } await request.expect(403).then(({ body }) => { From f3da2d4c617efe5ee6aeb909385f36159452a48e Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Tue, 3 Dec 2024 17:51:17 -0500 Subject: [PATCH 07/11] [Obs AI Assistant] Update type --- .../common/config.ts | 6 ++++++ .../tests/knowledge_base/knowledge_base.spec.ts | 3 ++- .../knowledge_base/knowledge_base_user_instructions.spec.ts | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts index 427258a6e291..bec4dbe0aaf3 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts @@ -33,6 +33,12 @@ export type ObservabilityAIAssistantAPIClient = Awaited< export type ObservabilityAIAssistantServices = Awaited>['services']; +export interface ApiErrorResponse { + statusCode: number; + error: string; + message: string; +} + export function createObservabilityAIAssistantAPIConfig({ config, license, diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts index 86ebe52d3e3e..87f941dd14aa 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts @@ -17,6 +17,7 @@ import { } from './helpers'; import { resolveEndpoint } from '../../common/resolve_endpoint'; import { unauthorizedUser } from '../../common/users/users'; +import { ApiErrorResponse } from '../../common/config'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); @@ -278,7 +279,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { await request.query(payload.query); } - await request.expect(403).then(({ body }) => { + await request.expect(403).then(({ body }: { body: ApiErrorResponse }) => { expect(body).to.eql({ statusCode: 403, error: 'Forbidden', diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts index 97e34efc84bc..7dc7a7b0b6f1 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts @@ -24,6 +24,7 @@ import { LlmProxy, createLlmProxy } from '../../common/create_llm_proxy'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; import { User, unauthorizedUser } from '../../common/users/users'; import { resolveEndpoint } from '../../common/resolve_endpoint'; +import { ApiErrorResponse } from '../../common/config'; export default function ApiTest({ getService }: FtrProviderContext) { const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); @@ -410,7 +411,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { await request.query(payload.query); } - await request.expect(403).then(({ body }) => { + await request.expect(403).then(({ body }: { body: ApiErrorResponse }) => { expect(body).to.eql({ statusCode: 403, error: 'Forbidden', From ff4908bf6c2c3babc9c2763f18f479b085edc79a Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Tue, 3 Dec 2024 19:36:12 -0500 Subject: [PATCH 08/11] [Obs AI Assistant] Update tests to use Obs AI Assistant API client --- .../common/config.ts | 9 +- .../common/resolve_endpoint.ts | 14 -- .../common/users/roles.ts | 19 +- .../common/users/users.ts | 9 +- .../tests/chat/chat.spec.ts | 51 ++---- .../tests/complete/complete.spec.ts | 50 ++---- .../tests/connectors/connectors.spec.ts | 27 +-- .../tests/conversations/conversations.spec.ts | 167 ++++++++++-------- .../knowledge_base/knowledge_base.spec.ts | 115 ++++-------- .../knowledge_base_setup.spec.ts | 27 ++- .../knowledge_base_status.spec.ts | 19 +- .../knowledge_base_user_instructions.spec.ts | 83 +++------ 12 files changed, 219 insertions(+), 371 deletions(-) delete mode 100644 x-pack/test/observability_ai_assistant_api_integration/common/resolve_endpoint.ts diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts index bec4dbe0aaf3..455004e5339f 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts @@ -11,7 +11,7 @@ import { ObservabilityAIAssistantFtrConfigName } from '../configs'; import { getApmSynthtraceEsClient } from './create_synthtrace_client'; import { InheritedFtrProviderContext, InheritedServices } from './ftr_provider_context'; import { getScopedApiClient } from './observability_ai_assistant_api_client'; -import { editor, secondaryEditor, viewer } from './users/users'; +import { editor, secondaryEditor, unauthorizedUser, viewer } from './users/users'; export interface ObservabilityAIAssistantFtrConfig { name: ObservabilityAIAssistantFtrConfigName; @@ -33,12 +33,6 @@ export type ObservabilityAIAssistantAPIClient = Awaited< export type ObservabilityAIAssistantServices = Awaited>['services']; -export interface ApiErrorResponse { - statusCode: number; - error: string; - message: string; -} - export function createObservabilityAIAssistantAPIConfig({ config, license, @@ -73,6 +67,7 @@ export function createObservabilityAIAssistantAPIConfig({ viewer: getScopedApiClientForUsername(viewer.username), editor: getScopedApiClientForUsername(editor.username), secondaryEditor: getScopedApiClientForUsername(secondaryEditor.username), + unauthorizedUser: getScopedApiClientForUsername(unauthorizedUser.username), }; }, }, diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/resolve_endpoint.ts b/x-pack/test/observability_ai_assistant_api_integration/common/resolve_endpoint.ts deleted file mode 100644 index c880b1a9b609..000000000000 --- a/x-pack/test/observability_ai_assistant_api_integration/common/resolve_endpoint.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export function resolveEndpoint(endpoint: string, pathParams: Record): string { - return Object.keys(pathParams).reduce( - (resolvedEndpoint, param) => - resolvedEndpoint.replace(`{${param}}`, encodeURIComponent(pathParams[param])), - endpoint - ); -} diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts index 2b9a11e6f9a0..3a56fd4662d0 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts @@ -51,21 +51,4 @@ export interface Role { }; } -const kibanaPrivileges = [ - { - feature: { - observabilityAIAssistant: ['all'], - actions: ['read'], - }, - spaces: ['*'], - }, -]; - -export const aiAssistantRole: Role = { - name: AI_ASSISTANT_ROLE_NAME, - privileges: { - kibana: kibanaPrivileges, - }, -}; - -export const allRoles = [aiAssistantRole]; +export const allRoles = []; diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts index e98c090ffaae..df60539d83f1 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts @@ -6,7 +6,6 @@ */ import { kbnTestConfig } from '@kbn/test'; -import { AI_ASSISTANT_ROLE_NAME } from './roles'; const password = kbnTestConfig.getUrlParts().password!; @@ -51,10 +50,4 @@ export const unauthorizedUser: User = { roles: [], }; -export const aiAssistantUser: User = { - username: AI_ASSISTANT_USER_NAME, - password: AI_ASSISTANT_USER_PASSWORD, - roles: [AI_ASSISTANT_ROLE_NAME], -}; - -export const allUsers = [editor, secondaryEditor, viewer, unauthorizedUser, aiAssistantUser]; +export const allUsers = [editor, secondaryEditor, viewer, unauthorizedUser]; diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts index c7d61a727770..c0a3da475e0c 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts @@ -11,12 +11,11 @@ import { PassThrough } from 'stream'; import { createLlmProxy, LlmProxy } from '../../common/create_llm_proxy'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; -import { aiAssistantUser, unauthorizedUser } from '../../common/users/users'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); + const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); const CHAT_API_URL = `/internal/observability_ai_assistant/chat`; @@ -188,40 +187,22 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('security roles and access privileges', () => { it('should deny access for users without the ai_assistant privilege', async () => { - await supertestWithoutAuth - .post(CHAT_API_URL) - .auth(unauthorizedUser.username, unauthorizedUser.password) - .send({ - name: 'my_api_call', - messages, - connectorId, - functions: [], - scopes: ['all'], - }) - .set('kbn-xsrf', 'true') - .expect(403) - .then(({ body }: any) => { - expect(body).to.eql({ - statusCode: 403, - error: 'Forbidden', - message: `API [POST ${CHAT_API_URL}] is unauthorized for user, this action is granted by the Kibana privileges [ai_assistant]`, - }); + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: `POST ${CHAT_API_URL}`, + params: { + body: { + name: 'my_api_call', + messages, + connectorId, + functions: [], + scopes: ['all'], + }, + }, }); - }); - - it('should allow access for users with the ai_assistant privilege', async () => { - await supertest - .post(CHAT_API_URL) - .auth(aiAssistantUser.username, aiAssistantUser.password) - .set('kbn-xsrf', 'true') - .send({ - name: 'my_api_call', - messages, - connectorId, - functions: [], - scopes: ['all'], - }) - .expect(200); + } catch (e) { + expect(e.status).to.be(403); + } }); }); }); diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts index da39146ef646..5e59eed3a8c2 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts @@ -32,11 +32,9 @@ import { getConversationUpdatedEvent, } from '../conversations/helpers'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; -import { unauthorizedUser, editor } from '../../common/users/users'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); @@ -491,40 +489,22 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('security roles and access privileges', () => { it('should deny access for users without the ai_assistant privilege', async () => { - await supertestWithoutAuth - .post(COMPLETE_API_URL) - .auth(unauthorizedUser.username, unauthorizedUser.password) - .send({ - messages, - connectorId, - persist: false, - screenContexts: [], - scopes: ['all'], - }) - .set('kbn-xsrf', 'true') - .expect(403) - .then(({ body }: any) => { - expect(body).to.eql({ - statusCode: 403, - error: 'Forbidden', - message: `API [POST ${COMPLETE_API_URL}] is unauthorized for user, this action is granted by the Kibana privileges [ai_assistant]`, - }); + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: 'POST /internal/observability_ai_assistant/chat/complete', + params: { + body: { + messages, + connectorId, + persist: false, + screenContexts: [], + scopes: ['all'], + }, + }, }); - }); - - it('should allow access for users with the ai_assistant privilege', async () => { - await supertest - .post(COMPLETE_API_URL) - .auth(editor.username, editor.password) - .set('kbn-xsrf', 'true') - .send({ - messages, - connectorId, - persist: false, - screenContexts: [], - scopes: ['all'], - }) - .expect(200); + } catch (e) { + expect(e.status).to.be(403); + } }); }); }); diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts index edccd3f6fa92..84b044d4b84a 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts @@ -9,12 +9,10 @@ import expect from '@kbn/expect'; import type { Agent as SuperTestAgent } from 'supertest'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; -import { unauthorizedUser, editor } from '../../common/users/users'; export default function ApiTest({ getService }: FtrProviderContext) { const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); const supertest = getService('supertest'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); const log = getService('log'); const CONNECTOR_API_URL = '/internal/observability_ai_assistant/connectors'; @@ -58,26 +56,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('security roles and access privileges', () => { it('should deny access for users without the ai_assistant privilege', async () => { - await supertestWithoutAuth - .get(CONNECTOR_API_URL) - .auth(unauthorizedUser.username, unauthorizedUser.password) - .set('kbn-xsrf', 'true') - .expect(403) - .then(({ body }: any) => { - expect(body).to.eql({ - statusCode: 403, - error: 'Forbidden', - message: `API [GET ${CONNECTOR_API_URL}] is unauthorized for user, this action is granted by the Kibana privileges [ai_assistant]`, - }); + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: `GET ${CONNECTOR_API_URL}`, }); - }); - - it('should allow access for users with the ai_assistant privilege', async () => { - await supertest - .get(CONNECTOR_API_URL) - .auth(editor.username, editor.password) - .set('kbn-xsrf', 'true') - .expect(200); + } catch (e) { + expect(e.status).to.be(403); + } }); }); }); diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts index d7f9f4fe8b25..3db3be8ec671 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts @@ -14,11 +14,8 @@ import { } from '@kbn/observability-ai-assistant-plugin/common/types'; import type { FtrProviderContext } from '../../common/ftr_provider_context'; import type { SupertestReturnType } from '../../common/observability_ai_assistant_api_client'; -import { unauthorizedUser } from '../../common/users/users'; -import { resolveEndpoint } from '../../common/resolve_endpoint'; export default function ApiTest({ getService }: FtrProviderContext) { - const supertestWithoutAuth = getService('supertestWithoutAuth'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); const conversationCreate: ConversationCreateRequest = { @@ -255,81 +252,109 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('security roles and access privileges', () => { - let createResponse: Awaited< - SupertestReturnType<'POST /internal/observability_ai_assistant/conversation'> - >; + describe('should deny access for users without the ai_assistant privilege', () => { + let createResponse: Awaited< + SupertestReturnType<'POST /internal/observability_ai_assistant/conversation'> + >; + before(async () => { + createResponse = await observabilityAIAssistantAPIClient + .editor({ + endpoint: 'POST /internal/observability_ai_assistant/conversation', + params: { + body: { + conversation: conversationCreate, + }, + }, + }) + .expect(200); + }); - before(async () => { - createResponse = await observabilityAIAssistantAPIClient - .editor({ - endpoint: 'POST /internal/observability_ai_assistant/conversation', - params: { - body: { - conversation: conversationCreate, + after(async () => { + await observabilityAIAssistantAPIClient + .editor({ + endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}', + params: { + path: { + conversationId: createResponse.body.conversation.id, + }, }, - }, - }) - .expect(200); - }); + }) + .expect(200); + }); - const routes: Array<{ - endpoint: string; - method: 'get' | 'post' | 'put' | 'delete'; - payload?: Record; - requiredPrivileges: string[]; - }> = [ - { - endpoint: '/internal/observability_ai_assistant/conversations', - method: 'post', - requiredPrivileges: ['ai_assistant'], - }, - { - endpoint: '/internal/observability_ai_assistant/conversation/{conversationId}', - method: 'put', - payload: { - path: { conversationId: 'test-id' }, - body: { conversation: conversationUpdate }, - }, - requiredPrivileges: ['ai_assistant'], - }, - { - endpoint: '/internal/observability_ai_assistant/conversation/{conversationId}', - method: 'get', - payload: { - path: { conversationId: 'test-id' }, - }, - requiredPrivileges: ['ai_assistant'], - }, - { - endpoint: '/internal/observability_ai_assistant/conversation/{conversationId}', - method: 'delete', - payload: { - path: { conversationId: 'test-id' }, - }, - requiredPrivileges: ['ai_assistant'], - }, - ]; + it('POST /internal/observability_ai_assistant/conversation', async () => { + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: 'POST /internal/observability_ai_assistant/conversation', + params: { + body: { + conversation: conversationCreate, + }, + }, + }); + } catch (e) { + expect(e.status).to.be(403); + } + }); - routes.forEach((route) => { - const { endpoint, method, payload } = route; + it('POST /internal/observability_ai_assistant/conversations', async () => { + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: 'POST /internal/observability_ai_assistant/conversations', + }); + } catch (e) { + expect(e.status).to.be(403); + } + }); - describe(`${method.toUpperCase()} ${endpoint}`, () => { - it('should deny access for unauthorized users', async () => { - const request = supertestWithoutAuth[method]( - resolveEndpoint( - endpoint, - payload?.path ? { conversationId: createResponse.body.conversation.id } : {} - ) - ) - .auth(unauthorizedUser.username, unauthorizedUser.password) - .set('kbn-xsrf', 'true'); + it('PUT /internal/observability_ai_assistant/conversation/{conversationId}', async () => { + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: 'PUT /internal/observability_ai_assistant/conversation/{conversationId}', + params: { + path: { + conversationId: createResponse.body.conversation.id, + }, + body: { + conversation: merge(omit(conversationUpdate, 'conversation.id'), { + conversation: { id: createResponse.body.conversation.id }, + }), + }, + }, + }); + } catch (e) { + expect(e.status).to.be(403); + } + }); - if (payload?.body) { - await request.send(payload.body); - } + it('GET /internal/observability_ai_assistant/conversation/{conversationId}', async () => { + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: 'GET /internal/observability_ai_assistant/conversation/{conversationId}', + params: { + path: { + conversationId: createResponse.body.conversation.id, + }, + }, + }); + } catch (e) { + expect(e.status).to.be(403); + } + }); - await request.expect(403); - }); + it('DELETE /internal/observability_ai_assistant/conversation/{conversationId}', async () => { + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}', + params: { + path: { + conversationId: createResponse.body.conversation.id, + }, + }, + }); + } catch (e) { + expect(e.status).to.be(403); + } }); }); }); diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts index 87f941dd14aa..73d53ffa6d1f 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts @@ -15,15 +15,11 @@ import { deleteInferenceEndpoint, deleteKnowledgeBaseModel, } from './helpers'; -import { resolveEndpoint } from '../../common/resolve_endpoint'; -import { unauthorizedUser } from '../../common/users/users'; -import { ApiErrorResponse } from '../../common/config'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); const es = getService('es'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); describe('Knowledge base', () => { before(async () => { @@ -216,81 +212,48 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('security roles and access privileges', () => { - const routes: Array<{ - endpoint: string; - endpointWithParams?: string; - method: 'get' | 'post' | 'put' | 'delete'; - payload?: Record; - requiredPrivileges: string[]; - }> = [ - { - endpoint: '/internal/observability_ai_assistant/kb/entries/save', - method: 'post', - payload: { - body: { - id: 'my-doc-id-1', - title: 'My title', - text: 'My content', - }, - }, - requiredPrivileges: ['ai_assistant'], - }, - { - endpoint: '/internal/observability_ai_assistant/kb/entries', - endpointWithParams: - '/internal/observability_ai_assistant/kb/entries?query=&sortBy=title&sortDirection=asc', - method: 'get', - payload: { - query: { - query: '', - sortBy: 'title', - sortDirection: 'asc', - }, - }, - requiredPrivileges: ['ai_assistant'], - }, - { - endpoint: '/internal/observability_ai_assistant/kb/entries/{entryId}', - endpointWithParams: '/internal/observability_ai_assistant/kb/entries/my-doc-id-1', - method: 'delete', - payload: { - path: { entryId: 'my-doc-id-1' }, - }, - requiredPrivileges: ['ai_assistant'], - }, - ]; - - routes.forEach((route) => { - const { endpoint, endpointWithParams, method, payload, requiredPrivileges } = route; - - describe(`${method.toUpperCase()} ${endpoint}`, () => { - it('should deny access for unauthorized users', async () => { - const request = supertestWithoutAuth[method]( - resolveEndpoint(endpoint, payload?.path || {}) - ) - .auth(unauthorizedUser.username, unauthorizedUser.password) - .set('kbn-xsrf', 'true'); - - if (payload?.body) { - await request.send(payload.body); - } + describe('should deny access for users without the ai_assistant privilege', () => { + it('POST /internal/observability_ai_assistant/kb/entries/save', async () => { + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: 'POST /internal/observability_ai_assistant/kb/entries/save', + params: { + body: { + id: 'my-doc-id-1', + title: 'My title', + text: 'My content', + }, + }, + }); + } catch (e) { + expect(e.status).to.be(403); + } + }); - if (payload?.query) { - await request.query(payload.query); - } + it('GET /internal/observability_ai_assistant/kb/entries', async () => { + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: 'GET /internal/observability_ai_assistant/kb/entries', + params: { + query: { query: '', sortBy: 'title', sortDirection: 'asc' }, + }, + }); + } catch (e) { + expect(e.status).to.be(403); + } + }); - await request.expect(403).then(({ body }: { body: ApiErrorResponse }) => { - expect(body).to.eql({ - statusCode: 403, - error: 'Forbidden', - message: `API [${method.toUpperCase()} ${ - endpointWithParams || endpoint - }] is unauthorized for user, this action is granted by the Kibana privileges [${requiredPrivileges.join( - ', ' - )}]`, - }); + it('DELETE /internal/observability_ai_assistant/kb/entries/{entryId}', async () => { + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: 'DELETE /internal/observability_ai_assistant/kb/entries/{entryId}', + params: { + path: { entryId: 'my-doc-id-1' }, + }, }); - }); + } catch (e) { + expect(e.status).to.be(403); + } }); }); }); diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts index fe8fc6cc4793..8220c4edc9c6 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts @@ -13,12 +13,10 @@ import { TINY_ELSER, deleteInferenceEndpoint, } from './helpers'; -import { unauthorizedUser } from '../../common/users/users'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); const es = getService('es'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); const KNOWLEDGE_BASE_SETUP_API_URL = '/internal/observability_ai_assistant/kb/setup'; @@ -67,21 +65,18 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('security roles and access privileges', () => { it('should deny access for users without the ai_assistant privilege', async () => { - await supertestWithoutAuth - .post(KNOWLEDGE_BASE_SETUP_API_URL) - .auth(unauthorizedUser.username, unauthorizedUser.password) - .query({ - model_id: TINY_ELSER.id, - }) - .set('kbn-xsrf', 'true') - .expect(403) - .then(({ body }: any) => { - expect(body).to.eql({ - statusCode: 403, - error: 'Forbidden', - message: `API [POST ${KNOWLEDGE_BASE_SETUP_API_URL}?model_id=pt_tiny_elser] is unauthorized for user, this action is granted by the Kibana privileges [ai_assistant]`, - }); + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: `POST ${KNOWLEDGE_BASE_SETUP_API_URL}`, + params: { + query: { + model_id: TINY_ELSER.id, + }, + }, }); + } catch (e) { + expect(e.status).to.be(403); + } }); }); }); diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts index 678a84e77041..7ff78751d474 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts @@ -13,13 +13,11 @@ import { TINY_ELSER, deleteInferenceEndpoint, } from './helpers'; -import { unauthorizedUser } from '../../common/users/users'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); const es = getService('es'); const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); const KNOWLEDGE_BASE_STATUS_API_URL = '/internal/observability_ai_assistant/kb/status'; @@ -87,18 +85,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { describe('security roles and access privileges', () => { it('should deny access for users without the ai_assistant privilege', async () => { - await supertestWithoutAuth - .get(KNOWLEDGE_BASE_STATUS_API_URL) - .auth(unauthorizedUser.username, unauthorizedUser.password) - .set('kbn-xsrf', 'true') - .expect(403) - .then(({ body }: any) => { - expect(body).to.eql({ - statusCode: 403, - error: 'Forbidden', - message: `API [GET ${KNOWLEDGE_BASE_STATUS_API_URL}] is unauthorized for user, this action is granted by the Kibana privileges [ai_assistant]`, - }); + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: `GET ${KNOWLEDGE_BASE_STATUS_API_URL}`, }); + } catch (e) { + expect(e.status).to.be(403); + } }); }); }); diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts index 7dc7a7b0b6f1..dbb3fa1fada0 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts @@ -22,14 +22,11 @@ import { import { getConversationCreatedEvent } from '../conversations/helpers'; import { LlmProxy, createLlmProxy } from '../../common/create_llm_proxy'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; -import { User, unauthorizedUser } from '../../common/users/users'; -import { resolveEndpoint } from '../../common/resolve_endpoint'; -import { ApiErrorResponse } from '../../common/config'; +import { User } from '../../common/users/users'; export default function ApiTest({ getService }: FtrProviderContext) { const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); const supertest = getService('supertest'); - const supertestWithoutAuth = getService('supertestWithoutAuth'); const es = getService('es'); const ml = getService('ml'); const log = getService('log'); @@ -367,60 +364,32 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); describe('security roles and access privileges', () => { - const routes: Array<{ - endpoint: string; - method: 'get' | 'post' | 'put' | 'delete'; - payload?: Record; - requiredPrivileges: string[]; - }> = [ - { - endpoint: '/internal/observability_ai_assistant/kb/user_instructions', - method: 'put', - payload: { - body: { - id: 'test-instruction', - text: 'Test user instruction', - public: true, - }, - }, - requiredPrivileges: ['ai_assistant'], - }, - { - endpoint: '/internal/observability_ai_assistant/kb/user_instructions', - method: 'get', - requiredPrivileges: ['ai_assistant'], - }, - ]; - - routes.forEach((route) => { - const { endpoint, method, payload, requiredPrivileges } = route; - - describe(`${method.toUpperCase()} ${endpoint}`, () => { - it('should deny access for unauthorized users', async () => { - const request = supertestWithoutAuth[method]( - resolveEndpoint(endpoint, payload?.path || {}) - ) - .auth(unauthorizedUser.username, unauthorizedUser.password) - .set('kbn-xsrf', 'true'); - - if (payload?.body) { - await request.send(payload.body); - } - - if (payload?.query) { - await request.query(payload.query); - } - - await request.expect(403).then(({ body }: { body: ApiErrorResponse }) => { - expect(body).to.eql({ - statusCode: 403, - error: 'Forbidden', - message: `API [${method.toUpperCase()} ${endpoint}] is unauthorized for user, this action is granted by the Kibana privileges [${requiredPrivileges.join( - ', ' - )}]`, - }); + describe('should deny access for users without the ai_assistant privilege', () => { + it('PUT /internal/observability_ai_assistant/kb/user_instructions', async () => { + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: 'PUT /internal/observability_ai_assistant/kb/user_instructions', + params: { + body: { + id: 'test-instruction', + text: 'Test user instruction', + public: true, + }, + }, }); - }); + } catch (e) { + expect(e.status).to.be(403); + } + }); + + it('GET /internal/observability_ai_assistant/kb/user_instructions', async () => { + try { + await observabilityAIAssistantAPIClient.unauthorizedUser({ + endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', + }); + } catch (e) { + expect(e.status).to.be(403); + } }); }); }); From 1a5c8eb5a511f7fb48e8226a396c92b898595a25 Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Tue, 3 Dec 2024 19:36:51 -0500 Subject: [PATCH 09/11] [Obs AI Assistant] Update tests to use Obs AI Assistant API client --- .../common/users/roles.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts index 3a56fd4662d0..ec5c9daac3ea 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/users/roles.ts @@ -5,8 +5,6 @@ * 2.0. */ -export const AI_ASSISTANT_ROLE_NAME = 'ai_assistant_role'; - // Example role: // export const allAccessRole: Role = { // name: 'all_access', From 0131d788969d249f62d283696c9ad25434595704 Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Tue, 3 Dec 2024 19:39:07 -0500 Subject: [PATCH 10/11] [Obs AI Assistant] Update tests to use Obs AI Assistant API client --- .../common/users/users.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts b/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts index df60539d83f1..2dc5a433517f 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/users/users.ts @@ -11,17 +11,9 @@ const password = kbnTestConfig.getUrlParts().password!; export const UNAUTHORIZED_USERNAME = 'unauthorized_user'; export const UNAUTHORIZED_USER_PASSWORD = 'unauthorized_password'; -export const AI_ASSISTANT_USER_NAME = 'ai_assistant_user'; -export const AI_ASSISTANT_USER_PASSWORD = `${AI_ASSISTANT_USER_NAME}-password`; export interface User { - username: - | 'elastic' - | 'editor' - | 'viewer' - | 'secondary_editor' - | 'unauthorized_user' - | 'ai_assistant_user'; + username: 'elastic' | 'editor' | 'viewer' | 'secondary_editor' | 'unauthorized_user'; password: string; roles: string[]; } From 4cfde64fe26a675582c8796b843a37cb27e48889 Mon Sep 17 00:00:00 2001 From: Viduni Wickramarachchi Date: Wed, 4 Dec 2024 08:31:11 -0500 Subject: [PATCH 11/11] [Obs AI Assistant] Update tests to definetely throw an error --- .../common/config.ts | 10 ++++++++++ .../tests/chat/chat.spec.ts | 2 ++ .../tests/complete/complete.spec.ts | 2 ++ .../tests/connectors/connectors.spec.ts | 2 ++ .../tests/conversations/conversations.spec.ts | 16 ++++++++++++++++ .../tests/knowledge_base/knowledge_base.spec.ts | 10 ++++++++++ .../knowledge_base/knowledge_base_setup.spec.ts | 2 ++ .../knowledge_base/knowledge_base_status.spec.ts | 2 ++ .../knowledge_base_user_instructions.spec.ts | 7 +++++++ 9 files changed, 53 insertions(+) diff --git a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts index 455004e5339f..6505ad3e94d6 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/common/config.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/common/config.ts @@ -33,6 +33,16 @@ export type ObservabilityAIAssistantAPIClient = Awaited< export type ObservabilityAIAssistantServices = Awaited>['services']; +export class ForbiddenApiError extends Error { + status: number; + + constructor(message: string = 'Forbidden') { + super(message); + this.name = 'ForbiddenApiError'; + this.status = 403; + } +} + export function createObservabilityAIAssistantAPIConfig({ config, license, diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts index c0a3da475e0c..cedd4c286dc1 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/chat/chat.spec.ts @@ -11,6 +11,7 @@ import { PassThrough } from 'stream'; import { createLlmProxy, LlmProxy } from '../../common/create_llm_proxy'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; +import { ForbiddenApiError } from '../../common/config'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -200,6 +201,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }, }); + throw new ForbiddenApiError('Expected unauthorizedUser() to throw a 403 Forbidden error'); } catch (e) { expect(e.status).to.be(403); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts index 5e59eed3a8c2..86e357e2e776 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/complete/complete.spec.ts @@ -32,6 +32,7 @@ import { getConversationUpdatedEvent, } from '../conversations/helpers'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; +import { ForbiddenApiError } from '../../common/config'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -502,6 +503,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }, }); + throw new ForbiddenApiError('Expected unauthorizedUser() to throw a 403 Forbidden error'); } catch (e) { expect(e.status).to.be(403); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts index 84b044d4b84a..42e1f8751719 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/connectors/connectors.spec.ts @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; import type { Agent as SuperTestAgent } from 'supertest'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; +import { ForbiddenApiError } from '../../common/config'; export default function ApiTest({ getService }: FtrProviderContext) { const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); @@ -60,6 +61,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { await observabilityAIAssistantAPIClient.unauthorizedUser({ endpoint: `GET ${CONNECTOR_API_URL}`, }); + throw new ForbiddenApiError('Expected unauthorizedUser() to throw a 403 Forbidden error'); } catch (e) { expect(e.status).to.be(403); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts index 3db3be8ec671..bb85e99b9950 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/conversations/conversations.spec.ts @@ -14,6 +14,7 @@ import { } from '@kbn/observability-ai-assistant-plugin/common/types'; import type { FtrProviderContext } from '../../common/ftr_provider_context'; import type { SupertestReturnType } from '../../common/observability_ai_assistant_api_client'; +import { ForbiddenApiError } from '../../common/config'; export default function ApiTest({ getService }: FtrProviderContext) { const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); @@ -292,6 +293,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }, }); + throw new ForbiddenApiError( + 'Expected unauthorizedUser() to throw a 403 Forbidden error' + ); } catch (e) { expect(e.status).to.be(403); } @@ -302,6 +306,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { await observabilityAIAssistantAPIClient.unauthorizedUser({ endpoint: 'POST /internal/observability_ai_assistant/conversations', }); + throw new ForbiddenApiError( + 'Expected unauthorizedUser() to throw a 403 Forbidden error' + ); } catch (e) { expect(e.status).to.be(403); } @@ -322,6 +329,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }, }); + throw new ForbiddenApiError( + 'Expected unauthorizedUser() to throw a 403 Forbidden error' + ); } catch (e) { expect(e.status).to.be(403); } @@ -337,6 +347,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }, }); + throw new ForbiddenApiError( + 'Expected unauthorizedUser() to throw a 403 Forbidden error' + ); } catch (e) { expect(e.status).to.be(403); } @@ -352,6 +365,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }, }); + throw new ForbiddenApiError( + 'Expected unauthorizedUser() to throw a 403 Forbidden error' + ); } catch (e) { expect(e.status).to.be(403); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts index 73d53ffa6d1f..9d80db3baeae 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base.spec.ts @@ -15,6 +15,7 @@ import { deleteInferenceEndpoint, deleteKnowledgeBaseModel, } from './helpers'; +import { ForbiddenApiError } from '../../common/config'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); @@ -225,6 +226,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }, }); + throw new ForbiddenApiError( + 'Expected unauthorizedUser() to throw a 403 Forbidden error' + ); } catch (e) { expect(e.status).to.be(403); } @@ -238,6 +242,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { query: { query: '', sortBy: 'title', sortDirection: 'asc' }, }, }); + throw new ForbiddenApiError( + 'Expected unauthorizedUser() to throw a 403 Forbidden error' + ); } catch (e) { expect(e.status).to.be(403); } @@ -251,6 +258,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { path: { entryId: 'my-doc-id-1' }, }, }); + throw new ForbiddenApiError( + 'Expected unauthorizedUser() to throw a 403 Forbidden error' + ); } catch (e) { expect(e.status).to.be(403); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts index 8220c4edc9c6..0d7625bb63ed 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_setup.spec.ts @@ -13,6 +13,7 @@ import { TINY_ELSER, deleteInferenceEndpoint, } from './helpers'; +import { ForbiddenApiError } from '../../common/config'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); @@ -74,6 +75,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }, }); + throw new ForbiddenApiError('Expected unauthorizedUser() to throw a 403 Forbidden error'); } catch (e) { expect(e.status).to.be(403); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts index 7ff78751d474..3f66931ca071 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_status.spec.ts @@ -13,6 +13,7 @@ import { TINY_ELSER, deleteInferenceEndpoint, } from './helpers'; +import { ForbiddenApiError } from '../../common/config'; export default function ApiTest({ getService }: FtrProviderContext) { const ml = getService('ml'); @@ -89,6 +90,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { await observabilityAIAssistantAPIClient.unauthorizedUser({ endpoint: `GET ${KNOWLEDGE_BASE_STATUS_API_URL}`, }); + throw new ForbiddenApiError('Expected unauthorizedUser() to throw a 403 Forbidden error'); } catch (e) { expect(e.status).to.be(403); } diff --git a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts index dbb3fa1fada0..d5022a052d78 100644 --- a/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts +++ b/x-pack/test/observability_ai_assistant_api_integration/tests/knowledge_base/knowledge_base_user_instructions.spec.ts @@ -23,6 +23,7 @@ import { getConversationCreatedEvent } from '../conversations/helpers'; import { LlmProxy, createLlmProxy } from '../../common/create_llm_proxy'; import { createProxyActionConnector, deleteActionConnector } from '../../common/action_connectors'; import { User } from '../../common/users/users'; +import { ForbiddenApiError } from '../../common/config'; export default function ApiTest({ getService }: FtrProviderContext) { const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient'); @@ -377,6 +378,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { }, }, }); + throw new ForbiddenApiError( + 'Expected unauthorizedUser() to throw a 403 Forbidden error' + ); } catch (e) { expect(e.status).to.be(403); } @@ -387,6 +391,9 @@ export default function ApiTest({ getService }: FtrProviderContext) { await observabilityAIAssistantAPIClient.unauthorizedUser({ endpoint: 'GET /internal/observability_ai_assistant/kb/user_instructions', }); + throw new ForbiddenApiError( + 'Expected unauthorizedUser() to throw a 403 Forbidden error' + ); } catch (e) { expect(e.status).to.be(403); }