mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Obs AI Assistant] Manual migration for routes with access tag (#202817)
## Summary ### Problem `tags: [access:ai_assistant]` is deprecated. ### Solution All the routes that use this tag needs to be migrated to the `authz`: `requiredPrivileges` property. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
a6f5017623
commit
d669c83be8
16 changed files with 449 additions and 60 deletions
|
@ -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<Readable> => {
|
||||
|
@ -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<Readable> => {
|
||||
|
|
|
@ -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<FindActionResult[]> => {
|
||||
const { request, plugins } = resources;
|
||||
|
|
|
@ -17,8 +17,10 @@ const getConversationRoute = createObservabilityAIAssistantServerRoute({
|
|||
conversationId: t.string,
|
||||
}),
|
||||
}),
|
||||
options: {
|
||||
tags: ['access:ai_assistant'],
|
||||
security: {
|
||||
authz: {
|
||||
requiredPrivileges: ['ai_assistant'],
|
||||
},
|
||||
},
|
||||
handler: async (resources): Promise<Conversation> => {
|
||||
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<Conversation> => {
|
||||
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<Conversation> => {
|
||||
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<Conversation> => {
|
||||
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<void> => {
|
||||
const { service, request, params } = resources;
|
||||
|
|
|
@ -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<void> => {
|
||||
const client = await resources.service.getClient({ request: resources.request });
|
||||
|
|
|
@ -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<InferenceInferenceEndpointInfo> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
const client = await resources.service.getClient({ request: resources.request });
|
||||
|
|
|
@ -85,5 +85,4 @@ export interface ObservabilityAIAssistantRouteCreateOptions {
|
|||
payload?: number;
|
||||
idleSocket?: number;
|
||||
};
|
||||
tags: Array<'access:ai_assistant'>;
|
||||
}
|
||||
|
|
|
@ -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,6 +33,16 @@ export type ObservabilityAIAssistantAPIClient = Awaited<
|
|||
|
||||
export type ObservabilityAIAssistantServices = Awaited<ReturnType<CreateTestConfig>>['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,
|
||||
|
@ -67,6 +77,7 @@ export function createObservabilityAIAssistantAPIConfig({
|
|||
viewer: getScopedApiClientForUsername(viewer.username),
|
||||
editor: getScopedApiClientForUsername(editor.username),
|
||||
secondaryEditor: getScopedApiClientForUsername(secondaryEditor.username),
|
||||
unauthorizedUser: getScopedApiClientForUsername(unauthorizedUser.username),
|
||||
};
|
||||
},
|
||||
},
|
||||
|
|
|
@ -6,10 +6,14 @@
|
|||
*/
|
||||
|
||||
import { kbnTestConfig } from '@kbn/test';
|
||||
|
||||
const password = kbnTestConfig.getUrlParts().password!;
|
||||
|
||||
export const UNAUTHORIZED_USERNAME = 'unauthorized_user';
|
||||
export const UNAUTHORIZED_USER_PASSWORD = 'unauthorized_password';
|
||||
|
||||
export interface User {
|
||||
username: 'elastic' | 'editor' | 'viewer' | 'secondary_editor';
|
||||
username: 'elastic' | 'editor' | 'viewer' | 'secondary_editor' | 'unauthorized_user';
|
||||
password: string;
|
||||
roles: string[];
|
||||
}
|
||||
|
@ -32,4 +36,10 @@ 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 allUsers = [editor, secondaryEditor, viewer, unauthorizedUser];
|
||||
|
|
|
@ -11,10 +11,12 @@ 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');
|
||||
const log = getService('log');
|
||||
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient');
|
||||
|
||||
const CHAT_API_URL = `/internal/observability_ai_assistant/chat`;
|
||||
|
||||
|
@ -183,5 +185,27 @@ 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 () => {
|
||||
try {
|
||||
await observabilityAIAssistantAPIClient.unauthorizedUser({
|
||||
endpoint: `POST ${CHAT_API_URL}`,
|
||||
params: {
|
||||
body: {
|
||||
name: 'my_api_call',
|
||||
messages,
|
||||
connectorId,
|
||||
functions: [],
|
||||
scopes: ['all'],
|
||||
},
|
||||
},
|
||||
});
|
||||
throw new ForbiddenApiError('Expected unauthorizedUser() to throw a 403 Forbidden error');
|
||||
} catch (e) {
|
||||
expect(e.status).to.be(403);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
@ -39,7 +40,7 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
|
||||
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 +487,27 @@ 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 () => {
|
||||
try {
|
||||
await observabilityAIAssistantAPIClient.unauthorizedUser({
|
||||
endpoint: 'POST /internal/observability_ai_assistant/chat/complete',
|
||||
params: {
|
||||
body: {
|
||||
messages,
|
||||
connectorId,
|
||||
persist: false,
|
||||
screenContexts: [],
|
||||
scopes: ['all'],
|
||||
},
|
||||
},
|
||||
});
|
||||
throw new ForbiddenApiError('Expected unauthorizedUser() to throw a 403 Forbidden error');
|
||||
} catch (e) {
|
||||
expect(e.status).to.be(403);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,12 +9,15 @@ 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');
|
||||
const supertest = getService('supertest');
|
||||
const log = getService('log');
|
||||
|
||||
const CONNECTOR_API_URL = '/internal/observability_ai_assistant/connectors';
|
||||
|
||||
describe('List connectors', () => {
|
||||
before(async () => {
|
||||
await deleteAllActionConnectors(supertest);
|
||||
|
@ -27,14 +30,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 +47,26 @@ 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 () => {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
@ -250,5 +251,128 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('security roles and access privileges', () => {
|
||||
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);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await observabilityAIAssistantAPIClient
|
||||
.editor({
|
||||
endpoint: 'DELETE /internal/observability_ai_assistant/conversation/{conversationId}',
|
||||
params: {
|
||||
path: {
|
||||
conversationId: createResponse.body.conversation.id,
|
||||
},
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('POST /internal/observability_ai_assistant/conversation', async () => {
|
||||
try {
|
||||
await observabilityAIAssistantAPIClient.unauthorizedUser({
|
||||
endpoint: 'POST /internal/observability_ai_assistant/conversation',
|
||||
params: {
|
||||
body: {
|
||||
conversation: conversationCreate,
|
||||
},
|
||||
},
|
||||
});
|
||||
throw new ForbiddenApiError(
|
||||
'Expected unauthorizedUser() to throw a 403 Forbidden error'
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e.status).to.be(403);
|
||||
}
|
||||
});
|
||||
|
||||
it('POST /internal/observability_ai_assistant/conversations', async () => {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
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 },
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
throw new ForbiddenApiError(
|
||||
'Expected unauthorizedUser() to throw a 403 Forbidden error'
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e.status).to.be(403);
|
||||
}
|
||||
});
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
});
|
||||
throw new ForbiddenApiError(
|
||||
'Expected unauthorizedUser() to throw a 403 Forbidden error'
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e.status).to.be(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,
|
||||
},
|
||||
},
|
||||
});
|
||||
throw new ForbiddenApiError(
|
||||
'Expected unauthorizedUser() to throw a 403 Forbidden error'
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e.status).to.be(403);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
deleteInferenceEndpoint,
|
||||
deleteKnowledgeBaseModel,
|
||||
} from './helpers';
|
||||
import { ForbiddenApiError } from '../../common/config';
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const ml = getService('ml');
|
||||
|
@ -210,6 +211,62 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
expect(entries[0].title).to.eql('My title b');
|
||||
});
|
||||
});
|
||||
|
||||
describe('security roles and access privileges', () => {
|
||||
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',
|
||||
},
|
||||
},
|
||||
});
|
||||
throw new ForbiddenApiError(
|
||||
'Expected unauthorizedUser() to throw a 403 Forbidden error'
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e.status).to.be(403);
|
||||
}
|
||||
});
|
||||
|
||||
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' },
|
||||
},
|
||||
});
|
||||
throw new ForbiddenApiError(
|
||||
'Expected unauthorizedUser() to throw a 403 Forbidden error'
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e.status).to.be(403);
|
||||
}
|
||||
});
|
||||
|
||||
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' },
|
||||
},
|
||||
});
|
||||
throw new ForbiddenApiError(
|
||||
'Expected unauthorizedUser() to throw a 403 Forbidden error'
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e.status).to.be(403);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -13,18 +13,21 @@ import {
|
|||
TINY_ELSER,
|
||||
deleteInferenceEndpoint,
|
||||
} from './helpers';
|
||||
import { ForbiddenApiError } from '../../common/config';
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const ml = getService('ml');
|
||||
const es = getService('es');
|
||||
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 +46,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 +63,23 @@ 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 () => {
|
||||
try {
|
||||
await observabilityAIAssistantAPIClient.unauthorizedUser({
|
||||
endpoint: `POST ${KNOWLEDGE_BASE_SETUP_API_URL}`,
|
||||
params: {
|
||||
query: {
|
||||
model_id: TINY_ELSER.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
throw new ForbiddenApiError('Expected unauthorizedUser() to throw a 403 Forbidden error');
|
||||
} catch (e) {
|
||||
expect(e.status).to.be(403);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -13,12 +13,15 @@ import {
|
|||
TINY_ELSER,
|
||||
deleteInferenceEndpoint,
|
||||
} from './helpers';
|
||||
import { ForbiddenApiError } from '../../common/config';
|
||||
|
||||
export default function ApiTest({ getService }: FtrProviderContext) {
|
||||
const ml = getService('ml');
|
||||
const es = getService('es');
|
||||
const observabilityAIAssistantAPIClient = getService('observabilityAIAssistantAPIClient');
|
||||
|
||||
const KNOWLEDGE_BASE_STATUS_API_URL = '/internal/observability_ai_assistant/kb/status';
|
||||
|
||||
describe('/internal/observability_ai_assistant/kb/status', () => {
|
||||
beforeEach(async () => {
|
||||
await createKnowledgeBaseModel(ml);
|
||||
|
@ -41,7 +44,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 +57,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 +73,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 +83,18 @@ 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 () => {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
@ -362,5 +363,42 @@ export default function ApiTest({ getService }: FtrProviderContext) {
|
|||
expect(res2).to.be('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('security roles and access privileges', () => {
|
||||
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,
|
||||
},
|
||||
},
|
||||
});
|
||||
throw new ForbiddenApiError(
|
||||
'Expected unauthorizedUser() to throw a 403 Forbidden error'
|
||||
);
|
||||
} 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',
|
||||
});
|
||||
throw new ForbiddenApiError(
|
||||
'Expected unauthorizedUser() to throw a 403 Forbidden error'
|
||||
);
|
||||
} catch (e) {
|
||||
expect(e.status).to.be(403);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue