[Fleet] Enforce unique names on download binary sources (#136648)

* [Fleet] Enforce unique names on download binary sources

* Add new SO to plugin.ts for privileges handling
This commit is contained in:
Cristina Amico 2022-07-20 10:29:06 +02:00 committed by GitHub
parent 3508350446
commit 8c239088e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 83 additions and 3 deletions

View file

@ -53,6 +53,7 @@ import {
PACKAGES_SAVED_OBJECT_TYPE,
ASSETS_SAVED_OBJECT_TYPE,
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
} from './constants';
import { registerSavedObjects, registerEncryptedSavedObjects } from './saved_objects';
import {
@ -141,6 +142,7 @@ const allSavedObjectTypes = [
PACKAGES_SAVED_OBJECT_TYPE,
ASSETS_SAVED_OBJECT_TYPE,
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
];
/**

View file

@ -33,7 +33,7 @@ function mockDownloadSourceSO(id: string, attributes: any = {}) {
};
}
function getMockedSoClient(options: { defaultDownloadSourceId?: string } = {}) {
function getMockedSoClient(options: { defaultDownloadSourceId?: string; sameName?: boolean } = {}) {
const soClient = savedObjectsClientMock.create();
soClient.get.mockImplementation(async (type: string, id: string) => {
@ -95,6 +95,25 @@ function getMockedSoClient(options: { defaultDownloadSourceId?: string } = {}) {
};
}
if (
options.sameName &&
findOptions.searchFields &&
findOptions.searchFields.includes('name') &&
findOptions
) {
return {
page: 1,
per_page: 10,
saved_objects: [
{
score: 0,
...(await soClient.get(DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE, 'download-source-test')),
},
],
total: 1,
};
}
return {
page: 1,
per_page: 10,
@ -102,12 +121,13 @@ function getMockedSoClient(options: { defaultDownloadSourceId?: string } = {}) {
total: 0,
};
});
mockedAppContextService.getInternalUserSOClient.mockReturnValue(soClient);
return soClient;
}
describe('Download Service Service', () => {
describe('Download Service', () => {
beforeEach(() => {
mockedAgentPolicyService.list.mockClear();
mockedAgentPolicyService.hasAPMIntegration.mockClear();
@ -256,4 +276,24 @@ describe('Download Service Service', () => {
expect(defaultId).toEqual('existing-default-download-source');
});
});
describe('requireUniqueName', () => {
it('throws an error if the name already exists', () => {
const soClient = getMockedSoClient({
defaultDownloadSourceId: 'download-source-test',
sameName: true,
});
expect(
async () => await downloadSourceService.requireUniqueName(soClient, { name: 'Test' })
).rejects.toThrow(`Download Source 'download-source-test' already exists with name 'Test'`);
});
it('does not throw if the name is unique', () => {
const soClient = getMockedSoClient({
defaultDownloadSourceId: 'download-source-test',
});
expect(
async () => await downloadSourceService.requireUniqueName(soClient, { name: 'Test' })
).not.toThrow();
});
});
});

View file

@ -13,11 +13,12 @@ import {
} from '../constants';
import type { DownloadSource, DownloadSourceAttributes, DownloadSourceBase } from '../types';
import { DownloadSourceError } from '../errors';
import { DownloadSourceError, IngestManagerError } from '../errors';
import { SO_SEARCH_LIMIT } from '../../common';
import { agentPolicyService } from './agent_policy';
import { appContextService } from './app_context';
import { escapeSearchQueryPhrase } from './saved_object';
function savedObjectToDownloadSource(so: SavedObject<DownloadSourceAttributes>) {
const { source_id: sourceId, ...attributes } = so.attributes;
@ -66,6 +67,11 @@ class DownloadSourceService {
): Promise<DownloadSource> {
const data: DownloadSourceAttributes = downloadSource;
await this.requireUniqueName(soClient, {
name: downloadSource.name,
id: options?.id,
});
// default should be only one
if (data.is_default) {
const defaultDownloadSourceId = await this.getDefaultDownloadSourceId(soClient);
@ -96,6 +102,13 @@ class DownloadSourceService {
) {
const updateData: Partial<DownloadSourceAttributes> = newData;
if (newData.name) {
await this.requireUniqueName(soClient, {
name: newData.name,
id,
});
}
if (updateData.is_default) {
const defaultDownloadSourceId = await this.getDefaultDownloadSourceId(soClient);
@ -164,6 +177,31 @@ class DownloadSourceService {
return defaultDS;
}
public async requireUniqueName(
soClient: SavedObjectsClientContract,
downloadSource: { name: string; id?: string }
) {
const results = await soClient.find<DownloadSourceAttributes>({
type: DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
searchFields: ['name'],
search: escapeSearchQueryPhrase(downloadSource.name),
});
const idsWithName = results.total && results.saved_objects.map(({ id }) => id);
if (Array.isArray(idsWithName)) {
const isEditingSelf = downloadSource?.id && idsWithName.includes(downloadSource.id);
if (!downloadSource.id || !isEditingSelf) {
const isSingle = idsWithName.length === 1;
const existClause = isSingle
? `Download Source '${idsWithName[0]}' already exists`
: `Download Sources '${idsWithName.join(',')}' already exist`;
throw new IngestManagerError(`${existClause} with name '${downloadSource.name}'`);
}
}
}
private async _getDefaultDownloadSourceSO(soClient: SavedObjectsClientContract) {
return await soClient.find<DownloadSourceAttributes>({
type: DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,