Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Saved objects extensions refactor merge #142878

Merged
merged 67 commits into from
Nov 18, 2022
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
e9f14d5
Adds extensions and factories for these
TinaHeiligers Oct 4, 2022
9386be3
Adds SO repo extensions, continues wrapper removal
jeramysoucy Oct 10, 2022
2f46222
Adds ROC extension implementations, complete removal of SOC wrapper r…
jeramysoucy Oct 11, 2022
06ca3e6
Removed unnecessary wrapper tests.
jeramysoucy Oct 12, 2022
72138ee
Removes wrapper references from secure spaces client. Moves extension…
jeramysoucy Oct 12, 2022
1bcbbdc
Fix to setup spaces client - now references security extension
jeramysoucy Oct 12, 2022
1f277e6
Resolves SO extension mock references.
jeramysoucy Oct 12, 2022
4fff618
Fixes several jest tests to include expected calling parameters with …
jeramysoucy Oct 12, 2022
c7c4d55
Fixes several unit tests, cleans up unused code and comments.
jeramysoucy Oct 13, 2022
a8eefce
Merges changes in api integration tests
jeramysoucy Oct 13, 2022
9935fef
Fixes accidental overwrite of esArchiver replacement.
jeramysoucy Oct 13, 2022
3398a57
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Oct 13, 2022
da05c66
Merge branch 'main' into refactor-update_object_spaces
jeramysoucy Oct 13, 2022
dfaf90b
Fixes repo called to find_legacy_url_aliases and find_shared_origin_o…
jeramysoucy Oct 14, 2022
e3b3fc1
Reordered testDataLoader calls to eliminate error when deleting objects.
jeramysoucy Oct 14, 2022
0eeb94b
Merge branch 'main' into refactor-update_object_spaces
jeramysoucy Oct 17, 2022
fce0433
Merge branch 'main' into refactor-update_object_spaces
jeramysoucy Oct 20, 2022
c3484af
Merge branch 'main' into refactor-update_object_spaces
jeramysoucy Oct 20, 2022
896813b
Manually adds definitions missed by merge from main.
jeramysoucy Oct 20, 2022
d831567
Additional manual merge of 'created at'
jeramysoucy Oct 20, 2022
ca77b0a
Merges bulk delete with SO extensions. Updates extension test suites …
jeramysoucy Oct 25, 2022
087c3a5
Adds test case for partially authorized in find
jeramysoucy Oct 26, 2022
dbb01f4
Adds test_helpers folder for commom test utilites.
jeramysoucy Oct 27, 2022
62c0ec1
Matches authorization behavior of bulk delete to existing delete. Res…
jeramysoucy Oct 28, 2022
7f8b670
Fixes bulk delete unit test based on changes to authz behavior.
jeramysoucy Oct 28, 2022
247de7b
Merge branch 'main' into merge-saved-objects-extensions
jeramysoucy Oct 28, 2022
2d01e30
Merge branch 'main' into refactor-update_object_spaces
jeramysoucy Oct 28, 2022
3192709
Resolves issues with fleet tests when bulkDelete is called with an em…
jeramysoucy Oct 31, 2022
e8c4840
Merge branch 'refactor-update_object_spaces' of https://github.com/Ti…
jeramysoucy Oct 31, 2022
1307b79
Merge branch 'main' into refactor-update_object_spaces
jeramysoucy Oct 31, 2022
2c45408
Merge branch 'main' into refactor-update_object_spaces
jeramysoucy Oct 31, 2022
0cbd92a
Fixes local types check issue
jeramysoucy Oct 31, 2022
b79b1b5
Adds missing TS docs - round 1
jeramysoucy Nov 2, 2022
33cb8e4
Adds missing TSDoc comments - round 2. Removes redundant ISavedObject…
jeramysoucy Nov 3, 2022
6a586e3
Removes 'import type' in so package domain. Updates mocks for find sh…
jeramysoucy Nov 3, 2022
7472293
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Nov 3, 2022
7f8214f
Removes 'import type' in so package domain. Updates mocks for find sh…
jeramysoucy Nov 3, 2022
507250f
Merge branch 'refactor-update_object_spaces' of https://github.com/Ti…
jeramysoucy Nov 3, 2022
d604293
Reverting removal of import type
jeramysoucy Nov 3, 2022
9ed2d02
Fixes lost update to point-in-time finder mock.
jeramysoucy Nov 3, 2022
bd0695b
import type housekeeping
jeramysoucy Nov 4, 2022
e72c6ad
Adds exports for extention ID's from core/server to replace static in…
jeramysoucy Nov 4, 2022
bca7189
Renames add_Extension functions to set_Extension
jeramysoucy Nov 4, 2022
8627b0f
Adds base extension factory and undefined consistency to extension fa…
jeramysoucy Nov 4, 2022
1df2e31
Adds enxtensions folder. Adds duplicate local extensions mock. Genera…
jeramysoucy Nov 4, 2022
eb39e38
[CI] Auto-commit changed files from 'node scripts/precommit_hook.js -…
kibanamachine Nov 4, 2022
f7dfda0
Fixes factory definitions.
jeramysoucy Nov 4, 2022
5edf4ed
Merge branch 'refactor-update_object_spaces' of https://github.com/Ti…
jeramysoucy Nov 4, 2022
f63f535
Merge branch 'main' into refactor-update_object_spaces
jeramysoucy Nov 4, 2022
7ca4adc
Merge branch 'main' into refactor-update_object_spaces
jeramysoucy Nov 7, 2022
1af7416
Changes imports for extension ID's to exports from packages. Removes …
jeramysoucy Nov 7, 2022
810878d
Merge branch 'refactor-update_object_spaces' of https://github.com/Ti…
jeramysoucy Nov 7, 2022
bd1a1a5
Adds unit tests for extension set methods of saved objects service.
jeramysoucy Nov 14, 2022
f146f2a
[CI] Auto-commit changed files from 'node scripts/precommit_hook.js -…
kibanamachine Nov 14, 2022
7e4d8c2
Updates checkConflicts comment, removes unused imports in service uni…
jeramysoucy Nov 14, 2022
4b083a1
Merge branch 'refactor-update_object_spaces' of https://github.com/Ti…
jeramysoucy Nov 14, 2022
6647ac9
Merge branch 'main' into refactor-update_object_spaces
jeramysoucy Nov 14, 2022
243bfaa
Merge branch 'main' into refactor-update_object_spaces
jeramysoucy Nov 15, 2022
3229e46
Refactor getAvailableSpaces in bulkGet to remove redundant null check.
jeramysoucy Nov 15, 2022
db77463
Merge branch 'refactor-update_object_spaces' of https://github.com/Ti…
jeramysoucy Nov 15, 2022
7f41316
Adds public types to core entry point exports.
jeramysoucy Nov 15, 2022
31b8532
Update packages/core/saved-objects/core-saved-objects-api-server/src/…
jeramysoucy Nov 17, 2022
f05c8b6
Refactors getExtensions of the scoped client provider, and CheckAutho…
jeramysoucy Nov 17, 2022
7678f71
Merge branch 'main' into refactor-update_object_spaces
jeramysoucy Nov 17, 2022
c9b25b0
Fixes unit tests for check auth expected actions.
jeramysoucy Nov 17, 2022
ae02c50
Fixes collect multi-namespace refs unit tests.
jeramysoucy Nov 17, 2022
9909253
Merge branch 'main' into refactor-update_object_spaces
jeramysoucy Nov 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
},
savedObjects: {
setClientFactoryProvider: deps.savedObjects.setClientFactoryProvider,
addClientWrapper: deps.savedObjects.addClientWrapper,
addEncryptionExtension: deps.savedObjects.addEncryptionExtension,
addSecurityExtension: deps.savedObjects.addSecurityExtension,
addSpacesExtension: deps.savedObjects.addSpacesExtension,
registerType: deps.savedObjects.registerType,
getKibanaIndex: deps.savedObjects.getKibanaIndex,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ import {
} from './collect_multi_namespace_references';
import { collectMultiNamespaceReferences } from './collect_multi_namespace_references';
import type { CreatePointInTimeFinderFn } from './point_in_time_finder';
import { AuditAction, ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server';

import {
authMap,
enforceError,
typeMapsAreEqual,
setsAreEqual,
setupCheckAuthorized,
setupCheckUnauthorized,
setupEnforceFailure,
setupEnforceSuccess,
setupRedactPassthrough,
} from '../test_helpers/repository.test.common';
import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks';

const SPACES = ['default', 'another-space'];
const VERSION_PROPS = { _seq_no: 1, _primary_term: 1 };
Expand All @@ -50,7 +64,8 @@ describe('collectMultiNamespaceReferences', () => {
/** Sets up the type registry, saved objects client, etc. and return the full parameters object to be passed to `collectMultiNamespaceReferences` */
function setup(
objects: SavedObjectsCollectMultiNamespaceReferencesObject[],
options: SavedObjectsCollectMultiNamespaceReferencesOptions = {}
options: SavedObjectsCollectMultiNamespaceReferencesOptions = {},
securityExtension?: ISavedObjectsSecurityExtension | undefined
): CollectMultiNamespaceReferencesParams {
const registry = typeRegistryMock.create();
registry.isMultiNamespace.mockImplementation(
Expand All @@ -65,8 +80,8 @@ describe('collectMultiNamespaceReferences', () => {
(type) => [MULTI_NAMESPACE_OBJ_TYPE_1, MULTI_NAMESPACE_HIDDEN_OBJ_TYPE].includes(type) // MULTI_NAMESPACE_OBJ_TYPE_2 and NON_MULTI_NAMESPACE_TYPE are omitted
);
client = elasticsearchClientMock.createElasticsearchClient();

const serializer = new SavedObjectsSerializer(registry);

return {
registry,
allowedTypes: [
Expand All @@ -78,6 +93,7 @@ describe('collectMultiNamespaceReferences', () => {
serializer,
getIndexForType: (type: string) => `index-for-${type}`,
createPointInTimeFinder: jest.fn() as CreatePointInTimeFinderFn,
securityExtension,
objects,
options,
};
Expand Down Expand Up @@ -287,6 +303,7 @@ describe('collectMultiNamespaceReferences', () => {
// obj3 is excluded from the results
]);
});

it(`handles 404 responses that don't come from Elasticsearch`, async () => {
const createEsUnavailableNotFoundError = () => {
return SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError();
Expand Down Expand Up @@ -447,4 +464,213 @@ describe('collectMultiNamespaceReferences', () => {
);
});
});

describe('with security enabled', () => {
const mockSecurityExt = savedObjectsExtensionsMock.createSecurityExtension();
const obj1 = { type: MULTI_NAMESPACE_OBJ_TYPE_1, id: 'id-1' };
const obj2 = { type: MULTI_NAMESPACE_OBJ_TYPE_1, id: 'id-2' };
const obj3 = { type: MULTI_NAMESPACE_OBJ_TYPE_1, id: 'id-3' };
const objects = [obj1, obj2];
const obj1LegacySpaces = ['space-1', 'space-2', 'space-3', 'space-4'];
let params: CollectMultiNamespaceReferencesParams;

beforeEach(() => {
params = setup([obj1, obj2], {}, mockSecurityExt);
mockMgetResults({ found: true, references: [obj3] }, { found: true, references: [] }); // results for obj1 and obj2
mockMgetResults({ found: true, references: [] }); // results for obj3
mockFindLegacyUrlAliases.mockResolvedValue(
new Map([
[`${obj1.type}:${obj1.id}`, new Set(obj1LegacySpaces)],
// the result map does not contain keys for obj2 or obj3 because we did not find any aliases for those objects
])
);
});

afterEach(() => {
mockSecurityExt.checkAuthorization.mockReset();
mockSecurityExt.enforceAuthorization.mockReset();
mockSecurityExt.redactNamespaces.mockReset();
mockSecurityExt.addAuditEvent.mockReset();
});

describe(`errors`, () => {
test(`propagates decorated error when not authorized`, async () => {
setupCheckUnauthorized(mockSecurityExt);
// Unlike other functions, it doesn't validate the level of authorization first, so we need to
// carry on and mock the enforce function as well to create an unauthorized condition
setupEnforceFailure(mockSecurityExt);

await expect(collectMultiNamespaceReferences(params)).rejects.toThrow(enforceError);
expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1);
expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1);
});

test(`adds audit event per object when not successful`, async () => {
setupCheckUnauthorized(mockSecurityExt);
// Unlike other functions, it doesn't validate the level of authorization first, so we need to
// carry on and mock the enforce function as well to create an unauthorized condition
setupEnforceFailure(mockSecurityExt);

await expect(collectMultiNamespaceReferences(params)).rejects.toThrow(enforceError);
expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1);
expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1);

expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(objects.length);
objects.forEach((obj) => {
expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({
action: AuditAction.COLLECT_MULTINAMESPACE_REFERENCES,
savedObject: { type: obj.type, id: obj.id },
error: enforceError,
});
});
});
});

describe('checks privileges', () => {
beforeEach(() => {
setupCheckUnauthorized(mockSecurityExt);
setupEnforceFailure(mockSecurityExt);
});
test(`in the default state`, async () => {
await expect(collectMultiNamespaceReferences(params)).rejects.toThrow(enforceError);

expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1);
const expectedSpaces = new Set(['default', ...SPACES, ...obj1LegacySpaces]);
const { spaces: actualSpaces } = mockSecurityExt.checkAuthorization.mock.calls[0][0];
expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy();

expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1);
const expectedTypesAndSpaces = new Map([[objects[0].type, new Set(['default'])]]);
const { typesAndSpaces: actualTypesAndSpaces } =
mockSecurityExt.enforceAuthorization.mock.calls[0][0];

expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy();
});

test(`in a non-default state`, async () => {
const namespace = 'space-X';
await expect(
collectMultiNamespaceReferences({ ...params, options: { namespace } })
).rejects.toThrow(enforceError);

expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1);
const expectedSpaces = new Set([namespace, ...SPACES, ...obj1LegacySpaces]);
const { spaces: actualSpaces } = mockSecurityExt.checkAuthorization.mock.calls[0][0];
expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy();

expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1);
const expectedTypesAndSpaces = new Map([[objects[0].type, new Set([namespace])]]);
const { typesAndSpaces: actualTypesAndSpaces } =
mockSecurityExt.enforceAuthorization.mock.calls[0][0];

expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy();
});

test(`with purpose 'collectMultiNamespaceReferences'`, async () => {
const options: SavedObjectsCollectMultiNamespaceReferencesOptions = {
purpose: 'collectMultiNamespaceReferences',
};

setupCheckUnauthorized(mockSecurityExt);
setupEnforceFailure(mockSecurityExt);

await expect(collectMultiNamespaceReferences({ ...params, options })).rejects.toThrow(
enforceError
);
expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1);
expect(mockSecurityExt.checkAuthorization).toBeCalledWith(
expect.objectContaining({
actions: ['bulk_get'],
})
);
expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1);
});

test(`with purpose 'updateObjectsSpaces'`, async () => {
const options: SavedObjectsCollectMultiNamespaceReferencesOptions = {
purpose: 'updateObjectsSpaces',
};

setupCheckUnauthorized(mockSecurityExt);
setupEnforceFailure(mockSecurityExt);

await expect(collectMultiNamespaceReferences({ ...params, options })).rejects.toThrow(
enforceError
);
expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1);
expect(mockSecurityExt.checkAuthorization).toBeCalledWith(
expect.objectContaining({
actions: ['share_to_space'],
})
);
expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1);
});
});

describe('success', () => {
beforeEach(async () => {
setupCheckAuthorized(mockSecurityExt);
setupEnforceSuccess(mockSecurityExt);
setupRedactPassthrough(mockSecurityExt);
await collectMultiNamespaceReferences(params);
});
test(`calls redactNamespaces with type, spaces, and authorization map`, async () => {
expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1);
const expectedSpaces = new Set(['default', ...SPACES, ...obj1LegacySpaces]);
const { spaces: actualSpaces } = mockSecurityExt.checkAuthorization.mock.calls[0][0];
expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy();

const resultObjects = [obj1, obj2, obj3];

// enforce is called once for all objects/spaces, then once per object
expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(
1 + resultObjects.length
);
const expectedTypesAndSpaces = new Map([[objects[0].type, new Set(['default'])]]);
const { typesAndSpaces: actualTypesAndSpaces } =
mockSecurityExt.enforceAuthorization.mock.calls[0][0];
expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy();

// Redact is called once per object, but an additional time for object 1 because it has legacy URL aliases in another set of spaces
expect(mockSecurityExt.redactNamespaces).toBeCalledTimes(resultObjects.length + 1);
const expectedRedactParams = [
{ type: obj1.type, spaces: SPACES },
{ type: obj1.type, spaces: obj1LegacySpaces },
{ type: obj2.type, spaces: SPACES },
{ type: obj3.type, spaces: SPACES },
];

expectedRedactParams.forEach((expected, i) => {
const { savedObject, typeMap } = mockSecurityExt.redactNamespaces.mock.calls[i][0];
expect(savedObject).toEqual(
expect.objectContaining({
type: expected.type,
namespaces: expected.spaces,
})
);
expect(typeMap).toBe(authMap);
});
});

test(`adds audit event per object when successful`, async () => {
expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1);

const resultObjects = [obj1, obj2, obj3];

// enforce is called once for all objects/spaces, then once per object
expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(
1 + resultObjects.length
);

expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(resultObjects.length);
resultObjects.forEach((obj) => {
expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({
action: AuditAction.COLLECT_MULTINAMESPACE_REFERENCES,
savedObject: { type: obj.type, id: obj.id },
error: undefined,
});
});
});
});
});
});
Loading