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

Add updated_by to saved objects #182687

Merged
merged 13 commits into from
May 29, 2024
1 change: 1 addition & 0 deletions .buildkite/ftr_configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ enabled:
- x-pack/test/saved_object_api_integration/security_and_spaces/config_basic.ts
- x-pack/test/saved_object_api_integration/security_and_spaces/config_trial.ts
- x-pack/test/saved_object_api_integration/spaces_only/config.ts
- x-pack/test/saved_object_api_integration/user_profiles/config.ts
- x-pack/test/saved_object_tagging/api_integration/security_and_spaces/config.ts
- x-pack/test/saved_object_tagging/api_integration/tagging_api/config.ts
- x-pack/test/saved_object_tagging/api_integration/tagging_usage_collection/config.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export interface SimpleSavedObject<T = unknown> {
references: SavedObjectType<T>['references'];
/** The date this object was last updated */
updatedAt: SavedObjectType<T>['updated_at'];
/** The user that last updated this object */
updatedBy: SavedObjectType<T>['updated_by'];
/** The date this object was created */
createdAt: SavedObjectType<T>['created_at'];
/** The user that created this object */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export const performBulkCreate = async <T>(
} = options;
const time = getCurrentTime();
const createdBy = userHelper.getCurrentUserProfileUid();
const updatedBy = createdBy;

let preflightCheckIndexCounter = 0;
const expectedResults = objects.map<ExpectedResult>((object) => {
Expand Down Expand Up @@ -234,6 +235,7 @@ export const performBulkCreate = async <T>(
updated_at: time,
created_at: time,
...(createdBy && { created_by: createdBy }),
...(updatedBy && { updated_by: updatedBy }),
references: object.references || [],
originId,
}) as SavedObjectSanitizedDoc<T>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,12 @@ export const performBulkUpdate = async <T>(
common: commonHelper,
encryption: encryptionHelper,
migration: migrationHelper,
user: userHelper,
} = helpers;
const { securityExtension } = extensions;
const { migrationVersionCompatibility } = options;
const namespace = commonHelper.getCurrentNamespace(options.namespace);
const updatedBy = userHelper.getCurrentUserProfileUid();
const time = getCurrentTime();

let bulkGetRequestIndexCounter = 0;
Expand Down Expand Up @@ -120,6 +122,7 @@ export const performBulkUpdate = async <T>(
const documentToSave = {
[type]: attributes,
updated_at: time,
updated_by: updatedBy,
...(Array.isArray(references) && { references }),
};

Expand Down Expand Up @@ -304,6 +307,7 @@ export const performBulkUpdate = async <T>(
namespaces,
attributes: updatedAttributes,
updated_at: time,
updated_by: updatedBy,
...(Array.isArray(documentToSave.references) && { references: documentToSave.references }),
});
const updatedMigratedDocumentToSave = serializer.savedObjectToRaw(
Expand Down Expand Up @@ -364,7 +368,7 @@ export const performBulkUpdate = async <T>(
const { _seq_no: seqNo, _primary_term: primaryTerm } = rawResponse;

// eslint-disable-next-line @typescript-eslint/naming-convention
const { [type]: attributes, references, updated_at } = documentToSave;
const { [type]: attributes, references, updated_at, updated_by } = documentToSave;

const { originId } = rawMigratedUpdatedDoc._source;
return {
Expand All @@ -373,6 +377,7 @@ export const performBulkUpdate = async <T>(
...(namespaces && { namespaces }),
...(originId && { originId }),
updated_at,
updated_by,
version: encodeVersion(seqNo, primaryTerm),
attributes,
references,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const performCreate = async <T>(

const time = getCurrentTime();
const createdBy = userHelper.getCurrentUserProfileUid();
const updatedBy = createdBy;
let savedObjectNamespace: string | undefined;
let savedObjectNamespaces: string[] | undefined;
let existingOriginId: string | undefined;
Expand Down Expand Up @@ -136,6 +137,7 @@ export const performCreate = async <T>(
created_at: time,
updated_at: time,
...(createdBy && { created_by: createdBy }),
...(updatedBy && { updated_by: updatedBy }),
...(Array.isArray(references) && { references }),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ describe('find', () => {
'typeMigrationVersion',
'managed',
'updated_at',
'updated_by',
'created_at',
'created_by',
'originId',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
createConflictErrorPayload,
createGenericNotFoundErrorPayload,
updateSuccess,
mockTimestampFieldsWithCreated,
} from '../../test_helpers/repository.test.common';

describe('#update', () => {
Expand Down Expand Up @@ -319,7 +320,7 @@ describe('#update', () => {
const expected = {
'index-pattern': { description: 'bar', title: 'foo' },
type: 'index-pattern',
...mockTimestampFields,
...mockTimestampFieldsWithCreated,
};
expect(
(client.create.mock.calls[0][0] as estypes.CreateRequest<SavedObjectsRawDocSource>).body!
Expand Down Expand Up @@ -352,7 +353,7 @@ describe('#update', () => {
multiNamespaceIsolatedType: { description: 'bar', title: 'foo' },
namespaces: ['default'],
type: 'multiNamespaceIsolatedType',
...mockTimestampFields,
...mockTimestampFieldsWithCreated,
};
expect(
(client.create.mock.calls[0][0] as estypes.CreateRequest<SavedObjectsRawDocSource>).body!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const executeUpdate = async <T>(
preflight: preflightHelper,
migration: migrationHelper,
validation: validationHelper,
user: userHelper,
} = helpers;
const { securityExtension } = extensions;
const typeDefinition = registry.getType(type)!;
Expand Down Expand Up @@ -151,6 +152,7 @@ export const executeUpdate = async <T>(
// END ALL PRE_CLIENT CALL CHECKS && MIGRATE EXISTING DOC;

const time = getCurrentTime();
const updatedBy = userHelper.getCurrentUserProfileUid();
let updatedOrCreatedSavedObject: SavedObject<T>;
// `upsert` option set and document was not found -> we need to perform an upsert operation
const shouldPerformUpsert = upsert && docNotFound;
Expand All @@ -176,7 +178,10 @@ export const executeUpdate = async <T>(
attributes: {
...(await encryptionHelper.optionallyEncryptAttributes(type, id, namespace, upsert)),
},
created_at: time,
updated_at: time,
...(updatedBy && { created_by: updatedBy }),
...(updatedBy && { updated_by: updatedBy }),
Dosant marked this conversation as resolved.
Show resolved Hide resolved
...(Array.isArray(references) && { references }),
}) as SavedObjectSanitizedDoc<T>;
validationHelper.validateObjectForCreate(type, migratedUpsert);
Expand Down Expand Up @@ -232,7 +237,10 @@ export const executeUpdate = async <T>(
updatedOrCreatedSavedObject = {
id,
type,
created_at: time,
updated_at: time,
...(updatedBy && { created_by: updatedBy }),
...(updatedBy && { updated_by: updatedBy }),
Dosant marked this conversation as resolved.
Show resolved Hide resolved
version: encodeHitVersion(createDocResponseBody),
namespaces,
...(originId && { originId }),
Expand Down Expand Up @@ -273,6 +281,7 @@ export const executeUpdate = async <T>(
namespaces: savedObjectNamespaces,
attributes: updatedAttributes,
updated_at: time,
updated_by: updatedBy,
...(Array.isArray(references) && { references }),
});

Expand Down Expand Up @@ -336,6 +345,7 @@ export const executeUpdate = async <T>(
id,
type,
updated_at: time,
...(updatedBy && { updated_by: updatedBy }),
version: encodeHitVersion(indexDocResponseBody),
namespaces,
...(originId && { originId }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ describe('#getSavedObjectFromSource', () => {
const updated_at = 'updatedAt';
// eslint-disable-next-line @typescript-eslint/naming-convention
const created_by = 'createdBy';
// eslint-disable-next-line @typescript-eslint/naming-convention
const updated_by = 'updatedBy';
const managed = false;

function createRawDoc(
Expand All @@ -123,6 +125,7 @@ describe('#getSavedObjectFromSource', () => {
originId,
updated_at,
created_by,
updated_by,
...namespaceAttrs,
},
};
Expand All @@ -145,6 +148,7 @@ describe('#getSavedObjectFromSource', () => {
references,
type,
updated_at,
updated_by,
created_by,
version: encodeHitVersion(doc),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export function getSavedObjectFromSource<T>(
updated_at: updatedAt,
created_at: createdAt,
created_by: createdBy,
updated_by: updatedBy,
coreMigrationVersion,
typeMigrationVersion,
managed,
Expand All @@ -136,6 +137,7 @@ export function getSavedObjectFromSource<T>(
...(updatedAt && { updated_at: updatedAt }),
...(createdAt && { created_at: createdAt }),
...(createdBy && { created_by: createdBy }),
...(updatedBy && { updated_by: updatedBy }),
version: encodeHitVersion(doc),
attributes: doc._source[type],
references: doc._source.references || [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,20 @@ describe('SavedObjectsRepository Security Extension', () => {
})
);
});

test(`adds updated_by to the saved object when the current user is available`, async () => {
const profileUid = 'profileUid';
mockSecurityExt.getCurrentUser.mockImplementationOnce(() =>
mockAuthenticatedUser({ profile_uid: profileUid })
);

const result = await updateSuccess(client, repository, registry, type, id, attributes, {
namespace,
});

expect(result).not.toHaveProperty('created_by');
expect(result.updated_by).toBe(profileUid);
});
});

describe('#create', () => {
Expand Down Expand Up @@ -425,7 +439,7 @@ describe('SavedObjectsRepository Security Extension', () => {
);
});

test(`adds created_by to the saved object when the current user is available`, async () => {
test(`adds created_by, updated_by to the saved object when the current user is available`, async () => {
const profileUid = 'profileUid';
mockSecurityExt.getCurrentUser.mockImplementationOnce(() =>
mockAuthenticatedUser({ profile_uid: profileUid })
Expand All @@ -434,14 +448,16 @@ describe('SavedObjectsRepository Security Extension', () => {
namespace,
});
expect(response.created_by).toBe(profileUid);
expect(response.updated_by).toBe(profileUid);
});

test(`keeps created_by empty if the current user is not available`, async () => {
test(`keeps created_by, updated_by empty if the current user is not available`, async () => {
mockSecurityExt.getCurrentUser.mockImplementationOnce(() => null);
const response = await repository.create(MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, attributes, {
namespace,
});
expect(response).not.toHaveProperty('created_by');
expect(response).not.toHaveProperty('updated_by');
});
});

Expand Down Expand Up @@ -1345,21 +1361,27 @@ describe('SavedObjectsRepository Security Extension', () => {
});
});

test(`adds created_by to the saved object when the current user is available`, async () => {
test(`adds created_by, updated_by to the saved object when the current user is available`, async () => {
const profileUid = 'profileUid';
mockSecurityExt.getCurrentUser.mockImplementationOnce(() =>
mockAuthenticatedUser({ profile_uid: profileUid })
);
const response = await bulkCreateSuccess(client, repository, [obj1, obj2], { namespace });
expect(response.saved_objects[0].created_by).toBe(profileUid);
expect(response.saved_objects[1].created_by).toBe(profileUid);

expect(response.saved_objects[0].updated_by).toBe(profileUid);
expect(response.saved_objects[1].updated_by).toBe(profileUid);
});

test(`keeps created_by empty if the current user is not available`, async () => {
test(`keeps created_by, updated_by empty if the current user is not available`, async () => {
mockSecurityExt.getCurrentUser.mockImplementationOnce(() => null);
const response = await bulkCreateSuccess(client, repository, [obj1, obj2], { namespace });
expect(response.saved_objects[0]).not.toHaveProperty('created_by');
expect(response.saved_objects[1]).not.toHaveProperty('created_by');

expect(response.saved_objects[0]).not.toHaveProperty('updated_by');
expect(response.saved_objects[1]).not.toHaveProperty('updated_by');
});
});

Expand Down Expand Up @@ -1512,6 +1534,19 @@ describe('SavedObjectsRepository Security Extension', () => {
expect(typeMap).toBe(authMap);
});
});

test(`adds updated_by to the saved object when the current user is available`, async () => {
const profileUid = 'profileUid';
mockSecurityExt.getCurrentUser.mockImplementationOnce(() =>
mockAuthenticatedUser({ profile_uid: profileUid })
);

const objects = [obj1, obj2];
const result = await bulkUpdateSuccess(client, repository, registry, objects, { namespace });

expect(result.saved_objects[0].updated_by).toBe(profileUid);
expect(result.saved_objects[1].updated_by).toBe(profileUid);
});
});

describe('#bulkDelete', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('getRootFields', () => {
"typeMigrationVersion",
"managed",
"updated_at",
"updated_by",
"created_at",
"created_by",
"originId",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const ROOT_FIELDS = [
'typeMigrationVersion',
'managed',
'updated_at',
'updated_by',
'created_at',
'created_by',
'originId',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,18 @@ describe('#rawToSavedObject', () => {
expect(actual).toHaveProperty('updated_at', now);
});

test('if specified it copies the _source.updated_by property to updated_by', () => {
const updatedBy = 'elastic';
const actual = singleNamespaceSerializer.rawToSavedObject({
_id: 'foo:bar',
_source: {
type: 'foo',
updated_by: updatedBy,
},
});
expect(actual).toHaveProperty('updated_by', updatedBy);
});

test('if specified it copies the _source.created_at property to created_at', () => {
const now = Date();
const actual = singleNamespaceSerializer.rawToSavedObject({
Expand Down Expand Up @@ -341,6 +353,16 @@ describe('#rawToSavedObject', () => {
expect(actual).not.toHaveProperty('created_at');
});

test(`if _source.updated_by is unspecified it doesn't set updated_by`, () => {
const actual = singleNamespaceSerializer.rawToSavedObject({
_id: 'foo:bar',
_source: {
type: 'foo',
},
});
expect(actual).not.toHaveProperty('updated_by');
});

test('if specified it copies the _source.originId property to originId', () => {
const originId = 'baz';
const actual = singleNamespaceSerializer.rawToSavedObject({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export class SavedObjectsSerializer implements ISavedObjectsSerializer {
...(coreMigrationVersion && { coreMigrationVersion }),
...(typeMigrationVersion != null ? { typeMigrationVersion } : {}),
...(_source.updated_at && { updated_at: _source.updated_at }),
...(_source.updated_by && { updated_by: _source.updated_by }),
...(_source.created_at && { created_at: _source.created_at }),
...(_source.created_by && { created_by: _source.created_by }),
...(version && { version }),
Expand All @@ -144,6 +145,7 @@ export class SavedObjectsSerializer implements ISavedObjectsSerializer {
migrationVersion,
// eslint-disable-next-line @typescript-eslint/naming-convention
updated_at,
updated_by: updatedBy,
created_at: createdAt,
created_by: createdBy,
version,
Expand All @@ -164,6 +166,7 @@ export class SavedObjectsSerializer implements ISavedObjectsSerializer {
...(coreMigrationVersion && { coreMigrationVersion }),
...(typeMigrationVersion != null ? { typeMigrationVersion } : {}),
...(updated_at && { updated_at }),
...(updatedBy && { updated_by: updatedBy }),
...(createdAt && { created_at: createdAt }),
...(createdBy && { created_by: createdBy }),
};
Expand Down
Loading