[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:
Viduni Wickramarachchi 2024-12-04 15:21:25 -05:00 committed by GitHub
parent a6f5017623
commit d669c83be8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 449 additions and 60 deletions

View file

@ -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> => {

View file

@ -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;

View file

@ -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;

View file

@ -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 });

View file

@ -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 });

View file

@ -85,5 +85,4 @@ export interface ObservabilityAIAssistantRouteCreateOptions {
payload?: number;
idleSocket?: number;
};
tags: Array<'access:ai_assistant'>;
}

View file

@ -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),
};
},
},

View file

@ -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];

View file

@ -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);
}
});
});
});
}

View file

@ -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);
}
});
});
});
}

View file

@ -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);
}
});
});
});
}

View file

@ -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);
}
});
});
});
});
}

View file

@ -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);
}
});
});
});
});
}

View file

@ -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);
}
});
});
});
}

View file

@ -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);
}
});
});
});
}

View file

@ -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);
}
});
});
});
});
}