Skip to content

Commit

Permalink
[Epic] Knowledge Base - API integration tests (#8737) (#197290)
Browse files Browse the repository at this point in the history
## Summary

This is a followup to the main Knowledge Base changes where we've:
1. Fixed the issue with access control to KB entries via bulk actions
APIs
2. Added the RBAC validation for the bulk actions API
3. Added integration tests to cover the bulk actions API


### Checklist

Delete any items that are not applicable to this PR.

- [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] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- Genai KB integration tests: [100 ESS + 100
Serverless](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/7208)

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
e40pud and kibanamachine authored Oct 23, 2024
1 parent 1a11d6a commit fd53861
Show file tree
Hide file tree
Showing 8 changed files with 666 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,13 @@ export class DocumentsDataWriter implements DocumentsDataWriter {
{
bool: {
must_not: {
exists: {
field: 'users',
nested: {
path: 'users',
query: {
exists: {
field: 'users',
},
},
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ import {
} from '../../../ai_assistant_data_clients/knowledge_base/types';
import { ElasticAssistantPluginRouter } from '../../../types';
import { buildResponse } from '../../utils';
import { transformESSearchToKnowledgeBaseEntry } from '../../../ai_assistant_data_clients/knowledge_base/transforms';
import {
transformESSearchToKnowledgeBaseEntry,
transformESToKnowledgeBase,
} from '../../../ai_assistant_data_clients/knowledge_base/transforms';
import {
getUpdateScript,
transformToCreateSchema,
transformToUpdateSchema,
} from '../../../ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry';
import { getKBUserFilter } from './utils';

export interface BulkOperationError {
message: string;
Expand Down Expand Up @@ -179,8 +183,19 @@ export const bulkActionKnowledgeBaseEntriesRoute = (router: ElasticAssistantPlug
const spaceId = ctx.elasticAssistant.getSpaceId();
// Authenticated user null check completed in `performChecks()` above
const authenticatedUser = ctx.elasticAssistant.getCurrentUser() as AuthenticatedUser;
const userFilter = getKBUserFilter(authenticatedUser);
const manageGlobalKnowledgeBaseAIAssistant =
kbDataClient?.options.manageGlobalKnowledgeBaseAIAssistant;

if (body.create && body.create.length > 0) {
// RBAC validation
body.create.forEach((entry) => {
const isGlobal = entry.users != null && entry.users.length === 0;
if (isGlobal && !manageGlobalKnowledgeBaseAIAssistant) {
throw new Error(`User lacks privileges to create global knowledge base entries`);
}
});

const result = await kbDataClient?.findDocuments<EsKnowledgeBaseEntrySchema>({
perPage: 100,
page: 1,
Expand All @@ -199,6 +214,44 @@ export const bulkActionKnowledgeBaseEntriesRoute = (router: ElasticAssistantPlug
}
}

const validateDocumentsModification = async (
documentIds: string[],
operation: 'delete' | 'update'
) => {
if (!documentIds.length) {
return;
}
const documentsFilter = documentIds.map((id) => `_id:${id}`).join(' OR ');
const entries = await kbDataClient?.findDocuments<EsKnowledgeBaseEntrySchema>({
page: 1,
perPage: 100,
filter: `${documentsFilter} AND ${userFilter}`,
});
const availableEntries = entries
? transformESSearchToKnowledgeBaseEntry(entries.data)
: [];
availableEntries.forEach((entry) => {
// RBAC validation
const isGlobal = entry.users != null && entry.users.length === 0;
if (isGlobal && !manageGlobalKnowledgeBaseAIAssistant) {
throw new Error(
`User lacks privileges to ${operation} global knowledge base entries`
);
}
});
const availableIds = availableEntries.map((doc) => doc.id);
const nonAvailableIds = documentIds.filter((id) => !availableIds.includes(id));
if (nonAvailableIds.length > 0) {
throw new Error(`Could not find documents to ${operation}: ${nonAvailableIds}.`);
}
};

await validateDocumentsModification(body.delete?.ids ?? [], 'delete');
await validateDocumentsModification(
body.update?.map((entry) => entry.id) ?? [],
'update'
);

const writer = await kbDataClient?.getWriter();
const changedAt = new Date().toISOString();
const {
Expand All @@ -214,11 +267,11 @@ export const bulkActionKnowledgeBaseEntriesRoute = (router: ElasticAssistantPlug
spaceId,
user: authenticatedUser,
entry,
global: entry.users != null && entry.users.length === 0,
})
),
documentsToDelete: body.delete?.ids,
documentsToUpdate: body.update?.map((entry) =>
// TODO: KB-RBAC check, required when users != null as entry will either be created globally if empty
transformToUpdateSchema({
user: authenticatedUser,
updatedAt: changedAt,
Expand All @@ -241,9 +294,10 @@ export const bulkActionKnowledgeBaseEntriesRoute = (router: ElasticAssistantPlug

return buildBulkResponse(response, {
// @ts-ignore-next-line TS2322
updated: docsUpdated,
updated: transformESToKnowledgeBase(docsUpdated),
created: created?.data ? transformESSearchToKnowledgeBaseEntry(created?.data) : [],
deleted: docsDeleted ?? [],
skipped: [],
errors,
});
} catch (err) {
Expand Down
Loading

0 comments on commit fd53861

Please sign in to comment.