diff --git a/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts
index f66686d53f1cb..3026e2eec4d83 100644
--- a/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts
+++ b/src/core/server/integration_tests/saved_objects/migrations/check_registered_types.test.ts
@@ -85,7 +85,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"event_loop_delays_daily": "d2ed39cf669577d90921c176499908b4943fb7bd",
"exception-list": "fe8cc004fd2742177cdb9300f4a67689463faf9c",
"exception-list-agnostic": "49fae8fcd1967cc4be45ba2a2c66c4afbc1e341b",
- "file": "70c2a768473057157f6ee5d29a436e5288d22ff4",
+ "file": "05c14a75e5e20b12ca514a1d7de231f420facf2c",
"file-upload-usage-collection-telemetry": "8478924cf0057bd90df737155b364f98d05420a5",
"fileShare": "3f88784b041bb8728a7f40763a08981828799a75",
"fleet-fleet-server-host": "f00ca963f1bee868806319789cdc33f1f53a97e2",
diff --git a/src/plugins/files/common/types.ts b/src/plugins/files/common/types.ts
index 48b05076fe02d..247340c583ea6 100644
--- a/src/plugins/files/common/types.ts
+++ b/src/plugins/files/common/types.ts
@@ -66,12 +66,10 @@ export type BaseFileMetadata = {
* ISO string representing the file creation date
*/
created?: string;
-
/**
* Size of the file
*/
size?: number;
-
/**
* Hash of the file's contents
*/
@@ -107,6 +105,25 @@ export type BaseFileMetadata = {
[hashName: string]: string | undefined;
};
+ /**
+ * Data about the user that created the file
+ */
+ user?: {
+ /**
+ * The human-friendly user name of the owner of the file
+ *
+ * @note this field cannot be used to uniquely ID a user. See {@link BaseFileMetadata['user']['id']}.
+ */
+ name?: string;
+ /**
+ * The unique ID of the user who created the file, taken from the user profile
+ * ID.
+ *
+ * See https://www.elastic.co/guide/en/elasticsearch/reference/master/user-profile.html.
+ */
+ id?: string;
+ };
+
/**
* The file extension, for example "jpg", "png", "svg" and so forth
*/
@@ -153,7 +170,7 @@ export type FileMetadata = Required<
FileKind: string;
/**
- * User-defined metadata
+ * User-defined metadata.
*/
Meta?: Meta;
};
@@ -222,6 +239,10 @@ export interface FileJSON {
* See {@link FileStatus} for more details.
*/
status: FileMetadata['Status'];
+ /**
+ * User data associated with this file
+ */
+ user?: FileMetadata['user'];
}
/**
diff --git a/src/plugins/files/public/components/file_picker/file_picker.stories.tsx b/src/plugins/files/public/components/file_picker/file_picker.stories.tsx
index 1d38a9893ed57..2492c366f5d0d 100644
--- a/src/plugins/files/public/components/file_picker/file_picker.stories.tsx
+++ b/src/plugins/files/public/components/file_picker/file_picker.stories.tsx
@@ -9,7 +9,7 @@
import React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { action } from '@storybook/addon-actions';
-import type { FileJSON } from '../../../common';
+import type { FileJSON, FileImageMetadata } from '../../../common';
import { FilesClient, FilesClientResponses } from '../../types';
import { register } from '../stories_shared';
import { base64dLogo } from '../image/image.constants.stories';
@@ -59,7 +59,7 @@ export const Empty = Template.bind({});
const d = new Date();
let id = 0;
-function createFileJSON(file?: Partial): FileJSON {
+function createFileJSON(file?: Partial>): FileJSON {
return {
alt: '',
created: d.toISOString(),
diff --git a/src/plugins/files/server/file/to_json.ts b/src/plugins/files/server/file/to_json.ts
index d1cb00277549a..2d01655c3a4e0 100644
--- a/src/plugins/files/server/file/to_json.ts
+++ b/src/plugins/files/server/file/to_json.ts
@@ -9,18 +9,20 @@
import { pickBy } from 'lodash';
import type { FileMetadata, FileJSON } from '../../common/types';
-export function serializeJSON(attrs: Partial): Partial {
- const { name, mimeType, size, created, updated, fileKind, status, alt, extension, meta } = attrs;
+export function serializeJSON(attrs: Partial): Partial> {
+ const { name, mimeType, size, created, updated, fileKind, status, alt, extension, meta, user } =
+ attrs;
return pickBy(
{
name,
mime_type: mimeType,
size,
+ user,
created,
extension,
Alt: alt,
Status: status,
- Meta: meta as M,
+ Meta: meta,
Updated: updated,
FileKind: fileKind,
},
@@ -28,11 +30,12 @@ export function serializeJSON(attrs: Partial): Partial(id: string, attrs: FileMetadata): FileJSON {
+export function toJSON(id: string, attrs: FileMetadata): FileJSON {
const {
name,
mime_type: mimeType,
size,
+ user,
created,
Updated,
FileKind,
@@ -44,6 +47,7 @@ export function toJSON(id: string, attrs: FileMetadata): FileJSON>(
{
id,
+ user,
name,
mimeType,
size,
@@ -51,7 +55,7 @@ export function toJSON(id: string, attrs: FileMetadata): FileJSON {
/**
* Unique file ID
*/
@@ -35,7 +35,7 @@ export interface CreateArgs {
/**
* The file's metadata
*/
- metadata: Omit;
+ metadata: Omit, 'fileKind'>;
}
/**
@@ -71,7 +71,7 @@ export interface FileClient {
*
* @param arg - Arg to create a file.
* */
- create(arg: CreateArgs): Promise>;
+ create(arg: CreateArgs): Promise>;
/**
* See {@link FileMetadataClient.get}
diff --git a/src/plugins/files/server/file_service/file_action_types.ts b/src/plugins/files/server/file_service/file_action_types.ts
index dfe5ed2f0ba9b..58aaef579f14b 100644
--- a/src/plugins/files/server/file_service/file_action_types.ts
+++ b/src/plugins/files/server/file_service/file_action_types.ts
@@ -6,7 +6,7 @@
* Side Public License, v 1.
*/
-import type { Pagination, UpdatableFileMetadata } from '../../common/types';
+import type { FileMetadata, Pagination, UpdatableFileMetadata } from '../../common/types';
/**
* Arguments to create a new file.
@@ -32,6 +32,10 @@ export interface CreateFileArgs {
* The MIME type of the file.
*/
mime?: string;
+ /**
+ * User data associated with this file
+ */
+ user?: FileMetadata['user'];
}
/**
diff --git a/src/plugins/files/server/plugin.ts b/src/plugins/files/server/plugin.ts
index 95408f1fc18c5..7edd42985b616 100755
--- a/src/plugins/files/server/plugin.ts
+++ b/src/plugins/files/server/plugin.ts
@@ -23,7 +23,12 @@ import {
import { BlobStorageService } from './blob_storage_service';
import { FileServiceFactory } from './file_service';
-import type { FilesPluginSetupDependencies, FilesSetup, FilesStart } from './types';
+import type {
+ FilesPluginSetupDependencies,
+ FilesPluginStartDependencies,
+ FilesSetup,
+ FilesStart,
+} from './types';
import type { FilesRequestHandlerContext, FilesRouter } from './routes/types';
import { registerRoutes, registerFileKindRoutes } from './routes';
@@ -33,6 +38,7 @@ export class FilesPlugin implements Plugin {
return {
+ security: this.securityStart,
fileService: {
asCurrentUser: () => this.fileServiceFactory!.asScoped(req),
asInternalUser: () => this.fileServiceFactory!.asInternal(),
@@ -81,8 +88,9 @@ export class FilesPlugin implements Plugin = CreateRouteDefinition }>;
export const handler: CreateHandler = async ({ fileKind, files }, req, res) => {
- const { fileService } = await files;
+ const { fileService, security } = await files;
const {
body: { name, alt, meta, mimeType },
} = req;
- const file = await fileService
- .asCurrentUser()
- .create({ fileKind, name, alt, meta, mime: mimeType });
+ const user = security?.authc.getCurrentUser(req);
+ const file = await fileService.asCurrentUser().create({
+ fileKind,
+ name,
+ alt,
+ meta,
+ user: user ? { name: user.username, id: user.profile_uid } : undefined,
+ mime: mimeType,
+ });
const body: Endpoint['output'] = {
file: file.toJSON(),
};
diff --git a/src/plugins/files/server/routes/file_kind/integration_tests/file_kind_http.test.ts b/src/plugins/files/server/routes/file_kind/integration_tests/file_kind_http.test.ts
index 58a6d195a0bdb..c53783e28602d 100644
--- a/src/plugins/files/server/routes/file_kind/integration_tests/file_kind_http.test.ts
+++ b/src/plugins/files/server/routes/file_kind/integration_tests/file_kind_http.test.ts
@@ -40,6 +40,9 @@ describe('File kind HTTP API', () => {
mimeType: 'image/png',
extension: 'png',
meta: {},
+ user: {
+ name: expect.any(String),
+ },
alt: 'a picture of my dog',
});
});
@@ -81,7 +84,7 @@ describe('File kind HTTP API', () => {
} = await request.get(root, `/api/files/files/${fileKind}/${id}`).expect(200);
expect(file.name).toBe('acoolfilename');
- const updatedFileAttrs: UpdatableFileMetadata = {
+ const updatedFileAttrs: UpdatableFileMetadata<{ something: string }> = {
name: 'anothercoolfilename',
alt: 'a picture of my cat',
meta: {
@@ -89,20 +92,24 @@ describe('File kind HTTP API', () => {
},
};
- const {
- body: { file: updatedFile },
- } = await request
- .patch(root, `/api/files/files/${fileKind}/${id}`)
- .send(updatedFileAttrs)
- .expect(200);
+ {
+ const {
+ body: { file: updatedFile },
+ } = await request
+ .patch(root, `/api/files/files/${fileKind}/${id}`)
+ .send(updatedFileAttrs)
+ .expect(200);
- expect(updatedFile).toEqual(expect.objectContaining(updatedFileAttrs));
+ expect(updatedFile).toMatchObject(updatedFileAttrs);
+ }
- const {
- body: { file: file2 },
- } = await request.get(root, `/api/files/files/${fileKind}/${id}`).expect(200);
+ {
+ const {
+ body: { file: updatedFile },
+ } = await request.get(root, `/api/files/files/${fileKind}/${id}`).expect(200);
- expect(file2).toEqual(expect.objectContaining(updatedFileAttrs));
+ expect(updatedFile).toMatchObject(updatedFileAttrs);
+ }
});
test('list current files', async () => {
diff --git a/src/plugins/files/server/routes/types.ts b/src/plugins/files/server/routes/types.ts
index cfe47a7652359..1c803c71b78c0 100644
--- a/src/plugins/files/server/routes/types.ts
+++ b/src/plugins/files/server/routes/types.ts
@@ -15,12 +15,14 @@ import type {
IKibanaResponse,
Logger,
} from '@kbn/core/server';
+import type { SecurityPluginStart } from '@kbn/security-plugin/server';
import type { FileServiceStart } from '../file_service';
import { Counters } from '../usage';
import { AnyEndpoint } from './api_routes';
export interface FilesRequestHandlerContext extends RequestHandlerContext {
files: Promise<{
+ security?: SecurityPluginStart;
fileService: {
asCurrentUser: () => FileServiceStart;
asInternalUser: () => FileServiceStart;
diff --git a/src/plugins/files/server/saved_objects/file.ts b/src/plugins/files/server/saved_objects/file.ts
index 54d7fb57a3a07..c4259e6d679c3 100644
--- a/src/plugins/files/server/saved_objects/file.ts
+++ b/src/plugins/files/server/saved_objects/file.ts
@@ -25,6 +25,9 @@ const properties: Properties = {
name: {
type: 'text',
},
+ user: {
+ type: 'flattened',
+ },
Status: {
type: 'keyword',
},
diff --git a/src/plugins/files/server/types.ts b/src/plugins/files/server/types.ts
index eb5cbff029678..25f6861d451d5 100644
--- a/src/plugins/files/server/types.ts
+++ b/src/plugins/files/server/types.ts
@@ -5,8 +5,7 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
-
-import type { SecurityPluginSetup } from '@kbn/security-plugin/server';
+import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { FileKind } from '../common';
import { FileServiceFactory } from './file_service/file_service_factory';
@@ -31,6 +30,10 @@ export interface FilesPluginSetupDependencies {
usageCollection?: UsageCollectionSetup;
}
+export interface FilesPluginStartDependencies {
+ security?: SecurityPluginStart;
+}
+
/**
* Files plugin start contract
*/