diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.customname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.customlabel.md similarity index 58% rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.customname.md rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.customlabel.md index b30201f9e3991..6a997d517e98d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.customname.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.customlabel.md @@ -1,11 +1,11 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [customName](./kibana-plugin-plugins-data-public.ifieldtype.customname.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) > [customLabel](./kibana-plugin-plugins-data-public.ifieldtype.customlabel.md) -## IFieldType.customName property +## IFieldType.customLabel property Signature: ```typescript -customName?: string; +customLabel?: string; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md index 6f3876ff82f04..2b3d3df1ec8d0 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldtype.md @@ -16,7 +16,7 @@ export interface IFieldType | --- | --- | --- | | [aggregatable](./kibana-plugin-plugins-data-public.ifieldtype.aggregatable.md) | boolean | | | [count](./kibana-plugin-plugins-data-public.ifieldtype.count.md) | number | | -| [customName](./kibana-plugin-plugins-data-public.ifieldtype.customname.md) | string | | +| [customLabel](./kibana-plugin-plugins-data-public.ifieldtype.customlabel.md) | string | | | [displayName](./kibana-plugin-plugins-data-public.ifieldtype.displayname.md) | string | | | [esTypes](./kibana-plugin-plugins-data-public.ifieldtype.estypes.md) | string[] | | | [filterable](./kibana-plugin-plugins-data-public.ifieldtype.filterable.md) | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getfieldattrs.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getfieldattrs.md index f81edf4b94b42..0c1fbe7d0d1b6 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getfieldattrs.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getfieldattrs.md @@ -9,7 +9,7 @@ ```typescript getFieldAttrs: () => { [x: string]: { - customName: string; + customLabel: string; }; }; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md index 1228bf7adc2ef..3383116f404b2 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md @@ -27,7 +27,7 @@ export declare class IndexPattern implements IIndexPattern | [flattenHit](./kibana-plugin-plugins-data-public.indexpattern.flattenhit.md) | | (hit: Record<string, any>, deep?: boolean) => Record<string, any> | | | [formatField](./kibana-plugin-plugins-data-public.indexpattern.formatfield.md) | | FormatFieldFn | | | [formatHit](./kibana-plugin-plugins-data-public.indexpattern.formathit.md) | | {
(hit: Record<string, any>, type?: string): any;
formatField: FormatFieldFn;
} | | -| [getFieldAttrs](./kibana-plugin-plugins-data-public.indexpattern.getfieldattrs.md) | | () => {
[x: string]: {
customName: string;
};
} | | +| [getFieldAttrs](./kibana-plugin-plugins-data-public.indexpattern.getfieldattrs.md) | | () => {
[x: string]: {
customLabel: string;
};
} | | | [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-public.indexpattern.getoriginalsavedobjectbody.md) | | () => {
fieldAttrs?: string | undefined;
title?: string | undefined;
timeFieldName?: string | undefined;
intervalName?: string | undefined;
fields?: string | undefined;
sourceFilters?: string | undefined;
fieldFormatMap?: string | undefined;
typeMeta?: string | undefined;
type?: string | undefined;
} | Get last saved saved object fields | | [id](./kibana-plugin-plugins-data-public.indexpattern.id.md) | | string | | | [intervalName](./kibana-plugin-plugins-data-public.indexpattern.intervalname.md) | | string | undefined | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.customname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.customlabel.md similarity index 59% rename from docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.customname.md rename to docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.customlabel.md index ef8f9f1d31e4f..8d9c1b7a1161e 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.customname.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.customlabel.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) > [customName](./kibana-plugin-plugins-data-public.indexpatternfield.customname.md) +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternField](./kibana-plugin-plugins-data-public.indexpatternfield.md) > [customLabel](./kibana-plugin-plugins-data-public.indexpatternfield.customlabel.md) -## IndexPatternField.customName property +## IndexPatternField.customLabel property Signature: ```typescript -get customName(): string | undefined; +get customLabel(): string | undefined; -set customName(label: string | undefined); +set customLabel(customLabel: string | undefined); ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md index ef99b4353a70b..caf7d374161dd 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md @@ -23,7 +23,7 @@ export declare class IndexPatternField implements IFieldType | [aggregatable](./kibana-plugin-plugins-data-public.indexpatternfield.aggregatable.md) | | boolean | | | [conflictDescriptions](./kibana-plugin-plugins-data-public.indexpatternfield.conflictdescriptions.md) | | Record<string, string[]> | undefined | Description of field type conflicts across different indices in the same index pattern | | [count](./kibana-plugin-plugins-data-public.indexpatternfield.count.md) | | number | Count is used for field popularity | -| [customName](./kibana-plugin-plugins-data-public.indexpatternfield.customname.md) | | string | undefined | | +| [customLabel](./kibana-plugin-plugins-data-public.indexpatternfield.customlabel.md) | | string | undefined | | | [displayName](./kibana-plugin-plugins-data-public.indexpatternfield.displayname.md) | | string | | | [esTypes](./kibana-plugin-plugins-data-public.indexpatternfield.estypes.md) | | string[] | undefined | | | [filterable](./kibana-plugin-plugins-data-public.indexpatternfield.filterable.md) | | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md index c7237701ae49d..f0600dd20658a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md @@ -20,7 +20,7 @@ toJSON(): { aggregatable: boolean; readFromDocValues: boolean; subType: import("../types").IFieldSubType | undefined; - customName: string | undefined; + customLabel: string | undefined; }; ``` Returns: @@ -38,6 +38,6 @@ toJSON(): { aggregatable: boolean; readFromDocValues: boolean; subType: import("../types").IFieldSubType | undefined; - customName: string | undefined; + customLabel: string | undefined; }` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.customname.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.customlabel.md similarity index 58% rename from docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.customname.md rename to docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.customlabel.md index f5fbc084237f2..8d4868cb8e9ab 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.customname.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.customlabel.md @@ -1,11 +1,11 @@ -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [customName](./kibana-plugin-plugins-data-server.ifieldtype.customname.md) +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) > [customLabel](./kibana-plugin-plugins-data-server.ifieldtype.customlabel.md) -## IFieldType.customName property +## IFieldType.customLabel property Signature: ```typescript -customName?: string; +customLabel?: string; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md index 638700b1d24f8..48836a1b620b8 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldtype.md @@ -16,7 +16,7 @@ export interface IFieldType | --- | --- | --- | | [aggregatable](./kibana-plugin-plugins-data-server.ifieldtype.aggregatable.md) | boolean | | | [count](./kibana-plugin-plugins-data-server.ifieldtype.count.md) | number | | -| [customName](./kibana-plugin-plugins-data-server.ifieldtype.customname.md) | string | | +| [customLabel](./kibana-plugin-plugins-data-server.ifieldtype.customlabel.md) | string | | | [displayName](./kibana-plugin-plugins-data-server.ifieldtype.displayname.md) | string | | | [esTypes](./kibana-plugin-plugins-data-server.ifieldtype.estypes.md) | string[] | | | [filterable](./kibana-plugin-plugins-data-server.ifieldtype.filterable.md) | boolean | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldattrs.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldattrs.md index 80dd329232ed8..b1e38258353c3 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldattrs.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getfieldattrs.md @@ -9,7 +9,7 @@ ```typescript getFieldAttrs: () => { [x: string]: { - customName: string; + customLabel: string; }; }; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md index 3d2b021b29515..5103af52f1b43 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md @@ -27,7 +27,7 @@ export declare class IndexPattern implements IIndexPattern | [flattenHit](./kibana-plugin-plugins-data-server.indexpattern.flattenhit.md) | | (hit: Record<string, any>, deep?: boolean) => Record<string, any> | | | [formatField](./kibana-plugin-plugins-data-server.indexpattern.formatfield.md) | | FormatFieldFn | | | [formatHit](./kibana-plugin-plugins-data-server.indexpattern.formathit.md) | | {
(hit: Record<string, any>, type?: string): any;
formatField: FormatFieldFn;
} | | -| [getFieldAttrs](./kibana-plugin-plugins-data-server.indexpattern.getfieldattrs.md) | | () => {
[x: string]: {
customName: string;
};
} | | +| [getFieldAttrs](./kibana-plugin-plugins-data-server.indexpattern.getfieldattrs.md) | | () => {
[x: string]: {
customLabel: string;
};
} | | | [getOriginalSavedObjectBody](./kibana-plugin-plugins-data-server.indexpattern.getoriginalsavedobjectbody.md) | | () => {
fieldAttrs?: string | undefined;
title?: string | undefined;
timeFieldName?: string | undefined;
intervalName?: string | undefined;
fields?: string | undefined;
sourceFilters?: string | undefined;
fieldFormatMap?: string | undefined;
typeMeta?: string | undefined;
type?: string | undefined;
} | Get last saved saved object fields | | [id](./kibana-plugin-plugins-data-server.indexpattern.id.md) | | string | | | [intervalName](./kibana-plugin-plugins-data-server.indexpattern.intervalname.md) | | string | undefined | | diff --git a/package.json b/package.json index 23f7a0b430654..d33135d37e1e6 100644 --- a/package.json +++ b/package.json @@ -723,7 +723,7 @@ "less": "npm:@elastic/less@2.7.3-kibana", "license-checker": "^16.0.0", "listr": "^0.14.1", - "lmdb-store": "^0.8.15", + "lmdb-store": "^0.6.10", "load-grunt-config": "^3.0.1", "loader-utils": "^1.2.3", "log-symbols": "^2.2.0", diff --git a/packages/kbn-optimizer/src/node/cache.ts b/packages/kbn-optimizer/src/node/cache.ts index a73dba5b16469..e918bae86c835 100644 --- a/packages/kbn-optimizer/src/node/cache.ts +++ b/packages/kbn-optimizer/src/node/cache.ts @@ -18,11 +18,20 @@ */ import Path from 'path'; +import Fs from 'fs'; +// @ts-expect-error no types available import * as LmdbStore from 'lmdb-store'; import { REPO_ROOT, UPSTREAM_BRANCH } from '@kbn/dev-utils'; -const CACHE_DIR = Path.resolve(REPO_ROOT, 'data/node_auto_transpilation_cache', UPSTREAM_BRANCH); +const LMDB_PKG = JSON.parse( + Fs.readFileSync(Path.resolve(REPO_ROOT, 'node_modules/lmdb-store/package.json'), 'utf8') +); +const CACHE_DIR = Path.resolve( + REPO_ROOT, + `data/node_auto_transpilation_cache/lmdb-${LMDB_PKG.version}/${UPSTREAM_BRANCH}` +); + const reportError = () => { // right now I'm not sure we need to worry about errors, the cache isn't actually // necessary, and if the cache is broken it should just rebuild on the next restart @@ -36,11 +45,30 @@ const MINUTE = 1000 * 60; const HOUR = MINUTE * 60; const DAY = HOUR * 24; +interface Lmdb { + name: string; + get(key: string): T | undefined; + put(key: string, value: T, version?: number, ifVersion?: number): Promise; + remove(key: string, ifVersion?: number): Promise; + removeSync(key: string): void; + openDB(options: { + name: string; + encoding: 'msgpack' | 'string' | 'json' | 'binary'; + }): Lmdb; + getRange(options?: { + start?: T; + end?: T; + reverse?: boolean; + limit?: number; + versions?: boolean; + }): Iterable<{ key: string; value: T }>; +} + export class Cache { - private readonly codes: LmdbStore.RootDatabase; - private readonly atimes: LmdbStore.Database; - private readonly mtimes: LmdbStore.Database; - private readonly sourceMaps: LmdbStore.Database; + private readonly codes: Lmdb; + private readonly atimes: Lmdb; + private readonly mtimes: Lmdb; + private readonly sourceMaps: Lmdb; private readonly prefix: string; constructor(config: { prefix: string }) { @@ -77,7 +105,7 @@ export class Cache { } getMtime(path: string) { - return this.safeGet(this.mtimes, this.getKey(path)); + return this.safeGet(this.mtimes, this.getKey(path)); } getCode(path: string) { @@ -88,11 +116,11 @@ export class Cache { // touched in a long time (currently 30 days) this.atimes.put(key, GLOBAL_ATIME).catch(reportError); - return this.safeGet(this.codes, key); + return this.safeGet(this.codes, key); } getSourceMap(path: string) { - return this.safeGet(this.sourceMaps, this.getKey(path)); + return this.safeGet(this.sourceMaps, this.getKey(path)); } update(path: string, file: { mtime: string; code: string; map: any }) { @@ -110,11 +138,13 @@ export class Cache { return `${this.prefix}${path}`; } - private safeGet(db: LmdbStore.Database, key: string) { + private safeGet(db: Lmdb, key: string) { try { - return db.get(key) as V | undefined; + return db.get(key); } catch (error) { - // get errors indicate that a key value is corrupt in some way, so remove it + process.stderr.write( + `failed to read node transpilation [${db.name}] cache for [${key}]: ${error.stack}\n` + ); db.removeSync(key); } } @@ -124,13 +154,12 @@ export class Cache { const ATIME_LIMIT = Date.now() - 30 * DAY; const BATCH_SIZE = 1000; - const validKeys: LmdbStore.Key[] = []; - const invalidKeys: LmdbStore.Key[] = []; + const validKeys: string[] = []; + const invalidKeys: string[] = []; - // @ts-expect-error See https://github.com/DoctorEvidence/lmdb-store/pull/18 for (const { key, value } of this.atimes.getRange()) { - const atime = parseInt(`${value}`, 10); - if (Number.isNaN(atime) || atime < ATIME_LIMIT) { + const atime = parseInt(value, 10); + if (atime < ATIME_LIMIT) { invalidKeys.push(key); } else { validKeys.push(key); diff --git a/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap b/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap index afaa2d00d8cfd..3e09fa449a1aa 100644 --- a/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap +++ b/src/plugins/data/common/index_patterns/fields/__snapshots__/index_pattern_field.test.ts.snap @@ -47,7 +47,7 @@ Object { ], }, "count": 1, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "text", ], diff --git a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts index 850c5a312fda1..4dd2d29f38e9f 100644 --- a/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts +++ b/src/plugins/data/common/index_patterns/fields/index_pattern_field.ts @@ -68,12 +68,12 @@ export class IndexPatternField implements IFieldType { this.spec.lang = lang; } - public get customName() { - return this.spec.customName; + public get customLabel() { + return this.spec.customLabel; } - public set customName(label) { - this.spec.customName = label; + public set customLabel(customLabel) { + this.spec.customLabel = customLabel; } /** @@ -93,8 +93,8 @@ export class IndexPatternField implements IFieldType { } public get displayName(): string { - return this.spec.customName - ? this.spec.customName + return this.spec.customLabel + ? this.spec.customLabel : this.spec.shortDotsEnable ? shortenDottedString(this.spec.name) : this.spec.name; @@ -163,7 +163,7 @@ export class IndexPatternField implements IFieldType { aggregatable: this.aggregatable, readFromDocValues: this.readFromDocValues, subType: this.subType, - customName: this.customName, + customLabel: this.customLabel, }; } @@ -186,7 +186,7 @@ export class IndexPatternField implements IFieldType { readFromDocValues: this.readFromDocValues, subType: this.subType, format: getFormatterForField ? getFormatterForField(this).toJSON() : undefined, - customName: this.customName, + customLabel: this.customLabel, shortDotsEnable: this.spec.shortDotsEnable, }; } diff --git a/src/plugins/data/common/index_patterns/fields/types.ts b/src/plugins/data/common/index_patterns/fields/types.ts index 86c22b0116ead..1c70a2e884025 100644 --- a/src/plugins/data/common/index_patterns/fields/types.ts +++ b/src/plugins/data/common/index_patterns/fields/types.ts @@ -37,7 +37,7 @@ export interface IFieldType { scripted?: boolean; subType?: IFieldSubType; displayName?: string; - customName?: string; + customLabel?: string; format?: any; toSpec?: (options?: { getFormatterForField?: IndexPattern['getFormatterForField'] }) => FieldSpec; } diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap index 2741322acec0f..e2bdb0009c20a 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap @@ -9,7 +9,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "keyword", ], @@ -33,7 +33,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 30, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "date", ], @@ -57,7 +57,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "_id", ], @@ -81,7 +81,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "_source", ], @@ -105,7 +105,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "_type", ], @@ -129,7 +129,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "geo_shape", ], @@ -153,7 +153,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 10, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "long", ], @@ -177,7 +177,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "conflict", ], @@ -201,7 +201,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "text", ], @@ -225,7 +225,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "keyword", ], @@ -253,7 +253,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "geo_point", ], @@ -277,7 +277,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "keyword", ], @@ -301,7 +301,7 @@ Object { "aggregatable": false, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "murmur3", ], @@ -325,7 +325,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "ip", ], @@ -349,7 +349,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "text", ], @@ -373,7 +373,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "keyword", ], @@ -401,7 +401,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "text", ], @@ -425,7 +425,7 @@ Object { "aggregatable": false, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "text", ], @@ -449,7 +449,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "integer", ], @@ -473,7 +473,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "geo_point", ], @@ -497,7 +497,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "attachment", ], @@ -521,7 +521,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "date", ], @@ -545,7 +545,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "murmur3", ], @@ -569,7 +569,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "long", ], @@ -593,7 +593,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "text", ], @@ -617,7 +617,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 20, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "boolean", ], @@ -641,7 +641,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 30, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "date", ], @@ -665,7 +665,7 @@ Object { "aggregatable": true, "conflictDescriptions": undefined, "count": 0, - "customName": undefined, + "customLabel": undefined, "esTypes": Array [ "date", ], diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index c3a0c98745e21..47ad5860801bc 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -135,8 +135,8 @@ export class IndexPattern implements IIndexPattern { const newFieldAttrs = { ...this.fieldAttrs }; this.fields.forEach((field) => { - if (field.customName) { - newFieldAttrs[field.name] = { customName: field.customName }; + if (field.customLabel) { + newFieldAttrs[field.name] = { customLabel: field.customLabel }; } else { delete newFieldAttrs[field.name]; } diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index d51de220111e3..82c8cf4abc5ac 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -309,7 +309,7 @@ export class IndexPatternsService { */ fieldArrayToMap = (fields: FieldSpec[], fieldAttrs?: FieldAttrs) => fields.reduce((collector, field) => { - collector[field.name] = { ...field, customName: fieldAttrs?.[field.name]?.customName }; + collector[field.name] = { ...field, customLabel: fieldAttrs?.[field.name]?.customLabel }; return collector; }, {}); diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index 22c400562f6d4..28b077f4bfdf3 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -52,7 +52,7 @@ export interface IndexPatternAttributes { } export interface FieldAttrs { - [key: string]: { customName: string }; + [key: string]: { customLabel: string }; } export type OnNotification = (toastInputFields: ToastInputFields) => void; @@ -169,7 +169,7 @@ export interface FieldSpec { readFromDocValues?: boolean; subType?: IFieldSubType; indexed?: boolean; - customName?: string; + customLabel?: string; // not persisted shortDotsEnable?: boolean; } diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 6c4609e5506c2..fc9b8d4839ea3 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -978,7 +978,7 @@ export interface IFieldType { // (undocumented) count?: number; // (undocumented) - customName?: string; + customLabel?: string; // (undocumented) displayName?: string; // (undocumented) @@ -1152,7 +1152,7 @@ export class IndexPattern implements IIndexPattern { // (undocumented) getFieldAttrs: () => { [x: string]: { - customName: string; + customLabel: string; }; }; // (undocumented) @@ -1259,8 +1259,8 @@ export class IndexPatternField implements IFieldType { get count(): number; set count(count: number); // (undocumented) - get customName(): string | undefined; - set customName(label: string | undefined); + get customLabel(): string | undefined; + set customLabel(customLabel: string | undefined); // (undocumented) get displayName(): string; // (undocumented) @@ -1299,7 +1299,7 @@ export class IndexPatternField implements IFieldType { aggregatable: boolean; readFromDocValues: boolean; subType: import("../types").IFieldSubType | undefined; - customName: string | undefined; + customLabel: string | undefined; }; // (undocumented) toSpec({ getFormatterForField, }?: { diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 8d1699c4ad5ed..47e17c26398d3 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -507,7 +507,7 @@ export interface IFieldType { // (undocumented) count?: number; // (undocumented) - customName?: string; + customLabel?: string; // (undocumented) displayName?: string; // (undocumented) @@ -612,7 +612,7 @@ export class IndexPattern implements IIndexPattern { // (undocumented) getFieldAttrs: () => { [x: string]: { - customName: string; + customLabel: string; }; }; // (undocumented) diff --git a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx index 4b63eb5c56fd1..8dd95adf00cc8 100644 --- a/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx +++ b/src/plugins/index_pattern_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx @@ -151,9 +151,9 @@ const editDescription = i18n.translate( { defaultMessage: 'Edit' } ); -const customNameDescription = i18n.translate( - 'indexPatternManagement.editIndexPattern.fields.table.customNameTooltip', - { defaultMessage: 'A custom name for the field.' } +const labelDescription = i18n.translate( + 'indexPatternManagement.editIndexPattern.fields.table.customLabelTooltip', + { defaultMessage: 'A custom label for the field.' } ); interface IndexedFieldProps { @@ -197,11 +197,11 @@ export class Table extends PureComponent { /> ) : null} - {field.customName && field.customName !== field.name ? ( + {field.customLabel && field.customLabel !== field.name ? (
- + - {field.customName} + {field.customLabel}
diff --git a/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap b/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap index babfbbfc2a763..29cbec38a5982 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap +++ b/src/plugins/index_pattern_management/public/components/field_editor/__snapshots__/field_editor.test.tsx.snap @@ -54,15 +54,15 @@ exports[`FieldEditor should render create new scripted field correctly 1`] = ` } - label="Custom name" + label="Custom label" > @@ -294,15 +294,15 @@ exports[`FieldEditor should render edit scripted field correctly 1`] = ` } - label="Custom name" + label="Custom label" > } - label="Custom name" + label="Custom label" > } - label="Custom name" + label="Custom label" > } - label="Custom name" + label="Custom label" > { expect(component).toMatchSnapshot(); }); - it('should display and update a customName correctly', async () => { + it('should display and update a custom label correctly', async () => { let testField = ({ name: 'test', format: new Format(), lang: undefined, type: 'string', - customName: 'Test', + customLabel: 'Test', } as unknown) as IndexPatternField; fieldList.push(testField); indexPattern.fields.getByName = (name) => { @@ -219,14 +219,14 @@ describe('FieldEditor', () => { await new Promise((resolve) => process.nextTick(resolve)); component.update(); - const input = findTestSubject(component, 'editorFieldCustomName'); + const input = findTestSubject(component, 'editorFieldCustomLabel'); expect(input.props().value).toBe('Test'); input.simulate('change', { target: { value: 'new Test' } }); const saveBtn = findTestSubject(component, 'fieldSaveButton'); await saveBtn.simulate('click'); await new Promise((resolve) => process.nextTick(resolve)); - expect(testField.customName).toEqual('new Test'); + expect(testField.customLabel).toEqual('new Test'); }); it('should show deprecated lang warning', async () => { diff --git a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx index 97d30d88e018c..29a87a65fdff7 100644 --- a/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx +++ b/src/plugins/index_pattern_management/public/components/field_editor/field_editor.tsx @@ -126,7 +126,7 @@ export interface FieldEditorState { errors?: string[]; format: any; spec: IndexPatternField['spec']; - customName: string; + customLabel: string; } export interface FieldEdiorProps { @@ -167,7 +167,7 @@ export class FieldEditor extends PureComponent } > { - this.setState({ customName: e.target.value }); + this.setState({ customLabel: e.target.value }); }} /> @@ -802,7 +802,7 @@ export class FieldEditor extends PureComponent { const field = this.state.spec; const { indexPattern } = this.props; - const { fieldFormatId, fieldFormatParams, customName } = this.state; + const { fieldFormatId, fieldFormatParams, customLabel } = this.state; if (field.scripted) { this.setState({ @@ -843,8 +843,8 @@ export class FieldEditor extends PureComponent {this.renderScriptingPanels()} {this.renderName()} - {this.renderCustomName()} + {this.renderCustomLabel()} {this.renderLanguage()} {this.renderType()} {this.renderTypeConflict()} diff --git a/test/functional/fixtures/es_archiver/discover/data.json b/test/functional/fixtures/es_archiver/discover/data.json index 0f9820a6c2f6e..0f2edc8c510c3 100644 --- a/test/functional/fixtures/es_archiver/discover/data.json +++ b/test/functional/fixtures/es_archiver/discover/data.json @@ -8,7 +8,7 @@ "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"@message\"}}},{\"name\":\"@tags\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"@tags\"}}},{\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"agent\"}}},{\"name\":\"bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"extension\"}}},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"headings\"}}},{\"name\":\"host\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"host\"}}},{\"name\":\"id\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"index\"}}},{\"name\":\"ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"links\"}}},{\"name\":\"machine.os\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"machine.os\"}}},{\"name\":\"machine.ram\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"esTypes\":[\"double\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nestedField.child\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"nested\":{\"path\":\"nestedField\"}}},{\"name\":\"phpmemory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.article:section\"}}},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.article:tag\"}}},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:description\"}}},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:image\"}}},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:image:height\"}}},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:image:width\"}}},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:site_name\"}}},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:title\"}}},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:type\"}}},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.og:url\"}}},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:card\"}}},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:description\"}}},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:image\"}}},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:site\"}}},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.twitter:title\"}}},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"relatedContent.url\"}}},{\"name\":\"request\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"request\"}}},{\"name\":\"response\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"response\"}}},{\"name\":\"spaces\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"spaces\"}}},{\"name\":\"type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"url\"}}},{\"name\":\"utc_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"esTypes\":[\"text\"],\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"xss\"}}}]", "timeFieldName": "@timestamp", "title": "logstash-*", - "fieldAttrs": "{\"referer\":{\"customName\":\"Referer custom\"}}" + "fieldAttrs": "{\"referer\":{\"customLabel\":\"Referer custom\"}}" }, "type": "index-pattern" } diff --git a/test/functional/fixtures/es_archiver/visualize/data.json b/test/functional/fixtures/es_archiver/visualize/data.json index c57cdb40ae952..56397351562de 100644 --- a/test/functional/fixtures/es_archiver/visualize/data.json +++ b/test/functional/fixtures/es_archiver/visualize/data.json @@ -9,7 +9,7 @@ "fields": "[{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":true,\"doc_values\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"indexed\":true,\"analyzed\":false,\"doc_values\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"indexed\":false,\"analyzed\":false,\"doc_values\":false}]", "timeFieldName": "@timestamp", "title": "logstash-*", - "fieldAttrs": "{\"utc_time\":{\"customName\":\"UTC time\"}}" + "fieldAttrs": "{\"utc_time\":{\"customLabel\":\"UTC time\"}}" }, "type": "index-pattern" } diff --git a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts index e97b37f16faf0..c08ff9449d151 100644 --- a/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerts/server/alerts_client/alerts_client.ts @@ -228,14 +228,17 @@ export class AlertsClient { this.validateActions(alertType, data.actions); + const createTime = Date.now(); const { references, actions } = await this.denormalizeActions(data.actions); + const rawAlert: RawAlert = { ...data, ...this.apiKeyAsAlertAttributes(createdAPIKey, username), actions, createdBy: username, updatedBy: username, - createdAt: new Date().toISOString(), + createdAt: new Date(createTime).toISOString(), + updatedAt: new Date(createTime).toISOString(), params: validatedAlertTypeParams as RawAlert['params'], muteAll: false, mutedInstanceIds: [], @@ -289,12 +292,7 @@ export class AlertsClient { }); createdAlert.attributes.scheduledTaskId = scheduledTask.id; } - return this.getAlertFromRaw( - createdAlert.id, - createdAlert.attributes, - createdAlert.updated_at, - references - ); + return this.getAlertFromRaw(createdAlert.id, createdAlert.attributes, references); } public async get({ id }: { id: string }): Promise { @@ -304,7 +302,7 @@ export class AlertsClient { result.attributes.consumer, ReadOperations.Get ); - return this.getAlertFromRaw(result.id, result.attributes, result.updated_at, result.references); + return this.getAlertFromRaw(result.id, result.attributes, result.references); } public async getAlertState({ id }: { id: string }): Promise { @@ -393,13 +391,11 @@ export class AlertsClient { type: 'alert', }); - // eslint-disable-next-line @typescript-eslint/naming-convention - const authorizedData = data.map(({ id, attributes, updated_at, references }) => { + const authorizedData = data.map(({ id, attributes, references }) => { ensureAlertTypeIsAuthorized(attributes.alertTypeId, attributes.consumer); return this.getAlertFromRaw( id, fields ? (pick(attributes, fields) as RawAlert) : attributes, - updated_at, references ); }); @@ -585,6 +581,7 @@ export class AlertsClient { params: validatedAlertTypeParams as RawAlert['params'], actions, updatedBy: username, + updatedAt: new Date().toISOString(), }); try { updatedObject = await this.unsecuredSavedObjectsClient.create( @@ -607,12 +604,7 @@ export class AlertsClient { throw e; } - return this.getPartialAlertFromRaw( - id, - updatedObject.attributes, - updatedObject.updated_at, - updatedObject.references - ); + return this.getPartialAlertFromRaw(id, updatedObject.attributes, updatedObject.references); } private apiKeyAsAlertAttributes( @@ -677,6 +669,7 @@ export class AlertsClient { await this.createAPIKey(this.generateAPIKeyName(attributes.alertTypeId, attributes.name)), username ), + updatedAt: new Date().toISOString(), updatedBy: username, }); try { @@ -751,6 +744,7 @@ export class AlertsClient { username ), updatedBy: username, + updatedAt: new Date().toISOString(), }); try { await this.unsecuredSavedObjectsClient.update('alert', id, updateAttributes, { version }); @@ -829,6 +823,7 @@ export class AlertsClient { apiKey: null, apiKeyOwner: null, updatedBy: await this.getUserName(), + updatedAt: new Date().toISOString(), }), { version } ); @@ -875,6 +870,7 @@ export class AlertsClient { muteAll: true, mutedInstanceIds: [], updatedBy: await this.getUserName(), + updatedAt: new Date().toISOString(), }); const updateOptions = { version }; @@ -913,6 +909,7 @@ export class AlertsClient { muteAll: false, mutedInstanceIds: [], updatedBy: await this.getUserName(), + updatedAt: new Date().toISOString(), }); const updateOptions = { version }; @@ -957,6 +954,7 @@ export class AlertsClient { this.updateMeta({ mutedInstanceIds, updatedBy: await this.getUserName(), + updatedAt: new Date().toISOString(), }), { version } ); @@ -999,6 +997,7 @@ export class AlertsClient { alertId, this.updateMeta({ updatedBy: await this.getUserName(), + updatedAt: new Date().toISOString(), mutedInstanceIds: mutedInstanceIds.filter((id: string) => id !== alertInstanceId), }), { version } @@ -1050,19 +1049,17 @@ export class AlertsClient { private getAlertFromRaw( id: string, rawAlert: RawAlert, - updatedAt: SavedObject['updated_at'], references: SavedObjectReference[] | undefined ): Alert { // In order to support the partial update API of Saved Objects we have to support // partial updates of an Alert, but when we receive an actual RawAlert, it is safe // to cast the result to an Alert - return this.getPartialAlertFromRaw(id, rawAlert, updatedAt, references) as Alert; + return this.getPartialAlertFromRaw(id, rawAlert, references) as Alert; } private getPartialAlertFromRaw( id: string, - { createdAt, meta, scheduledTaskId, ...rawAlert }: Partial, - updatedAt: SavedObject['updated_at'] = createdAt, + { createdAt, updatedAt, meta, scheduledTaskId, ...rawAlert }: Partial, references: SavedObjectReference[] | undefined ): PartialAlert { // Not the prettiest code here, but if we want to use most of the diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts index ee407b1a6d50c..6d259029ac480 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/create.test.ts @@ -196,6 +196,7 @@ describe('create()', () => { createdAt: '2019-02-12T21:01:22.479Z', createdBy: 'elastic', updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', muteAll: false, mutedInstanceIds: [], actions: [ @@ -330,6 +331,7 @@ describe('create()', () => { "foo", ], "throttle": null, + "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); @@ -418,6 +420,7 @@ describe('create()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -555,6 +558,7 @@ describe('create()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -631,6 +635,7 @@ describe('create()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -971,6 +976,7 @@ describe('create()', () => { createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', enabled: true, meta: { versionApiKeyLastmodified: 'v7.10.0', @@ -1092,6 +1098,7 @@ describe('create()', () => { createdBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', enabled: false, meta: { versionApiKeyLastmodified: 'v7.10.0', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts index 11ce0027f82d8..8c9ab9494a50a 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/disable.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); @@ -45,6 +45,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('disable()', () => { let alertsClient: AlertsClient; const existingAlert = { @@ -136,6 +138,7 @@ describe('disable()', () => { scheduledTaskId: null, apiKey: null, apiKeyOwner: null, + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', actions: [ { @@ -190,6 +193,7 @@ describe('disable()', () => { scheduledTaskId: null, apiKey: null, apiKeyOwner: null, + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', actions: [ { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts index 16e83c42d8930..feec1d1b9334a 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/enable.test.ts @@ -13,7 +13,7 @@ import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { TaskStatus } from '../../../../task_manager/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); @@ -46,6 +46,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('enable()', () => { let alertsClient: AlertsClient; const existingAlert = { @@ -186,6 +188,7 @@ describe('enable()', () => { meta: { versionApiKeyLastmodified: kibanaVersion, }, + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', apiKey: null, apiKeyOwner: null, @@ -292,6 +295,7 @@ describe('enable()', () => { apiKey: Buffer.from('123:abc').toString('base64'), apiKeyOwner: 'elastic', updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', actions: [ { group: 'default', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts index 1b3a776bd23e0..3d7473a746986 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/find.test.ts @@ -79,6 +79,7 @@ describe('find()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts index 5c0d80f159b31..3f0c783f424d1 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get.test.ts @@ -59,6 +59,7 @@ describe('get()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts index 269b2eb2ab7a7..9bd61c0fe66d2 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -76,6 +76,7 @@ const BaseAlertInstanceSummarySavedObject: SavedObject = { createdBy: null, updatedBy: null, createdAt: mockedDateString, + updatedAt: mockedDateString, apiKey: null, apiKeyOwner: null, throttle: null, diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts index 868fa3d8c6aa2..14ebca2135587 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/mute_all.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -43,6 +43,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('muteAll()', () => { test('mutes an alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -74,6 +76,7 @@ describe('muteAll()', () => { { muteAll: true, mutedInstanceIds: [], + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts index 05ca741f480ca..c2188f128cb4d 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/mute_instance.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -44,6 +44,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('muteInstance()', () => { test('mutes an alert instance', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -68,6 +70,7 @@ describe('muteInstance()', () => { '1', { mutedInstanceIds: ['2'], + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts index 5ef1af9b6f0ee..d92304ab873be 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_all.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -44,6 +44,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('unmuteAll()', () => { test('unmutes an alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -75,6 +77,7 @@ describe('unmuteAll()', () => { { muteAll: false, mutedInstanceIds: [], + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, { diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts index 88692239ac2fe..3486df98f2f05 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/unmute_instance.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); @@ -44,6 +44,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('unmuteInstance()', () => { test('unmutes an alert instance', async () => { const alertsClient = new AlertsClient(alertsClientParams); @@ -69,6 +71,7 @@ describe('unmuteInstance()', () => { { mutedInstanceIds: [], updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', }, { version: '123' } ); diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts index ad58e36ade722..d0bb2607f7a47 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update.test.ts @@ -140,8 +140,8 @@ describe('update()', () => { ], scheduledTaskId: 'task-123', createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), }, - updated_at: new Date().toISOString(), references: [ { name: 'action_0', @@ -300,6 +300,7 @@ describe('update()', () => { "foo", ], "throttle": null, + "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); @@ -362,6 +363,7 @@ describe('update()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -484,6 +486,7 @@ describe('update()', () => { "foo", ], "throttle": "5m", + "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); @@ -534,6 +537,7 @@ describe('update()', () => { bar: true, }, createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), actions: [ { group: 'default', @@ -648,6 +652,7 @@ describe('update()', () => { "foo", ], "throttle": "5m", + "updatedAt": "2019-02-12T21:01:22.479Z", "updatedBy": "elastic", } `); diff --git a/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts b/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts index af178a1fac5f5..ca5f44078f513 100644 --- a/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts +++ b/x-pack/plugins/alerts/server/alerts_client/tests/update_api_key.test.ts @@ -12,7 +12,7 @@ import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/s import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { AlertsAuthorization } from '../../authorization/alerts_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; -import { getBeforeSetup } from './lib'; +import { getBeforeSetup, setGlobalDate } from './lib'; import { InvalidatePendingApiKey } from '../../types'; const taskManager = taskManagerMock.createStart(); @@ -44,6 +44,8 @@ beforeEach(() => { getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry); }); +setGlobalDate(); + describe('updateApiKey()', () => { let alertsClient: AlertsClient; const existingAlert = { @@ -113,6 +115,7 @@ describe('updateApiKey()', () => { apiKey: Buffer.from('234:abc').toString('base64'), apiKeyOwner: 'elastic', updatedBy: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', actions: [ { group: 'default', @@ -162,6 +165,7 @@ describe('updateApiKey()', () => { enabled: true, apiKey: Buffer.from('234:abc').toString('base64'), apiKeyOwner: 'elastic', + updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', actions: [ { diff --git a/x-pack/plugins/alerts/server/saved_objects/index.ts b/x-pack/plugins/alerts/server/saved_objects/index.ts index da30273e93c6b..dfe122f56bc48 100644 --- a/x-pack/plugins/alerts/server/saved_objects/index.ts +++ b/x-pack/plugins/alerts/server/saved_objects/index.ts @@ -16,6 +16,7 @@ export const AlertAttributesExcludedFromAAD = [ 'muteAll', 'mutedInstanceIds', 'updatedBy', + 'updatedAt', 'executionStatus', ]; @@ -28,6 +29,7 @@ export type AlertAttributesExcludedFromAADType = | 'muteAll' | 'mutedInstanceIds' | 'updatedBy' + | 'updatedAt' | 'executionStatus'; export function setupSavedObjects( diff --git a/x-pack/plugins/alerts/server/saved_objects/mappings.json b/x-pack/plugins/alerts/server/saved_objects/mappings.json index a6c92080f18be..f40a7d9075eed 100644 --- a/x-pack/plugins/alerts/server/saved_objects/mappings.json +++ b/x-pack/plugins/alerts/server/saved_objects/mappings.json @@ -62,6 +62,9 @@ "createdAt": { "type": "date" }, + "updatedAt": { + "type": "date" + }, "apiKey": { "type": "binary" }, diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts index 8c9d10769b18a..a4cbc18e13b47 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.test.ts @@ -261,8 +261,48 @@ describe('7.10.0 migrates with failure', () => { }); }); +describe('7.11.0', () => { + beforeEach(() => { + jest.resetAllMocks(); + encryptedSavedObjectsSetup.createMigration.mockImplementation( + (shouldMigrateWhenPredicate, migration) => migration + ); + }); + + test('add updatedAt field to alert - set to SavedObject updated_at attribute', () => { + const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const alert = getMockData({}, true); + expect(migration711(alert, { log })).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + updatedAt: alert.updated_at, + }, + }); + }); + + test('add updatedAt field to alert - set to createdAt when SavedObject updated_at is not defined', () => { + const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const alert = getMockData({}); + expect(migration711(alert, { log })).toEqual({ + ...alert, + attributes: { + ...alert.attributes, + updatedAt: alert.attributes.createdAt, + }, + }); + }); +}); + +function getUpdatedAt(): string { + const updatedAt = new Date(); + updatedAt.setHours(updatedAt.getHours() + 2); + return updatedAt.toISOString(); +} + function getMockData( - overwrites: Record = {} + overwrites: Record = {}, + withSavedObjectUpdatedAt: boolean = false ): SavedObjectUnsanitizedDoc> { return { attributes: { @@ -295,6 +335,7 @@ function getMockData( ], ...overwrites, }, + updated_at: withSavedObjectUpdatedAt ? getUpdatedAt() : undefined, id: uuid.v4(), type: 'alert', }; diff --git a/x-pack/plugins/alerts/server/saved_objects/migrations.ts b/x-pack/plugins/alerts/server/saved_objects/migrations.ts index 0b2c86b84f67b..d8ebced03c5a6 100644 --- a/x-pack/plugins/alerts/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerts/server/saved_objects/migrations.ts @@ -37,8 +37,15 @@ export function getMigrations( ) ); + const migrationAlertUpdatedAtDate = encryptedSavedObjects.createMigration( + // migrate all documents in 7.11 in order to add the "updatedAt" field + (doc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(setAlertUpdatedAtDate) + ); + return { '7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'), + '7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtDate, '7.11.0'), }; } @@ -59,6 +66,19 @@ function executeMigrationWithErrorHandling( }; } +const setAlertUpdatedAtDate = ( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc => { + const updatedAt = doc.updated_at || doc.attributes.createdAt; + return { + ...doc, + attributes: { + ...doc.attributes, + updatedAt, + }, + }; +}; + const consumersToChange: Map = new Map( Object.entries({ alerting: 'alerts', diff --git a/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts b/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts index 50815c797e399..8041ec551bb0d 100644 --- a/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts +++ b/x-pack/plugins/alerts/server/saved_objects/partially_update_alert.test.ts @@ -95,6 +95,7 @@ const DefaultAttributes = { muteAll: true, mutedInstanceIds: ['muted-instance-id-1', 'muted-instance-id-2'], updatedBy: 'someone', + updatedAt: '2019-02-12T21:01:22.479Z', }; const InvalidAttributes = { ...DefaultAttributes, foo: 'bar' }; diff --git a/x-pack/plugins/alerts/server/types.ts b/x-pack/plugins/alerts/server/types.ts index 9532d8d1def62..500c681a1d2b9 100644 --- a/x-pack/plugins/alerts/server/types.ts +++ b/x-pack/plugins/alerts/server/types.ts @@ -147,6 +147,7 @@ export interface RawAlert extends SavedObjectAttributes { createdBy: string | null; updatedBy: string | null; createdAt: string; + updatedAt: string; apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts index 8f62984db1b5e..be95c6ffe6f38 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts @@ -124,3 +124,5 @@ export const getContentSourcePath = ( export const getGroupPath = (groupId: string) => generatePath(GROUP_PATH, { groupId }); export const getGroupSourcePrioritizationPath = (groupId: string) => `${GROUPS_PATH}/${groupId}/source_prioritization`; +export const getSourcesPath = (path: string, isOrganization: boolean) => + isOrganization ? `${ORG_PATH}${path}` : path; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx new file mode 100644 index 0000000000000..7b6d02c36c0cc --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx @@ -0,0 +1,235 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState } from 'react'; + +import { History } from 'history'; +import { useActions, useValues } from 'kea'; +import { useHistory } from 'react-router-dom'; + +import { AppLogic } from '../../../../app_logic'; +import { Loading } from '../../../../../../applications/shared/loading'; +import { ViewContentHeader } from '../../../../components/shared/view_content_header'; +import { CUSTOM_SERVICE_TYPE } from '../../../../constants'; +import { staticSourceData } from '../../source_data'; +import { SourceLogic } from '../../source_logic'; +import { SourceDataItem, FeatureIds } from '../../../../types'; +import { SOURCE_ADDED_PATH, getSourcesPath } from '../../../../routes'; + +import { AddSourceHeader } from './add_source_header'; +import { ConfigCompleted } from './config_completed'; +import { ConfigurationIntro } from './configuration_intro'; +import { ConfigureCustom } from './configure_custom'; +import { ConfigureOauth } from './configure_oauth'; +import { ConnectInstance } from './connect_instance'; +import { ReAuthenticate } from './re_authenticate'; +import { SaveConfig } from './save_config'; +import { SaveCustom } from './save_custom'; + +enum Steps { + ConfigIntroStep = 'Config Intro', + SaveConfigStep = 'Save Config', + ConfigCompletedStep = 'Config Completed', + ConnectInstanceStep = 'Connect Instance', + ConfigureCustomStep = 'Configure Custom', + ConfigureOauthStep = 'Configure Oauth', + SaveCustomStep = 'Save Custom', + ReAuthenticateStep = 'ReAuthenticate', +} + +interface AddSourceProps { + sourceIndex: number; + connect?: boolean; + configure?: boolean; + reAuthenticate?: boolean; +} + +export const AddSource: React.FC = ({ + sourceIndex, + connect, + configure, + reAuthenticate, +}) => { + const history = useHistory() as History; + const { + getSourceConfigData, + saveSourceConfig, + createContentSource, + resetSourceState, + } = useActions(SourceLogic); + const { + sourceConfigData: { + name, + categories, + needsPermissions, + accountContextOnly, + privateSourcesEnabled, + }, + dataLoading, + newCustomSource, + } = useValues(SourceLogic); + + const { + serviceType, + configuration, + features, + objTypes, + sourceDescription, + connectStepDescription, + addPath, + } = staticSourceData[sourceIndex] as SourceDataItem; + + const { isOrganization } = useValues(AppLogic); + + useEffect(() => { + getSourceConfigData(serviceType); + return resetSourceState; + }, []); + + const isCustom = serviceType === CUSTOM_SERVICE_TYPE; + const isRemote = features?.platinumPrivateContext.includes(FeatureIds.Remote); + + const getFirstStep = () => { + if (isCustom) return Steps.ConfigureCustomStep; + if (connect) return Steps.ConnectInstanceStep; + if (configure) return Steps.ConfigureOauthStep; + if (reAuthenticate) return Steps.ReAuthenticateStep; + return Steps.ConfigIntroStep; + }; + + const [currentStep, setStep] = useState(getFirstStep()); + + if (dataLoading) return ; + + const goToConfigurationIntro = () => setStep(Steps.ConfigIntroStep); + const goToSaveConfig = () => setStep(Steps.SaveConfigStep); + const setConfigCompletedStep = () => setStep(Steps.ConfigCompletedStep); + const goToConfigCompleted = () => saveSourceConfig(false, setConfigCompletedStep); + + const goToConnectInstance = () => { + setStep(Steps.ConnectInstanceStep); + history.push(`${getSourcesPath(addPath, isOrganization)}/connect`); + }; + + const saveCustomSuccess = () => setStep(Steps.SaveCustomStep); + const goToSaveCustom = () => createContentSource(CUSTOM_SERVICE_TYPE, saveCustomSuccess); + + const goToFormSourceCreated = (sourceName: string) => { + history.push(`${getSourcesPath(SOURCE_ADDED_PATH, isOrganization)}/?name=${sourceName}`); + }; + + const pageTitle = () => { + if (currentStep === Steps.ConnectInstanceStep || currentStep === Steps.ConfigureOauthStep) { + return 'Connect'; + } + if (currentStep === Steps.ReAuthenticateStep) { + return 'Re-authenticate'; + } + if (currentStep === Steps.ConfigureCustomStep || currentStep === Steps.SaveCustomStep) { + return 'Create a'; + } + return 'Configure'; + }; + + const CREATE_CUSTOM_SOURCE_SIDEBAR_BLURB = + 'Custom API Sources provide a set of feature-rich endpoints for indexing data from any content repository.'; + const CONFIGURE_ORGANIZATION_SOURCE_SIDEBAR_BLURB = + 'Follow the configuration flow to add a new content source to Workplace Search. First, create an OAuth application in the content source. After that, connect as many instances of the content source that you need.'; + const CONFIGURE_PRIVATE_SOURCE_SIDEBAR_BLURB = + 'Follow the configuration flow to add a new private content source to Workplace Search. Private content sources are added by each person via their own personal dashboards. Their data stays safe and visible only to them.'; + const CONNECT_ORGANIZATION_SOURCE_SIDEBAR_BLURB = `Upon successfully connecting ${name}, source content will be synced to your organization and will be made available and searchable.`; + const CONNECT_PRIVATE_REMOTE_SOURCE_SIDEBAR_BLURB = ( + <> + {name} is a remote source, which means that each time you search, we reach + out to the content source and get matching results directly from {name}'s servers. + + ); + const CONNECT_PRIVATE_STANDARD_SOURCE_SIDEBAR_BLURB = ( + <> + {name} is a standard source for which content is synchronized on a regular + basis, in a relevant and secure way. + + ); + + const CONNECT_PRIVATE_SOURCE_SIDEBAR_BLURB = isRemote + ? CONNECT_PRIVATE_REMOTE_SOURCE_SIDEBAR_BLURB + : CONNECT_PRIVATE_STANDARD_SOURCE_SIDEBAR_BLURB; + const CONFIGURE_SOURCE_SIDEBAR_BLURB = accountContextOnly + ? CONFIGURE_PRIVATE_SOURCE_SIDEBAR_BLURB + : CONFIGURE_ORGANIZATION_SOURCE_SIDEBAR_BLURB; + + const CONFIG_SIDEBAR_BLURB = isCustom + ? CREATE_CUSTOM_SOURCE_SIDEBAR_BLURB + : CONFIGURE_SOURCE_SIDEBAR_BLURB; + const CONNECT_SIDEBAR_BLURB = isOrganization + ? CONNECT_ORGANIZATION_SOURCE_SIDEBAR_BLURB + : CONNECT_PRIVATE_SOURCE_SIDEBAR_BLURB; + + const PAGE_DESCRIPTION = + currentStep === Steps.ConnectInstanceStep ? CONNECT_SIDEBAR_BLURB : CONFIG_SIDEBAR_BLURB; + + const header = ; + + return ( + <> + + {currentStep === Steps.ConfigIntroStep && ( + + )} + {currentStep === Steps.SaveConfigStep && ( + + )} + {currentStep === Steps.ConfigCompletedStep && ( + + )} + {currentStep === Steps.ConnectInstanceStep && ( + + )} + {currentStep === Steps.ConfigureCustomStep && ( + + )} + {currentStep === Steps.ConfigureOauthStep && ( + + )} + {currentStep === Steps.SaveCustomStep && ( + + )} + {currentStep === Steps.ReAuthenticateStep && } + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx new file mode 100644 index 0000000000000..22230bb59f847 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_header.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { startCase } from 'lodash'; + +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTextColor } from '@elastic/eui'; + +import { SourceIcon } from '../../../../components/shared/source_icon'; + +interface AddSourceHeaderProps { + name: string; + serviceType: string; + categories: string[]; +} + +export const AddSourceHeader: React.FC = ({ + name, + serviceType, + categories, +}) => { + return ( + <> + + + + + + + +

+ {name} +

+
+ + {categories.map((category) => startCase(category)).join(', ')} + +
+
+ + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx new file mode 100644 index 0000000000000..24c3a8f8ddb3b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_list.tsx @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState, ChangeEvent } from 'react'; +import noSharedSourcesIcon from 'workplace_search/components/assets/shareCircle.svg'; + +import { useActions, useValues } from 'kea'; + +import { + EuiFieldSearch, + EuiFormRow, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiEmptyPrompt, +} from '@elastic/eui'; + +import { AppLogic } from '../../../../app_logic'; +import { ContentSection } from '../../../../components/shared/content_section'; +import { ViewContentHeader } from '../../../../components/shared/view_content_header'; +import { Loading } from '../../../../../../applications/shared/loading'; +import { CUSTOM_SERVICE_TYPE } from '../../../../constants'; +import { SourceDataItem } from '../../../../types'; + +import { SourcesLogic } from '../../sources_logic'; +import { AvailableSourcesList } from './available_sources_list'; +import { ConfiguredSourcesList } from './configured_sources_list'; + +const NEW_SOURCE_DESCRIPTION = + 'When configuring and connecting a source, you are creating distinct entities with searchable content synchronized from the content platform itself. A source can be added using one of the available source connectors or via Custom API Sources, for additional flexibility.'; +const ORG_SOURCE_DESCRIPTION = + 'Shared content sources are available to your entire organization or can be assigned to specific user groups.'; +const PRIVATE_SOURCE_DESCRIPTION = + 'Connect a new source to add its content and documents to your search experience.'; +const NO_SOURCES_TITLE = 'Configure and connect your first content source'; +const ORG_SOURCES_TITLE = 'Add a shared content source'; +const PRIVATE_SOURCES_TITLE = 'Add a new content source'; +const PLACEHOLDER = 'Filter sources...'; + +export const AddSourceList: React.FC = () => { + const { contentSources, dataLoading, availableSources, configuredSources } = useValues( + SourcesLogic + ); + + const { initializeSources, resetSourcesState } = useActions(SourcesLogic); + + const { isOrganization } = useValues(AppLogic); + + const [filterValue, setFilterValue] = useState(''); + + useEffect(() => { + initializeSources(); + return resetSourcesState; + }, []); + + if (dataLoading) return ; + + const hasSources = contentSources.length > 0; + const showConfiguredSourcesList = configuredSources.find( + ({ serviceType }) => serviceType !== CUSTOM_SERVICE_TYPE + ); + + const BASE_DESCRIPTION = hasSources ? '' : NEW_SOURCE_DESCRIPTION; + const PAGE_CONTEXT_DESCRIPTION = isOrganization + ? ORG_SOURCE_DESCRIPTION + : PRIVATE_SOURCE_DESCRIPTION; + + const PAGE_DESCRIPTION = BASE_DESCRIPTION + PAGE_CONTEXT_DESCRIPTION; + const HAS_SOURCES_TITLE = isOrganization ? ORG_SOURCES_TITLE : PRIVATE_SOURCES_TITLE; + const PAGE_TITLE = hasSources ? HAS_SOURCES_TITLE : NO_SOURCES_TITLE; + + const handleFilterChange = (e: ChangeEvent) => setFilterValue(e.target.value); + + const filterSources = (source: SourceDataItem, sources: SourceDataItem[]): boolean => { + if (!filterValue) return true; + const filterSource = sources.find(({ serviceType }) => serviceType === source.serviceType); + const filteredName = filterSource?.name || ''; + return filteredName.toLowerCase().indexOf(filterValue.toLowerCase()) > -1; + }; + + const filterAvailableSources = (source: SourceDataItem) => + filterSources(source, availableSources); + const filterConfiguredSources = (source: SourceDataItem) => + filterSources(source, configuredSources); + + const visibleAvailableSources = availableSources.filter( + filterAvailableSources + ) as SourceDataItem[]; + const visibleConfiguredSources = configuredSources.filter( + filterConfiguredSources + ) as SourceDataItem[]; + + return ( + <> + + {showConfiguredSourcesList || isOrganization ? ( + + + + + + + {showConfiguredSourcesList && ( + + )} + {isOrganization && } + + ) : ( + + + + + + + + No available sources} + body={ +

+ Sources will be available for search when an administrator adds them to this + organization. +

+ } + /> + + +
+ +
+
+
+ )} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/available_sources_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/available_sources_list.tsx new file mode 100644 index 0000000000000..0d4345c67cfb3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/available_sources_list.tsx @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { Link } from 'react-router-dom'; + +import { + EuiCard, + EuiFlexGrid, + EuiFlexItem, + EuiSpacer, + EuiTitle, + EuiText, + EuiToolTip, +} from '@elastic/eui'; + +import { useValues } from 'kea'; + +import { LicensingLogic } from '../../../../../../applications/shared/licensing'; + +import { SourceIcon } from '../../../../components/shared/source_icon'; +import { SourceDataItem } from '../../../../types'; +import { ADD_CUSTOM_PATH, getSourcesPath } from '../../../../routes'; + +interface AvailableSourcesListProps { + sources: SourceDataItem[]; +} + +export const AvailableSourcesList: React.FC = ({ sources }) => { + const { hasPlatinumLicense } = useValues(LicensingLogic); + + const getSourceCard = ({ name, serviceType, addPath, accountContextOnly }: SourceDataItem) => { + const disabled = !hasPlatinumLicense && accountContextOnly; + const card = ( + } + isDisabled={disabled} + icon={ + + } + /> + ); + + if (disabled) { + return ( + + {card} + + ); + } + return {card}; + }; + + const visibleSources = ( + + {sources.map((source, i) => ( + + {getSourceCard(source)} + + ))} + + ); + + const emptyState =

No available sources matching your query.

; + + return ( + <> + +

Available for configuration

+
+ +

+ Configure an available source or build your own with the{' '} + + Custom API Source + + . +

+
+ + {sources.length > 0 ? visibleSources : emptyState} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx new file mode 100644 index 0000000000000..0409bbf578d5a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx @@ -0,0 +1,111 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { Link } from 'react-router-dom'; + +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLink, + EuiSpacer, + EuiText, + EuiTextAlign, +} from '@elastic/eui'; + +import { + getSourcesPath, + ADD_SOURCE_PATH, + SECURITY_PATH, + PRIVATE_SOURCES_DOCS_URL, +} from '../../../../routes'; + +interface ConfigCompletedProps { + header: React.ReactNode; + name: string; + accountContextOnly?: boolean; + privateSourcesEnabled: boolean; + advanceStep(): void; +} + +export const ConfigCompleted: React.FC = ({ + name, + advanceStep, + accountContextOnly, + header, + privateSourcesEnabled, +}) => ( +
+ {header} + + + + + + + + + + +

{name} Configured

+
+
+ + + {!accountContextOnly ? ( +

{name} can now be connected to Workplace Search

+ ) : ( + +

Users can now link their {name} accounts from their personal dashboards.

+ {!privateSourcesEnabled && ( +

+ Remember to{' '} + + enable private source connection + {' '} + in Security settings. +

+ )} +

+ + Learn more about private content sources. + +

+
+ )} +
+
+
+
+
+
+ + + + + + Configure a new content source + + + + {!accountContextOnly && ( + + + Connect {name} + + + )} + +
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_docs_links.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_docs_links.tsx new file mode 100644 index 0000000000000..b666c859948d5 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_docs_links.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +interface ConfigDocsLinksProps { + name: string; + documentationUrl: string; + applicationPortalUrl?: string; + applicationLinkTitle?: string; +} + +export const ConfigDocsLinks: React.FC = ({ + name, + documentationUrl, + applicationPortalUrl, + applicationLinkTitle, +}) => ( + + + + Documentation + + + + {applicationPortalUrl && ( + + {applicationLinkTitle || `${name} Application Portal`} + + )} + + +); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx new file mode 100644 index 0000000000000..2bf5134e59e26 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { + EuiBadge, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +import connectionIllustration from 'workplace_search/components/assets/connectionIllustration.svg'; + +interface ConfigurationIntroProps { + header: React.ReactNode; + name: string; + advanceStep(): void; +} + +export const ConfigurationIntro: React.FC = ({ + name, + advanceStep, + header, +}) => ( +
+ {header} + + + + +
+ connection illustration +
+
+ + + + + +

How to add {name}

+
+ + +

Quick setup, then all of your documents will be searchable.

+
+ +
+ + + +
+ +

Step 1

+
+
+
+ + +

+ Configure an OAuth application  + One-Time Action +

+

+ Setup a secure OAuth application through the content source that you or your + team will use to connect and synchronize content. You only have to do this + once per content source. +

+
+
+
+
+ + + +
+ +

Step 2

+
+
+
+ + +

Connect the content source

+

+ Use the new OAuth application to connect any number of instances of the + content source to Workplace Search. +

+
+
+
+
+ + + + + Configure {name} + + + + +
+
+
+
+
+
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx new file mode 100644 index 0000000000000..3788071979e67 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { ChangeEvent, FormEvent } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiButton, + EuiFieldText, + EuiForm, + EuiFormRow, + EuiLink, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +import { CUSTOM_SOURCE_DOCS_URL } from '../../../../routes'; +import { SourceLogic } from '../../source_logic'; + +interface ConfigureCustomProps { + header: React.ReactNode; + helpText: string; + advanceStep(): void; +} + +export const ConfigureCustom: React.FC = ({ + helpText, + advanceStep, + header, +}) => { + const { setCustomSourceNameValue } = useActions(SourceLogic); + const { customSourceNameValue, buttonLoading } = useValues(SourceLogic); + + const handleFormSubmit = (e: FormEvent) => { + e.preventDefault(); + advanceStep(); + }; + + const handleNameChange = (e: ChangeEvent) => + setCustomSourceNameValue(e.target.value); + + return ( +
+ {header} +
+ + +

{helpText}

+

+ + Read the documentation + {' '} + to learn more about Custom API Sources. +

+
+ + + + + + + + Create Custom API Source + + +
+
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.tsx new file mode 100644 index 0000000000000..9c2084483c816 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_oauth.tsx @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState, FormEvent } from 'react'; + +import { Location } from 'history'; +import { useActions, useValues } from 'kea'; +import { useLocation } from 'react-router-dom'; + +import { + EuiButton, + EuiCheckboxGroup, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, +} from '@elastic/eui'; + +import { EuiCheckboxGroupIdToSelectedMap } from '@elastic/eui/src/components/form/checkbox/checkbox_group'; + +import { parseQueryParams } from '../../../../../../applications/shared/query_params'; +import { Loading } from '../../../../../../applications/shared/loading'; +import { SourceLogic } from '../../source_logic'; + +interface OauthQueryParams { + preContentSourceId: string; +} + +interface ConfigureOauthProps { + header: React.ReactNode; + name: string; + onFormCreated(name: string): void; +} + +export const ConfigureOauth: React.FC = ({ name, onFormCreated, header }) => { + const { search } = useLocation() as Location; + + const { preContentSourceId } = (parseQueryParams(search) as unknown) as OauthQueryParams; + const [formLoading, setFormLoading] = useState(false); + + const { + getPreContentSourceConfigData, + setSelectedGithubOrganizations, + createContentSource, + } = useActions(SourceLogic); + const { + currentServiceType, + githubOrganizations, + selectedGithubOrganizationsMap, + sectionLoading, + } = useValues(SourceLogic); + + const checkboxOptions = githubOrganizations.map((item) => ({ id: item, label: item })); + + useEffect(() => { + getPreContentSourceConfigData(preContentSourceId); + }, []); + + const handleChange = (option: string) => setSelectedGithubOrganizations(option); + const formSubmitSuccess = () => onFormCreated(name); + const handleFormSubmitError = () => setFormLoading(false); + const handleFormSubmut = (e: FormEvent) => { + setFormLoading(true); + e.preventDefault(); + createContentSource(currentServiceType, formSubmitSuccess, handleFormSubmitError); + }; + + const configfieldsForm = ( +
+ + + + + + + + + Complete connection + + + + +
+ ); + + return ( +
+ {header} + {sectionLoading ? : configfieldsForm} +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx new file mode 100644 index 0000000000000..a95d5ca75b0b6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { Link } from 'react-router-dom'; + +import { + EuiButtonEmpty, + EuiFlexGrid, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, + EuiTitle, + EuiToken, + EuiToolTip, +} from '@elastic/eui'; + +import { SourceIcon } from '../../../../components/shared/source_icon'; +import { SourceDataItem } from '../../../../types'; +import { getSourcesPath } from '../../../../routes'; + +interface ConfiguredSourcesProps { + sources: SourceDataItem[]; + isOrganization: boolean; +} + +export const ConfiguredSourcesList: React.FC = ({ + sources, + isOrganization, +}) => { + const unConnectedTooltip = ( + + + + + + ); + + const accountOnlyTooltip = ( + + + + + + ); + + const visibleSources = ( + + {sources.map(({ name, serviceType, addPath, connected, accountContextOnly }, i) => ( + + +
+ + + + + + + + +

+ {name}  + {!connected && + !accountContextOnly && + isOrganization && + unConnectedTooltip} + {accountContextOnly && isOrganization && accountOnlyTooltip} +

+
+
+
+
+ {(!isOrganization || (isOrganization && !accountContextOnly)) && ( + + + Connect + + + )} +
+
+
+
+ ))} +
+ ); + + const emptyState =

There are no configured sources matching your query.

; + + return ( + <> + +

Configured content sources

+
+ +

Configured and ready for connection.

+
+ + {sources.length > 0 ? visibleSources : emptyState} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx new file mode 100644 index 0000000000000..ad183181b4eca --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx @@ -0,0 +1,256 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useEffect, FormEvent } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiButton, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiFieldText, + EuiFormRow, + EuiLink, + EuiSpacer, + EuiSwitch, + EuiText, + EuiTitle, + EuiTextColor, + EuiBadge, + EuiBadgeGroup, +} from '@elastic/eui'; + +import { LicensingLogic } from '../../../../../../applications/shared/licensing'; + +import { AppLogic } from '../../../../app_logic'; +import { SourceLogic } from '../../source_logic'; +import { FeatureIds, Configuration, Features } from '../../../../types'; +import { DOCUMENT_PERMISSIONS_DOCS_URL } from '../../../../routes'; +import { SourceFeatures } from './source_features'; + +interface ConnectInstanceProps { + header: React.ReactNode; + configuration: Configuration; + features?: Features; + objTypes?: string[]; + name: string; + serviceType: string; + sourceDescription: string; + connectStepDescription: string; + needsPermissions: boolean; + onFormCreated(name: string): void; +} + +export const ConnectInstance: React.FC = ({ + configuration: { needsSubdomain, hasOauthRedirect }, + features, + objTypes, + name, + serviceType, + sourceDescription, + connectStepDescription, + needsPermissions, + onFormCreated, + header, +}) => { + const [formLoading, setFormLoading] = useState(false); + + const { hasPlatinumLicense } = useValues(LicensingLogic); + + const { + getSourceConnectData, + createContentSource, + setSourceLoginValue, + setSourcePasswordValue, + setSourceSubdomainValue, + setSourceIndexPermissionsValue, + } = useActions(SourceLogic); + + const { loginValue, passwordValue, indexPermissionsValue, subdomainValue } = useValues( + SourceLogic + ); + + const { isOrganization } = useValues(AppLogic); + + // Default indexPermissions to true, if needed + useEffect(() => { + setSourceIndexPermissionsValue(needsPermissions && isOrganization && hasPlatinumLicense); + }, []); + + const redirectOauth = (oauthUrl: string) => (window.location.href = oauthUrl); + const redirectFormCreated = () => onFormCreated(name); + const onOauthFormSubmit = () => getSourceConnectData(serviceType, redirectOauth); + const handleFormSubmitError = () => setFormLoading(false); + const onCredentialsFormSubmit = () => + createContentSource(serviceType, redirectFormCreated, handleFormSubmitError); + + const handleFormSubmit = (e: FormEvent) => { + setFormLoading(true); + e.preventDefault(); + const onSubmit = hasOauthRedirect ? onOauthFormSubmit : onCredentialsFormSubmit; + onSubmit(); + }; + + const credentialsFields = ( + <> + + setSourceLoginValue(e.target.value)} + /> + + + setSourcePasswordValue(e.target.value)} + /> + + + + ); + + const subdomainField = ( + <> + + setSourceSubdomainValue(e.target.value)} + /> + + + + ); + + const featureBadgeGroup = () => { + if (isOrganization) { + return null; + } + + const isRemote = features?.platinumPrivateContext.includes(FeatureIds.Remote); + const isPrivate = features?.platinumPrivateContext.includes(FeatureIds.Private); + + if (isRemote || isPrivate) { + return ( + <> + + {isRemote && Remote} + {isPrivate && Private} + + + + ); + } + }; + + const descriptionBlock = ( + + {sourceDescription &&

{sourceDescription}

} + {connectStepDescription &&

{connectStepDescription}

} + +
+ ); + + const whichDocsLink = ( + + Which option should I choose? + + ); + + const permissionField = ( + <> + + + Document-level permissions + + + + Enable document-level permission synchronization} + name="index_permissions" + onChange={(e) => setSourceIndexPermissionsValue(e.target.checked)} + checked={indexPermissionsValue} + disabled={!needsPermissions} + /> + + + {!needsPermissions && ( + + Document-level permissions are not yet available for this source.{' '} + + Learn more + + + )} + {needsPermissions && indexPermissionsValue && ( + + Document-level permission information will be synchronized. Additional configuration is + required following the initial connection before documents are available for search. +
+ {whichDocsLink} +
+ )} +
+ + {!indexPermissionsValue && ( + +

+ All documents accessible to the connecting service user will be synchronized and made + available to the organization’s users, or group’s users. Documents are immediately + available for search. {needsPermissions && whichDocsLink} +

+
+ )} + + + ); + + const formFields = ( + <> + {isOrganization && hasPlatinumLicense && permissionField} + {!hasOauthRedirect && credentialsFields} + {needsSubdomain && subdomainField} + + + + Connect {name} + + + + ); + + return ( +
+
+ + + {header} + {featureBadgeGroup()} + {descriptionBlock} + {formFields} + + + + + +
+
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/index.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/index.ts new file mode 100644 index 0000000000000..8a46eaa7d70e9 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AddSource } from './add_source'; +export { AddSourceList } from './add_source_list'; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx new file mode 100644 index 0000000000000..7336a3b51a444 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/re_authenticate.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState, FormEvent } from 'react'; + +import { Location } from 'history'; +import { useActions, useValues } from 'kea'; +import { useLocation } from 'react-router-dom'; + +import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSpacer } from '@elastic/eui'; +import { parseQueryParams } from '../../../../../../applications/shared/query_params'; + +import { SourceLogic } from '../../source_logic'; + +interface SourceQueryParams { + sourceId: string; +} + +interface ReAuthenticateProps { + name: string; + header: React.ReactNode; +} + +export const ReAuthenticate: React.FC = ({ name, header }) => { + const { search } = useLocation() as Location; + + const { sourceId } = (parseQueryParams(search) as unknown) as SourceQueryParams; + const [formLoading, setFormLoading] = useState(false); + + const { getSourceReConnectData } = useActions(SourceLogic); + const { + sourceConnectData: { oauthUrl }, + } = useValues(SourceLogic); + + useEffect(() => { + getSourceReConnectData(sourceId); + }, []); + + const handleFormSubmit = (e: FormEvent) => { + e.preventDefault(); + setFormLoading(true); + window.location.href = oauthUrl; + }; + + return ( +
+ {header} +
+ + +

+ Your {name} credentials are no longer valid. Please re-authenticate with the original + credentials to resume content syncing. +

+
+
+ + + + Re-authenticate {name} + + + +
+ ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx new file mode 100644 index 0000000000000..4036bb6a771bb --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx @@ -0,0 +1,218 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FormEvent } from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiButton, + EuiButtonEmpty, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormRow, + EuiSpacer, + EuiSteps, +} from '@elastic/eui'; + +import { LicensingLogic } from '../../../../../../applications/shared/licensing'; + +import { ApiKey } from '../../../../components/shared/api_key'; +import { SourceLogic } from '../../source_logic'; +import { Configuration } from '../../../../types'; + +import { ConfigDocsLinks } from './config_docs_links'; + +interface SaveConfigProps { + header: React.ReactNode; + name: string; + configuration: Configuration; + advanceStep(): void; + goBackStep?(): void; + onDeleteConfig?(): void; +} + +export const SaveConfig: React.FC = ({ + name, + configuration: { + isPublicKey, + needsBaseUrl, + documentationUrl, + applicationPortalUrl, + applicationLinkTitle, + baseUrlTitle, + }, + advanceStep, + goBackStep, + onDeleteConfig, + header, +}) => { + const { hasPlatinumLicense } = useValues(LicensingLogic); + + const { setClientIdValue, setClientSecretValue, setBaseUrlValue } = useActions(SourceLogic); + + const { + sourceConfigData, + buttonLoading, + clientIdValue, + clientSecretValue, + baseUrlValue, + } = useValues(SourceLogic); + + const { + accountContextOnly, + configuredFields: { publicKey, consumerKey }, + } = sourceConfigData; + + const handleFormSubmission = (e: FormEvent) => { + e.preventDefault(); + advanceStep(); + }; + + const saveButton = ( + + Save configuration + + ); + + const deleteButton = ( + + Remove + + ); + + const backButton =  Go back; + const showSaveButton = hasPlatinumLicense || !accountContextOnly; + + const formActions = ( + + + {showSaveButton && {saveButton}} + + {goBackStep && backButton} + {onDeleteConfig && deleteButton} + + + + ); + + const publicKeyStep1 = ( + + + + + + + + + + + + + + ); + + const credentialsStep1 = ( + + ); + + const publicKeyStep2 = ( + <> + + setBaseUrlValue(e.target.value)} + name="base-uri" + /> + + + {formActions} + + ); + + const credentialsStep2 = ( + + + + + setClientIdValue(e.target.value)} + name="client-id" + /> + + + setClientSecretValue(e.target.value)} + name="client-secret" + /> + + {needsBaseUrl && ( + + setBaseUrlValue(e.target.value)} + name="base-uri" + /> + + )} + + {formActions} + + + + ); + + const oauthSteps = (sourceName: string) => [ + `Create an OAuth app in your organization's ${sourceName}\u00A0account`, + 'Provide the appropriate configuration information', + ]; + + const configSteps = [ + { + title: oauthSteps(name)[0], + children: isPublicKey ? publicKeyStep1 : credentialsStep1, + }, + { + title: oauthSteps(name)[1], + children: isPublicKey ? publicKeyStep2 : credentialsStep2, + }, + ]; + + return ( + <> + {header} +
+ + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx new file mode 100644 index 0000000000000..17510c3ece914 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { Link } from 'react-router-dom'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiIcon, + EuiSpacer, + EuiText, + EuiTextAlign, + EuiTitle, + EuiLink, + EuiPanel, +} from '@elastic/eui'; + +import { CredentialItem } from '../../../../components/shared/credential_item'; +import { LicenseBadge } from '../../../../components/shared/license_badge'; + +import { CustomSource } from '../../../../types'; +import { + SOURCES_PATH, + SOURCE_DISPLAY_SETTINGS_PATH, + CUSTOM_API_DOCUMENT_PERMISSIONS_DOCS_URL, + ENT_SEARCH_LICENSE_MANAGEMENT, + getContentSourcePath, + getSourcesPath, +} from '../../../../routes'; + +interface SaveCustomProps { + documentationUrl: string; + newCustomSource: CustomSource; + isOrganization: boolean; + header: React.ReactNode; +} + +export const SaveCustom: React.FC = ({ + documentationUrl, + newCustomSource: { key, id, accessToken, name }, + isOrganization, + header, +}) => ( +
+ {header} + + + + + + + + + + +

{name} Created

+
+
+ + + Your endpoints are ready to accept requests. +
+ Be sure to copy your API keys below. +
+ + Return to Sources + +
+
+
+
+ + + + +

API Keys

+
+ +

You'll need these keys to sync documents for this custom source.

+
+ + + + +
+
+
+
+ + + + +
+ +

Visual Walkthrough

+
+ + +

+ + Check out the documentation + {' '} + to learn more about Custom API Sources. +

+
+
+ +
+ +

Styling Results

+
+ + +

+ Use{' '} + + Display Settings + {' '} + to customize how your documents will appear within your search results. Workplace + Search will use fields in alphabetical order by default. +

+
+
+ +
+ + + + +

Set document-level permissions

+
+ + +

+ + Document-level permissions + {' '} + manage content access content on individual or group attributes. Allow or deny + access to specific documents. +

+
+ + + + Learn about Platinum features + + +
+
+
+
+
+
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx new file mode 100644 index 0000000000000..6c92f3a9e13ff --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx @@ -0,0 +1,223 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { useValues } from 'kea'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPanel, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; + +import { LicensingLogic } from '../../../../../../applications/shared/licensing'; + +import { AppLogic } from '../../../../app_logic'; +import { LicenseBadge } from '../../../../components/shared/license_badge'; +import { Features, FeatureIds } from '../../../../types'; +import { ENT_SEARCH_LICENSE_MANAGEMENT } from '../../../../routes'; + +interface ConnectInstanceProps { + features?: Features; + objTypes?: string[]; + name: string; +} + +export const SourceFeatures: React.FC = ({ features, objTypes, name }) => { + const { hasPlatinumLicense } = useValues(LicensingLogic); + const { isOrganization } = useValues(AppLogic); + + const Feature = ({ title, children }: { title: string; children: React.ReactElement }) => ( + <> + + + {title} + + + {children} + + ); + + const SyncFrequencyFeature = ( + + +

+ This source gets new content from {name} every 2 hours (following the + initial sync). +

+
+
+ ); + + const SyncedItemsFeature = ( + + <> + +

The following items are searchable:

+
+ + +
    + {objTypes!.map((objType, i) => ( +
  • {objType}
  • + ))} +
+
+ +
+ ); + + const SearchableContentFeature = ( + + + +

The following items are searchable:

+
+ +
    + {objTypes!.map((objType, i) => ( +
  • {objType}
  • + ))} +
+
+
+ ); + + const RemoteFeature = ( + + +

+ Message data and other information is searchable in real-time from the Workplace Search + experience. +

+
+
+ ); + + const PrivateFeature = ( + + +

+ Results returned are specific and relevant to you. Connecting this source does not expose + your personal data to other search users - only you. +

+
+
+ ); + + const GlobalAccessPermissionsFeature = ( + + +

+ All documents accessible to the connecting service user will be synchronized and made + available to the organization’s users, or group’s users. Documents are immediately + available for search +

+
+
+ ); + + const DocumentLevelPermissionsFeature = ( + + +

+ Document-level permissions manage user content access based on defined rules. Allow or + deny access to certain documents for individuals and groups. +

+ + Explore Platinum features + +
+
+ ); + + const FeaturesRouter = ({ featureId }: { featureId: FeatureIds }) => + ({ + [FeatureIds.SyncFrequency]: SyncFrequencyFeature, + [FeatureIds.SearchableContent]: SearchableContentFeature, + [FeatureIds.SyncedItems]: SyncedItemsFeature, + [FeatureIds.Remote]: RemoteFeature, + [FeatureIds.Private]: PrivateFeature, + [FeatureIds.GlobalAccessPermissions]: GlobalAccessPermissionsFeature, + [FeatureIds.DocumentLevelPermissions]: DocumentLevelPermissionsFeature, + }[featureId]); + + const IncludedFeatures = () => { + let includedFeatures: FeatureIds[] | undefined; + + if (!hasPlatinumLicense && isOrganization) { + includedFeatures = features?.basicOrgContext; + } + if (hasPlatinumLicense && isOrganization) { + includedFeatures = features?.platinumOrgContext; + } + if (hasPlatinumLicense && !isOrganization) { + includedFeatures = features?.platinumPrivateContext; + } + + if (!includedFeatures?.length) { + return null; + } + + return ( + + +

Included features

+
+ {includedFeatures.map((featureId, i) => ( + + ))} +
+ ); + }; + + const ExcludedFeatures = () => { + let excludedFeatures: FeatureIds[] | undefined; + + if (!hasPlatinumLicense && isOrganization) { + excludedFeatures = features?.basicOrgContextExcludedFeatures; + } + + if (!excludedFeatures?.length) { + return null; + } + + return ( + + + {excludedFeatures.map((featureId, i) => ( + + ))} + + ); + }; + + return ( + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/common/detection_engine/types.ts b/x-pack/plugins/security_solution/common/detection_engine/types.ts index 6099a34f9afd1..9e4c71d5eb116 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/types.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/types.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { AlertAction } from '../../../alerts/common'; export type RuleAlertAction = Omit & { diff --git a/x-pack/plugins/security_solution/common/test/index.ts b/x-pack/plugins/security_solution/common/test/index.ts new file mode 100644 index 0000000000000..2fa5fa4ada45a --- /dev/null +++ b/x-pack/plugins/security_solution/common/test/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// For the source of these roles please consult the PR these were introduced https://github.com/elastic/kibana/pull/81866#issue-511165754 +export enum ROLES { + t1_analyst = 't1_analyst', + t2_analyst = 't2_analyst', + hunter = 'hunter', + rule_author = 'rule_author', + soc_manager = 'soc_manager', + platform_engineer = 'platform_engineer', + detections_admin = 'detections_admin', +} + +export type RolesType = keyof typeof ROLES; diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts index 36dc38b684742..db841d2a732c4 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts.spec.ts @@ -30,8 +30,7 @@ import { loginAndWaitForPage } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; -// FLAKY: https://github.com/elastic/kibana/issues/83773 -describe.skip('Alerts', () => { +describe('Alerts', () => { context('Closing alerts', () => { beforeEach(() => { esArchiverLoad('alerts'); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts index 83f1a02aceeb8..fb1f2920aaceb 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_custom.spec.ts @@ -114,8 +114,7 @@ const expectedEditedtags = editedRule.tags.join(''); const expectedEditedIndexPatterns = editedRule.index && editedRule.index.length ? editedRule.index : indexPatterns; -// SKIP: https://github.com/elastic/kibana/issues/83769 -describe.skip('Custom detection rules creation', () => { +describe('Custom detection rules creation', () => { before(() => { esArchiverLoad('timeline'); }); @@ -216,8 +215,7 @@ describe.skip('Custom detection rules creation', () => { }); }); -// FLAKY: https://github.com/elastic/kibana/issues/83793 -describe.skip('Custom detection rules deletion and edition', () => { +describe('Custom detection rules deletion and edition', () => { beforeEach(() => { esArchiverLoad('custom_rules'); loginAndWaitForPageWithoutDateRange(DETECTIONS_URL); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts index 6f995045dfc6a..eb8448233c624 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_detection_rules_export.spec.ts @@ -17,8 +17,7 @@ import { DETECTIONS_URL } from '../urls/navigation'; const EXPECTED_EXPORTED_RULE_FILE_PATH = 'cypress/test_files/expected_rules_export.ndjson'; -// SKIP: https://github.com/elastic/kibana/issues/83769 -describe.skip('Export rules', () => { +describe('Export rules', () => { before(() => { esArchiverLoad('export_rule'); cy.server(); diff --git a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts index c28c4e842e08b..31d8e4666d91d 100644 --- a/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/alerts_timeline.spec.ts @@ -17,8 +17,7 @@ import { loginAndWaitForPage } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; -// FLAKY: https://github.com/elastic/kibana/issues/83771 -describe.skip('Alerts timeline', () => { +describe('Alerts timeline', () => { beforeEach(() => { esArchiverLoad('timeline_alerts'); loginAndWaitForPage(DETECTIONS_URL); diff --git a/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts index 1bba390780264..ed885ad653e5d 100644 --- a/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/cases_connectors.spec.ts @@ -17,8 +17,7 @@ import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { CASES_URL } from '../urls/navigation'; -// FLAKY: https://github.com/elastic/kibana/issues/65278 -describe.skip('Cases connectors', () => { +describe('Cases connectors', () => { before(() => { cy.server(); cy.route('POST', '**/api/actions/action').as('createConnector'); diff --git a/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts b/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts index f4de6d978a70d..403538a37f523 100644 --- a/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/value_lists.spec.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; +import { ROLES } from '../../common/test'; +import { deleteRoleAndUser, loginAndWaitForPageWithoutDateRange } from '../tasks/login'; import { DETECTIONS_URL } from '../urls/navigation'; import { waitForAlertsPanelToBeLoaded, @@ -24,7 +25,7 @@ import { deleteValueListsFile, exportValueList, } from '../tasks/lists'; -import { VALUE_LISTS_TABLE, VALUE_LISTS_ROW } from '../screens/lists'; +import { VALUE_LISTS_TABLE, VALUE_LISTS_ROW, VALUE_LISTS_MODAL_ACTIVATOR } from '../screens/lists'; describe('value lists', () => { describe('management modal', () => { @@ -220,4 +221,19 @@ describe('value lists', () => { }); }); }); + + describe('user with restricted access role', () => { + beforeEach(() => { + loginAndWaitForPageWithoutDateRange(DETECTIONS_URL, ROLES.t1_analyst); + goToManageAlertsDetectionRules(); + }); + + afterEach(() => { + deleteRoleAndUser(ROLES.t1_analyst); + }); + + it('Does not allow a t1 analyst user to upload a value list', () => { + cy.get(VALUE_LISTS_MODAL_ACTIVATOR).should('have.attr', 'disabled'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/login.ts b/x-pack/plugins/security_solution/cypress/tasks/login.ts index 65f821ec5bfb7..9f385d9ccd2fc 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/login.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/login.ts @@ -5,6 +5,9 @@ */ import * as yaml from 'js-yaml'; +import Url, { UrlObject } from 'url'; + +import { RolesType } from '../../common/test'; import { TIMELINE_FLYOUT_BODY } from '../screens/timeline'; /** @@ -42,6 +45,89 @@ const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD'; */ const LOGIN_API_ENDPOINT = '/internal/security/login'; +/** + * cy.visit will default to the baseUrl which uses the default kibana test user + * This function will override that functionality in cy.visit by building the baseUrl + * directly from the environment variables set up in x-pack/test/security_solution_cypress/runner.ts + * + * @param role string role/user to log in with + * @param route string route to visit + */ +export const getUrlWithRoute = (role: RolesType, route: string) => { + const theUrl = `${Url.format({ + auth: `${role}:changeme`, + username: role, + password: 'changeme', + protocol: Cypress.env('protocol'), + hostname: Cypress.env('hostname'), + port: Cypress.env('configport'), + } as UrlObject)}${route.startsWith('/') ? '' : '/'}${route}`; + cy.log(`origin: ${theUrl}`); + return theUrl; +}; + +export const getCurlScriptEnvVars = () => ({ + ELASTICSEARCH_URL: Cypress.env('ELASTICSEARCH_URL'), + ELASTICSEARCH_USERNAME: Cypress.env('ELASTICSEARCH_USERNAME'), + ELASTICSEARCH_PASSWORD: Cypress.env('ELASTICSEARCH_PASSWORD'), + KIBANA_URL: Cypress.env('KIBANA_URL'), +}); + +export const postRoleAndUser = (role: RolesType) => { + const env = getCurlScriptEnvVars(); + const detectionsRoleScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/post_detections_role.sh`; + const detectionsRoleJsonPath = `./server/lib/detection_engine/scripts/roles_users/${role}/detections_role.json`; + const detectionsUserScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/post_detections_user.sh`; + const detectionsUserJsonPath = `./server/lib/detection_engine/scripts/roles_users/${role}/detections_user.json`; + + // post the role + cy.exec(`bash ${detectionsRoleScriptPath} ${detectionsRoleJsonPath}`, { + env, + }); + + // post the user associated with the role to elasticsearch + cy.exec(`bash ${detectionsUserScriptPath} ${detectionsUserJsonPath}`, { + env, + }); +}; + +export const deleteRoleAndUser = (role: RolesType) => { + const env = getCurlScriptEnvVars(); + const detectionsUserDeleteScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/delete_detections_user.sh`; + + // delete the role + cy.exec(`bash ${detectionsUserDeleteScriptPath}`, { + env, + }); +}; + +export const loginWithRole = async (role: RolesType) => { + postRoleAndUser(role); + const theUrl = Url.format({ + auth: `${role}:changeme`, + username: role, + password: 'changeme', + protocol: Cypress.env('protocol'), + hostname: Cypress.env('hostname'), + port: Cypress.env('configport'), + } as UrlObject); + cy.log(`origin: ${theUrl}`); + cy.request({ + body: { + providerType: 'basic', + providerName: 'basic', + currentURL: '/', + params: { + username: role, + password: 'changeme', + }, + }, + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + method: 'POST', + url: getUrlWithRoute(role, LOGIN_API_ENDPOINT), + }); +}; + /** * Authenticates with Kibana using, if specified, credentials specified by * environment variables. The credentials in `kibana.dev.yml` will be used @@ -50,8 +136,10 @@ const LOGIN_API_ENDPOINT = '/internal/security/login'; * To speed the execution of tests, prefer this non-interactive authentication, * which is faster than authentication via Kibana's interactive login page. */ -export const login = () => { - if (credentialsProvidedByEnvironment()) { +export const login = (role?: RolesType) => { + if (role != null) { + loginWithRole(role); + } else if (credentialsProvidedByEnvironment()) { loginViaEnvironmentCredentials(); } else { loginViaConfig(); @@ -129,8 +217,8 @@ const loginViaConfig = () => { * Authenticates with Kibana, visits the specified `url`, and waits for the * Kibana global nav to be displayed before continuing */ -export const loginAndWaitForPage = (url: string) => { - login(); +export const loginAndWaitForPage = (url: string, role?: RolesType) => { + login(role); cy.viewport('macbook-15'); cy.visit( `${url}?timerange=(global:(linkTo:!(timeline),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)))` @@ -138,17 +226,19 @@ export const loginAndWaitForPage = (url: string) => { cy.get('[data-test-subj="headerGlobalNav"]'); }; -export const loginAndWaitForPageWithoutDateRange = (url: string) => { - login(); +export const loginAndWaitForPageWithoutDateRange = (url: string, role?: RolesType) => { + login(role); cy.viewport('macbook-15'); - cy.visit(url); + cy.visit(role ? getUrlWithRoute(role, url) : url); cy.get('[data-test-subj="headerGlobalNav"]', { timeout: 120000 }); }; -export const loginAndWaitForTimeline = (timelineId: string) => { - login(); +export const loginAndWaitForTimeline = (timelineId: string, role?: RolesType) => { + const route = `/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t)`; + + login(role); cy.viewport('macbook-15'); - cy.visit(`/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t)`); + cy.visit(role ? getUrlWithRoute(role, route) : route); cy.get('[data-test-subj="headerGlobalNav"]'); cy.get(TIMELINE_FLYOUT_BODY).should('be.visible'); }; diff --git a/x-pack/plugins/security_solution/package.json b/x-pack/plugins/security_solution/package.json index 97410d8a97cef..048f3846cc322 100644 --- a/x-pack/plugins/security_solution/package.json +++ b/x-pack/plugins/security_solution/package.json @@ -10,7 +10,7 @@ "build-graphql-types": "node scripts/generate_types_from_graphql.js", "cypress:open": "../../../node_modules/.bin/cypress open --config-file ./cypress/cypress.json", "cypress:open-as-ci": "node ../../../scripts/functional_tests --config ../../test/security_solution_cypress/visual_config.ts", - "cypress:run": "../../../node_modules/.bin/cypress run --browser chrome --headless --spec ./cypress/integration/**/*.spec.ts --config-file ./cypress/cypress.json --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json; status=$?; ../../node_modules/.bin/mochawesome-merge ../../../target/kibana-security-solution/cypress/results/mochawesome*.json > ../../../target/kibana-security-solution/cypress/results/output.json; ../../../node_modules/.bin/marge ../../../target/kibana-security-solution/cypress/results/output.json --reportDir ../../../target/kibana-security-solution/cypress/results; mkdir -p ../../../target/junit && cp ../../../target/kibana-security-solution/cypress/results/*.xml ../../../target/junit/ && exit $status;", + "cypress:run": "../../../node_modules/.bin/cypress run --browser chrome --headless --spec ./cypress/integration/**/*.spec.ts --config-file ./cypress/cypress.json --reporter ../../../node_modules/cypress-multi-reporters --reporter-options configFile=./cypress/reporter_config.json; status=$?; ../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-security-solution/cypress/results/mochawesome*.json > ../../../target/kibana-security-solution/cypress/results/output.json; ../../../node_modules/.bin/marge ../../../target/kibana-security-solution/cypress/results/output.json --reportDir ../../../target/kibana-security-solution/cypress/results; mkdir -p ../../../target/junit && cp ../../../target/kibana-security-solution/cypress/results/*.xml ../../../target/junit/ && exit $status;", "cypress:run-as-ci": "node --max-old-space-size=2048 ../../../scripts/functional_tests --config ../../test/security_solution_cypress/cli_config.ts", "test:generate": "node scripts/endpoint/resolver_generator" } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/README.md new file mode 100644 index 0000000000000..cb38a23ebdea8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/README.md @@ -0,0 +1,12 @@ +1. When first starting up elastic, detections will not be available until you visit the page with a SOC Manager role or Platform Engineer role +2. I gave the Hunter role "all" privileges for saved objects management and builtInAlerts so that they can create rules. +3. Rule Author has the ability to create rules and create value lists + +| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Action Connectors | Signals/Alerts | +| :------------------------------------------: | :----------: | :-------------------------------: | :---------: | :--------------: | :---------------: | :------------------------------: | +| T1 Analyst | read | read | none | read | read | read, write | +| T2 Analyst | read | read | read | read | read | read, write | +| Hunter / T3 Analyst | read, write | read | read | read, write | read | read, write | +| Rule Author / Manager / Detections Engineer | read, write | read | read, write | read, write | read | read, write, view_index_metadata | +| SOC Manager | read, write | read | read, write | read, write | all | read, write, manage | +| Platform Engineer (data ingest, cluster ops) | read, write | all | all | read, write | all | all | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/README.md new file mode 100644 index 0000000000000..2ebcedcc75d95 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/README.md @@ -0,0 +1 @@ +This user contains all the possible privileges listed in our detections privileges docs https://www.elastic.co/guide/en/security/current/detections-permissions-section.html This user has higher privileges than the Platform Engineer user diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/delete_detections_user.sh new file mode 100755 index 0000000000000..d17d4792af4c5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/delete_detections_user.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XDELETE ${ELASTICSEARCH_URL}/_security/user/detections_admin diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json new file mode 100644 index 0000000000000..357b8cde8ad10 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json @@ -0,0 +1,35 @@ +{ + "elasticsearch": { + "cluster": ["manage"], + "indices": [ + { + "names": [ + ".siem-signals-*", + ".lists*", + ".items*", + "apm-*-transaction*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*" + ], + "privileges": ["manage", "write", "read"] + } + ] + }, + "kibana": [ + { + "feature": { + "ml": ["all"], + "siem": ["all"], + "actions": ["read"], + "builtInAlerts": ["all"], + "dev_tools": ["all"], + "savedObjectsManagement": ["all"] + }, + "spaces": ["*"] + } + ] +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_user.json new file mode 100644 index 0000000000000..9910d9b516a20 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_user.json @@ -0,0 +1,6 @@ +{ + "password": "changeme", + "roles": ["detections_admin"], + "full_name": "Detections User", + "email": "detections-user@example.com" +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/get_detections_role.sh new file mode 100755 index 0000000000000..f64e9d888fe66 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/get_detections_role.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XGET ${KIBANA_URL}/api/security/role/detections_admin | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_role.sh new file mode 100755 index 0000000000000..318fca59a85a6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_role.sh @@ -0,0 +1,11 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XPUT ${KIBANA_URL}/api/security/role/detections_admin \ +-d @detections_role.json diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_user.sh new file mode 100755 index 0000000000000..2561888f447a1 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/post_detections_user.sh @@ -0,0 +1,13 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +USER=(${@:-./detections_user.json}) + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + ${ELASTICSEARCH_URL}/_security/user/detections_admin \ +-d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/README.md new file mode 100644 index 0000000000000..f0060fb006e32 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/README.md @@ -0,0 +1,12 @@ +This user can CRUD rules and signals. The main difference here is the user has + +```json +"builtInAlerts": ["all"], +"savedObjectsManagement": ["all"] +``` + +privileges whereas the T1 and T2 have "read" privileges which prevents them from creating rules + +| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Action Connectors | Signals/Alerts | +| :-----------------: | :----------: | :------------------: | :---: | :--------------: | :---------------: | :------------: | +| Hunter / T3 Analyst | read, write | read | read | read, write | read | read, write | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/delete_detections_user.sh new file mode 100755 index 0000000000000..04146cf20f8ec --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/delete_detections_user.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XDELETE ${ELASTICSEARCH_URL}/_security/user/hunter diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json new file mode 100644 index 0000000000000..f5482643fb268 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json @@ -0,0 +1,39 @@ +{ + "elasticsearch": { + "cluster": [], + "indices": [ + { + "names": [ + "apm-*-transaction*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*" + ], + "privileges": ["read", "write"] + }, + { + "names": [".siem-signals-*"], + "privileges": ["read", "write"] + }, + { + "names": [".lists*", ".items*"], + "privileges": ["read", "write"] + } + ] + }, + "kibana": [ + { + "feature": { + "ml": ["read"], + "siem": ["all"], + "actions": ["read"], + "builtInAlerts": ["all"], + "savedObjectsManagement": ["all"] + }, + "spaces": ["*"] + } + ] +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_user.json new file mode 100644 index 0000000000000..f9454cc0ad2fe --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_user.json @@ -0,0 +1,6 @@ +{ + "password": "changeme", + "roles": ["hunter"], + "full_name": "Hunter", + "email": "detections-reader@example.com" +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/get_detections_role.sh new file mode 100755 index 0000000000000..b79c40cda3df2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/get_detections_role.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XGET ${KIBANA_URL}/api/security/role/hunter | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_role.sh new file mode 100755 index 0000000000000..11efa658fcdd2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_role.sh @@ -0,0 +1,13 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +ROLE=(${@:-./detections_role.json}) + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XPUT ${KIBANA_URL}/api/security/role/hunter \ +-d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_user.sh new file mode 100755 index 0000000000000..75f21b8017204 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/post_detections_user.sh @@ -0,0 +1,13 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +USER=(${@:-./detections_user.json}) + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + ${ELASTICSEARCH_URL}/_security/user/hunter \ +-d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/README.md new file mode 100644 index 0000000000000..b9173c973abab --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/README.md @@ -0,0 +1,5 @@ +essentially a superuser for security solution + +| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Action Connectors | Signals/Alerts | +| :------------------------------------------: | :----------: | :------------------: | :---: | :--------------: | :---------------: | :------------: | +| Platform Engineer (data ingest, cluster ops) | all | all | all | read, write | all | all | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/delete_detections_user.sh new file mode 100755 index 0000000000000..2a7a56f42d98c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/delete_detections_user.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XDELETE ${ELASTICSEARCH_URL}/_security/user/platform_engineer diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json new file mode 100644 index 0000000000000..75001292242c3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json @@ -0,0 +1,39 @@ +{ + "elasticsearch": { + "cluster": ["manage"], + "indices": [ + { + "names": [".lists*", ".items*"], + "privileges": ["all"] + }, + { + "names": [ + "apm-*-transaction*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*" + ], + "privileges": ["all"] + }, + { + "names": [".siem-signals-*"], + "privileges": ["all"] + } + ] + }, + "kibana": [ + { + "feature": { + "ml": ["all"], + "siem": ["all"], + "actions": ["all"], + "builtInAlerts": ["all"], + "savedObjectsManagement": ["all"] + }, + "spaces": ["*"] + } + ] +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_user.json new file mode 100644 index 0000000000000..8c4eab8b05e6e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_user.json @@ -0,0 +1,6 @@ +{ + "password": "changeme", + "roles": ["platform_engineer"], + "full_name": "platform engineer", + "email": "detections-reader@example.com" +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/get_detections_role.sh new file mode 100755 index 0000000000000..b7a04beda8934 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/get_detections_role.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XGET ${KIBANA_URL}/api/security/role/platform_engineer | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_role.sh new file mode 100755 index 0000000000000..a6d7504bd8d5b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_role.sh @@ -0,0 +1,13 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +ROLE=(${@:-./detections_role.json}) + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XPUT ${KIBANA_URL}/api/security/role/platform_engineer \ +-d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_user.sh new file mode 100755 index 0000000000000..88217795da40b --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/post_detections_user.sh @@ -0,0 +1,14 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +USER=(${@:-./detections_user.json}) + + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + ${ELASTICSEARCH_URL}/_security/user/platform_engineer \ +-d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/README.md new file mode 100644 index 0000000000000..1d2ef736f580c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/README.md @@ -0,0 +1,5 @@ +rule author has the same privileges as hunter with the additional privileges of uploading value lists + +| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Action Connectors | Signals/Alerts | +| :-----------------------------------------: | :----------: | :------------------: | :---------: | :--------------: | :---------------: | :------------------------------: | +| Rule Author / Manager / Detections Engineer | read, write | read | read, write | read, write | read | read, write, view_index_metadata | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/delete_detections_user.sh new file mode 100755 index 0000000000000..66c49bd210135 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/delete_detections_user.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XDELETE ${ELASTICSEARCH_URL}/_security/user/rule_author diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json new file mode 100644 index 0000000000000..f4950a25fdb77 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json @@ -0,0 +1,37 @@ +{ + "elasticsearch": { + "cluster": [], + "indices": [ + { + "names": [ + "apm-*-transaction*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*", + ".lists*", + ".items*" + ], + "privileges": ["read", "write"] + }, + { + "names": [".siem-signals-*"], + "privileges": ["read", "write", "view_index_metadata"] + } + ] + }, + "kibana": [ + { + "feature": { + "ml": ["read"], + "siem": ["all"], + "actions": ["read"], + "builtInAlerts": ["all"], + "savedObjectsManagement": ["all"] + }, + "spaces": ["*"] + } + ] +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_user.json new file mode 100644 index 0000000000000..ae08072b5890e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_user.json @@ -0,0 +1,6 @@ +{ + "password": "changeme", + "roles": ["rule_author"], + "full_name": "rule author", + "email": "detections-reader@example.com" +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/get_detections_role.sh new file mode 100755 index 0000000000000..0aa8a5f70f4de --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/get_detections_role.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XGET ${KIBANA_URL}/api/security/role/rule_author | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_role.sh new file mode 100755 index 0000000000000..01c132c3f947f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_role.sh @@ -0,0 +1,13 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +ROLE=(${@:-./detections_role.json}) + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XPUT ${KIBANA_URL}/api/security/role/rule_author \ +-d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_user.sh new file mode 100755 index 0000000000000..63eb626f580d4 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/post_detections_user.sh @@ -0,0 +1,13 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +USER=(${@:-./detections_user.json}) + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + ${ELASTICSEARCH_URL}/_security/user/rule_author \ +-d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/README.md new file mode 100644 index 0000000000000..fef99dfed2fbb --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/README.md @@ -0,0 +1,5 @@ +SOC Manager has all of the privileges of a rule author role with the additional privilege of managing the signals index. It can't create the signals index though. + +| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Action Connectors | Signals/Alerts | +| :---------: | :----------: | :------------------: | :---------: | :--------------: | :---------------: | :-----------------: | +| SOC Manager | read, write | read | read, write | read, write | all | read, write, manage | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/delete_detections_user.sh new file mode 100755 index 0000000000000..5bc3e4401c015 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/delete_detections_user.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XDELETE ${ELASTICSEARCH_URL}/_security/user/soc_manager diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json new file mode 100644 index 0000000000000..a6cb64ef83ba7 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json @@ -0,0 +1,37 @@ +{ + "elasticsearch": { + "cluster": [], + "indices": [ + { + "names": [ + "apm-*-transaction*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*", + ".lists*", + ".items*" + ], + "privileges": ["read", "write"] + }, + { + "names": [".siem-signals-*"], + "privileges": ["read", "write", "manage"] + } + ] + }, + "kibana": [ + { + "feature": { + "ml": ["read"], + "siem": ["all"], + "actions": ["all"], + "builtInAlerts": ["all"], + "savedObjectsManagement": ["all"] + }, + "spaces": ["*"] + } + ] +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_user.json new file mode 100644 index 0000000000000..18c7cc2312bf5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_user.json @@ -0,0 +1,6 @@ +{ + "password": "changeme", + "roles": ["soc_manager"], + "full_name": "SOC manager", + "email": "detections-reader@example.com" +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/get_detections_role.sh new file mode 100755 index 0000000000000..a93911573d374 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/get_detections_role.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XGET ${KIBANA_URL}/api/security/role/soc_manager | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_role.sh new file mode 100755 index 0000000000000..313011859c487 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_role.sh @@ -0,0 +1,14 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +ROLE=(${@:-./detections_role.json}) + + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XPUT ${KIBANA_URL}/api/security/role/soc_manager \ +-d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_user.sh new file mode 100755 index 0000000000000..c0928dbeb15ed --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/post_detections_user.sh @@ -0,0 +1,14 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +USER=(${@:-./detections_user.json}) + + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + ${ELASTICSEARCH_URL}/_security/user/soc_manager \ +-d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/README.md new file mode 100644 index 0000000000000..9ba0deba763aa --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/README.md @@ -0,0 +1,3 @@ +| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Actions Connectors | Signals/Alerts | +| :--------: | :----------: | :------------------: | :---: | :--------------: | :----------------: | :------------: | +| T1 Analyst | read | read | none | read | read | read, write | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/delete_detections_user.sh new file mode 100755 index 0000000000000..d0f1773c30cc7 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/delete_detections_user.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XDELETE ${ELASTICSEARCH_URL}/_security/user/t1_analyst diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json new file mode 100644 index 0000000000000..87be597e4bdb5 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json @@ -0,0 +1,32 @@ +{ + "elasticsearch": { + "cluster": [], + "indices": [ + { "names": [".siem-signals-*"], "privileges": ["read", "write"] }, + { + "names": [ + "apm-*-transaction*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*" + ], + "privileges": ["read"] + } + ] + }, + "kibana": [ + { + "feature": { + "ml": ["read"], + "siem": ["all"], + "actions": ["read"], + "builtInAlerts": ["read"], + "savedObjectsManagement": ["read"] + }, + "spaces": ["*"] + } + ] +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_user.json new file mode 100644 index 0000000000000..203abec8ad433 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_user.json @@ -0,0 +1,6 @@ +{ + "password": "changeme", + "roles": ["t1_analyst"], + "full_name": "T1 Analyst", + "email": "detections-reader@example.com" +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/get_detections_role.sh new file mode 100755 index 0000000000000..3570a3fc49947 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/get_detections_role.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XGET ${KIBANA_URL}/api/security/role/t1_analyst | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_role.sh new file mode 100755 index 0000000000000..da0f03b5916f3 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_role.sh @@ -0,0 +1,14 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +# Uses a default if no argument is specified +ROLE=(${@:-./detections_role.json}) + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XPUT ${KIBANA_URL}/api/security/role/t1_analyst \ +-d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_user.sh new file mode 100755 index 0000000000000..6ae5521a43638 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/post_detections_user.sh @@ -0,0 +1,13 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +USER=(${@:-./detections_user.json}) + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + ${ELASTICSEARCH_URL}/_security/user/t1_analyst \ +-d @${USER} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/README.md new file mode 100644 index 0000000000000..3988e88870755 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/README.md @@ -0,0 +1,5 @@ +This role can view rules. Essentially there is no difference between a T1 and T2 analyst. + +| Role | Data Sources | Security Solution ML Jobs/Results | Lists | Rules/Exceptions | Action Connectors | Signals/Alerts | +| :--------: | :----------: | :------------------: | :---: | :--------------: | :---------------: | :------------: | +| T2 Analyst | read | read | read | read | read | read, write | diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/delete_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/delete_detections_user.sh new file mode 100755 index 0000000000000..487c66064ce42 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/delete_detections_user.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -v -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XDELETE ${ELASTICSEARCH_URL}/_security/user/t2_analyst diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json new file mode 100644 index 0000000000000..18ada2ef7ab21 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json @@ -0,0 +1,34 @@ +{ + "elasticsearch": { + "cluster": [], + "indices": [ + { "names": [".siem-signals-*"], "privileges": ["read", "write"] }, + { + "names": [ + ".lists*", + ".items*", + "apm-*-transaction*", + "auditbeat-*", + "endgame-*", + "filebeat-*", + "logs-*", + "packetbeat-*", + "winlogbeat-*" + ], + "privileges": ["read"] + } + ] + }, + "kibana": [ + { + "feature": { + "ml": ["read"], + "siem": ["all"], + "actions": ["read"], + "builtInAlerts": ["read"], + "savedObjectsManagement": ["read"] + }, + "spaces": ["*"] + } + ] +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_user.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_user.json new file mode 100644 index 0000000000000..3f5da2752314f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_user.json @@ -0,0 +1,6 @@ +{ + "password": "changeme", + "roles": ["t2_analyst"], + "full_name": "t2 analyst", + "email": "detections-reader@example.com" +} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/get_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/get_detections_role.sh new file mode 100755 index 0000000000000..8625211591303 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/get_detections_role.sh @@ -0,0 +1,10 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XGET ${KIBANA_URL}/api/security/role/t2_analyst | jq -S . diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_role.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_role.sh new file mode 100755 index 0000000000000..67f971f4b6de6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_role.sh @@ -0,0 +1,13 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +ROLE=(${@:-./detections_role.json}) + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ +-XPUT ${KIBANA_URL}/api/security/role/t2_analyst \ +-d @${ROLE} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_user.sh b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_user.sh new file mode 100755 index 0000000000000..45f20381d2738 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/post_detections_user.sh @@ -0,0 +1,13 @@ + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +USER=(${@:-./detections_user.json}) + +curl -H 'Content-Type: application/json' -H 'kbn-xsrf: 123'\ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + ${ELASTICSEARCH_URL}/_security/user/t2_analyst \ +-d @${USER} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 41f6b66c30aaf..cf7fc9edd9529 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -91,6 +91,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { }); expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.eql(Date.parse(response.body.createdAt)); expect(typeof response.body.scheduledTaskId).to.be('string'); const { _source: taskRecord } = await getScheduledTask(response.body.scheduledTaskId); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts index 5ebce8edf6fb7..642173a7c2c6c 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/execution_status.ts @@ -63,6 +63,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; + const alertUpdatedAt = response.body.updatedAt; dates.push(response.body.executionStatus.lastExecutionDate); objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); @@ -70,6 +71,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon dates.push(executionStatus.lastExecutionDate); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); + ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -97,6 +99,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; + const alertUpdatedAt = response.body.updatedAt; dates.push(response.body.executionStatus.lastExecutionDate); objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); @@ -104,6 +107,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon dates.push(executionStatus.lastExecutionDate); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); + ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -128,6 +132,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; + const alertUpdatedAt = response.body.updatedAt; dates.push(response.body.executionStatus.lastExecutionDate); objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); @@ -135,6 +140,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon dates.push(executionStatus.lastExecutionDate); dates.push(Date.now()); ensureDatetimesAreOrdered(dates); + ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); // Ensure AAD isn't broken await checkAAD({ @@ -162,12 +168,14 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; + const alertUpdatedAt = response.body.updatedAt; objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); const executionStatus = await waitForStatus(alertId, new Set(['error'])); expect(executionStatus.error).to.be.ok(); expect(executionStatus.error.reason).to.be('execute'); expect(executionStatus.error.message).to.be('this alert is intended to fail'); + ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); }); it('should eventually have error reason "unknown" when appropriate', async () => { @@ -183,6 +191,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon ); expect(response.status).to.eql(200); const alertId = response.body.id; + const alertUpdatedAt = response.body.updatedAt; objectRemover.add(Spaces.space1.id, alertId, 'alert', 'alerts'); let executionStatus = await waitForStatus(alertId, new Set(['ok'])); @@ -201,6 +210,7 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon executionStatus = await waitForStatus(alertId, new Set(['error'])); expect(executionStatus.error).to.be.ok(); expect(executionStatus.error.reason).to.be('unknown'); + ensureAlertUpdatedAtHasNotChanged(alertId, alertUpdatedAt); const message = 'params invalid: [param1]: expected value of type [string] but got [number]'; expect(executionStatus.error.message).to.be(message); @@ -306,6 +316,18 @@ export default function executionStatusAlertTests({ getService }: FtrProviderCon await delay(WaitForStatusIncrement); return await waitForStatus(id, statuses, waitMillis - WaitForStatusIncrement); } + + async function ensureAlertUpdatedAtHasNotChanged(alertId: string, originalUpdatedAt: string) { + const response = await supertest.get( + `${getUrlPrefix(Spaces.space1.id)}/api/alerts/alert/${alertId}` + ); + const { updatedAt, executionStatus } = response.body; + expect(Date.parse(updatedAt)).to.be.greaterThan(0); + expect(Date.parse(updatedAt)).to.eql(Date.parse(originalUpdatedAt)); + expect(Date.parse(executionStatus.lastExecutionDate)).to.be.greaterThan( + Date.parse(originalUpdatedAt) + ); + } } function expectErrorExecutionStatus(executionStatus: Record, startDate: number) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts index 17070a14069ce..bd6afacf206d9 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/migrations.ts @@ -82,5 +82,14 @@ export default function createGetTests({ getService }: FtrProviderContext) { }, ]); }); + + it('7.11.0 migrates alerts to contain `updatedAt` field', async () => { + const response = await supertest.get( + `${getUrlPrefix(``)}/api/alerts/alert/74f3e6d7-b7bb-477d-ac28-92ee22728e6e` + ); + + expect(response.status).to.eql(200); + expect(response.body.updatedAt).to.eql('2020-06-17T15:35:39.839Z'); + }); }); } diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/roles_users_utils/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/roles_users_utils/index.ts new file mode 100644 index 0000000000000..5098ff157b116 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/roles_users_utils/index.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t1AnalystUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_user.json'; +import * as t2AnalystUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_user.json'; +import * as hunterUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_user.json'; +import * as ruleAuthorUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_user.json'; +import * as socManagerUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_user.json'; +import * as platformEngineerUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_user.json'; +import * as detectionsAdminUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_user.json'; + +import * as t1AnalystRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json'; +import * as t2AnalystRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json'; +import * as hunterRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json'; +import * as ruleAuthorRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json'; +import * as socManagerRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json'; +import * as platformEngineerRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json'; +import * as detectionsAdminRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json'; + +import { ROLES } from '../../../../plugins/security_solution/common/test'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +export const createUserAndRole = async ( + securityService: ReturnType, + role: keyof typeof ROLES +) => { + switch (role) { + case ROLES.detections_admin: + await postRoleAndUser( + ROLES.detections_admin, + detectionsAdminRole, + detectionsAdminUser, + securityService + ); + break; + case ROLES.t1_analyst: + await postRoleAndUser(ROLES.t1_analyst, t1AnalystRole, t1AnalystUser, securityService); + break; + case ROLES.t2_analyst: + await postRoleAndUser(ROLES.t2_analyst, t2AnalystRole, t2AnalystUser, securityService); + break; + case ROLES.hunter: + await postRoleAndUser(ROLES.hunter, hunterRole, hunterUser, securityService); + break; + case ROLES.rule_author: + await postRoleAndUser(ROLES.rule_author, ruleAuthorRole, ruleAuthorUser, securityService); + break; + case ROLES.soc_manager: + await postRoleAndUser(ROLES.soc_manager, socManagerRole, socManagerUser, securityService); + break; + case ROLES.platform_engineer: + await postRoleAndUser( + ROLES.platform_engineer, + platformEngineerRole, + platformEngineerUser, + securityService + ); + break; + default: + break; + } +}; + +interface UserInterface { + password: string; + roles: string[]; + full_name: string; + email: string; +} + +interface RoleInterface { + elasticsearch: { + cluster: string[]; + indices: Array<{ + names: string[]; + privileges: string[]; + }>; + }; + kibana: Array<{ + feature: { + ml: string[]; + siem: string[]; + actions: string[]; + builtInAlerts: string[]; + savedObjectsManagement: string[]; + }; + spaces: string[]; + }>; +} + +export const postRoleAndUser = async ( + roleName: string, + role: RoleInterface, + user: UserInterface, + securityService: ReturnType +) => { + await securityService.role.create(roleName, { + kibana: role.kibana, + elasticsearch: role.elasticsearch, + }); + await securityService.user.create(roleName, { + password: 'changeme', + full_name: user.full_name, + roles: user.roles, + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts index d2a3e86526db4..bbc3943b75955 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/open_close_signals.ts @@ -25,12 +25,16 @@ import { waitForSignalsToBePresent, getAllSignals, } from '../../utils'; +import { createUserAndRole } from '../roles_users_utils'; +import { ROLES } from '../../../../plugins/security_solution/common/test'; // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const es = getService('es'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const securityService = getService('security'); describe('open_close_signals', () => { describe('validation checks', () => { @@ -157,6 +161,79 @@ export default ({ getService }: FtrProviderContext) => { ); expect(everySignalClosed).to.eql(true); }); + + it('should NOT be able to close signals with t1 analyst user', async () => { + const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest); + await createUserAndRole(securityService, ROLES.t1_analyst); + const signalsOpen = await getAllSignals(supertest); + const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); + + // Try to set all of the signals to the state of closed. + // This should not be possible with the given user. + await supertestWithoutAuth + .post(DETECTION_ENGINE_SIGNALS_STATUS_URL) + .set('kbn-xsrf', 'true') + .auth(ROLES.t1_analyst, 'changeme') + .send(setSignalStatus({ signalIds, status: 'closed' })) + .expect(403); + + // query for the signals with the superuser + // to allow a check that the signals were NOT closed with t1 analyst + const { + body: signalsClosed, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQuerySignalIds(signalIds)) + .expect(200); + + const everySignalOpen = signalsClosed.hits.hits.every( + ({ + _source: { + signal: { status }, + }, + }) => status === 'open' + ); + expect(everySignalOpen).to.eql(true); + }); + + it('should be able to close signals with soc_manager user', async () => { + const rule = { ...getSimpleRule(), from: '1900-01-01T00:00:00.000Z', query: '*:*' }; + await createRule(supertest, rule); + await waitForSignalsToBePresent(supertest); + const userAndRole = ROLES.soc_manager; + await createUserAndRole(securityService, userAndRole); + const signalsOpen = await getAllSignals(supertest); + const signalIds = signalsOpen.hits.hits.map((signal) => signal._id); + + // Try to set all of the signals to the state of closed. + // This should not be possible with the given user. + await supertestWithoutAuth + .post(DETECTION_ENGINE_SIGNALS_STATUS_URL) + .set('kbn-xsrf', 'true') + .auth(userAndRole, 'changeme') // each user has the same password + .send(setSignalStatus({ signalIds, status: 'closed' })) + .expect(200); + + const { + body: signalsClosed, + }: { body: SearchResponse<{ signal: Signal }> } = await supertest + .post(DETECTION_ENGINE_QUERY_SIGNALS_URL) + .set('kbn-xsrf', 'true') + .send(getQuerySignalIds(signalIds)) + .expect(200); + + const everySignalClosed = signalsClosed.hits.hits.every( + ({ + _source: { + signal: { status }, + }, + }) => status === 'closed' + ); + expect(everySignalClosed).to.eql(true); + }); }); }); }); diff --git a/x-pack/test/security_solution_cypress/es_archives/custom_rules/data.json.gz b/x-pack/test/security_solution_cypress/es_archives/custom_rules/data.json.gz index 4a8fdf53fa9a1..fb262155ea03a 100644 Binary files a/x-pack/test/security_solution_cypress/es_archives/custom_rules/data.json.gz and b/x-pack/test/security_solution_cypress/es_archives/custom_rules/data.json.gz differ diff --git a/x-pack/test/security_solution_cypress/es_archives/custom_rules/mappings.json b/x-pack/test/security_solution_cypress/es_archives/custom_rules/mappings.json index 5869964991ba7..d416926a40fa6 100644 --- a/x-pack/test/security_solution_cypress/es_archives/custom_rules/mappings.json +++ b/x-pack/test/security_solution_cypress/es_archives/custom_rules/mappings.json @@ -321,6 +321,9 @@ "throttle": { "type": "keyword" }, + "updatedAt": { + "type": "date" + }, "updatedBy": { "type": "keyword" }, diff --git a/x-pack/test/security_solution_cypress/es_archives/export_rule/data.json.gz b/x-pack/test/security_solution_cypress/es_archives/export_rule/data.json.gz index aad07a0bf6d53..c9739a7725293 100644 Binary files a/x-pack/test/security_solution_cypress/es_archives/export_rule/data.json.gz and b/x-pack/test/security_solution_cypress/es_archives/export_rule/data.json.gz differ diff --git a/x-pack/test/security_solution_cypress/es_archives/export_rule/mappings.json b/x-pack/test/security_solution_cypress/es_archives/export_rule/mappings.json index 5eec03ca3d11a..757121df53d44 100644 --- a/x-pack/test/security_solution_cypress/es_archives/export_rule/mappings.json +++ b/x-pack/test/security_solution_cypress/es_archives/export_rule/mappings.json @@ -191,6 +191,9 @@ "throttle": { "type": "keyword" }, + "updatedAt": { + "type": "date" + }, "updatedBy": { "type": "keyword" } diff --git a/x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/data.json.gz b/x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/data.json.gz index cac63ed9c585f..0bec997503146 100644 Binary files a/x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/data.json.gz and b/x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/data.json.gz differ diff --git a/x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/mappings.json b/x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/mappings.json index f4278c4d4318f..7ef00495390ee 100644 --- a/x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/mappings.json +++ b/x-pack/test/security_solution_cypress/es_archives/prebuilt_rules_loaded/mappings.json @@ -322,6 +322,9 @@ "throttle": { "type": "keyword" }, + "updatedAt": { + "type": "date" + }, "updatedBy": { "type": "keyword" } diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index ccdc2fa4424ac..a1a1a3916ef7f 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -28,9 +28,20 @@ export async function SecuritySolutionCypressCliTestRunner({ getService }: FtrPr FORCE_COLOR: '1', // eslint-disable-next-line @typescript-eslint/naming-convention CYPRESS_baseUrl: Url.format(config.get('servers.kibana')), + // eslint-disable-next-line @typescript-eslint/naming-convention + CYPRESS_protocol: config.get('servers.kibana.protocol'), + // eslint-disable-next-line @typescript-eslint/naming-convention + CYPRESS_hostname: config.get('servers.kibana.hostname'), + // eslint-disable-next-line @typescript-eslint/naming-convention + CYPRESS_configport: config.get('servers.kibana.port'), CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')), CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'), CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'), + CYPRESS_KIBANA_URL: Url.format({ + protocol: config.get('servers.kibana.protocol'), + hostname: config.get('servers.kibana.hostname'), + port: config.get('servers.kibana.port'), + }), ...process.env, }, wait: true, @@ -55,9 +66,20 @@ export async function SecuritySolutionCypressVisualTestRunner({ getService }: Ft FORCE_COLOR: '1', // eslint-disable-next-line @typescript-eslint/naming-convention CYPRESS_baseUrl: Url.format(config.get('servers.kibana')), + // eslint-disable-next-line @typescript-eslint/naming-convention + CYPRESS_protocol: config.get('servers.kibana.protocol'), + // eslint-disable-next-line @typescript-eslint/naming-convention + CYPRESS_hostname: config.get('servers.kibana.hostname'), + // eslint-disable-next-line @typescript-eslint/naming-convention + CYPRESS_configport: config.get('servers.kibana.port'), CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')), CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'), CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'), + CYPRESS_KIBANA_URL: Url.format({ + protocol: config.get('servers.kibana.protocol'), + hostname: config.get('servers.kibana.hostname'), + port: config.get('servers.kibana.port'), + }), ...process.env, }, wait: true, diff --git a/yarn.lock b/yarn.lock index 9be39ea18e3d1..b2acb219343d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18798,28 +18798,16 @@ livereload-js@^2.3.0: resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c" integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw== -lmdb-store-0.9@0.7.3: - version "0.7.3" - resolved "https://registry.yarnpkg.com/lmdb-store-0.9/-/lmdb-store-0.9-0.7.3.tgz#c2cb27dfa916ab966cceed692c67e4236813104a" - integrity sha512-t8iCnN6T3NZPFelPmjYIjCg+nhGbOdc0xcHCG40v01AWRTN49OINSt2k/u+16/2/HrI+b6Ssb8WByXUhbyHz6w== - dependencies: - fs-extra "^9.0.1" - msgpackr "^0.5.3" - nan "^2.14.1" - node-gyp-build "^4.2.3" - weak-lru-cache "^0.3.9" - -lmdb-store@^0.8.15: - version "0.8.15" - resolved "https://registry.yarnpkg.com/lmdb-store/-/lmdb-store-0.8.15.tgz#4efb0341c2df505dd6f3a7f26f834f0a142a80a2" - integrity sha512-4Q0WZh2FmcJC6esZRUWMfkCmNiz0WU9cOgrxt97ZMTnVfHyOdZhtrt0oOF5EQPfetxxJf/BorKY28aX92R6G6g== +lmdb-store@^0.6.10: + version "0.6.10" + resolved "https://registry.yarnpkg.com/lmdb-store/-/lmdb-store-0.6.10.tgz#db8efde6e052aabd17ebc63c8a913e1f31694129" + integrity sha512-ZLvp3qbBQ5VlBmaWa4EUAPyYEZ8qdUHsW69HmxkDi84pFQ37WMxYhFaF/7PQkdtxS/vyiKkZigd9TFgHjek1Nw== dependencies: fs-extra "^9.0.1" - lmdb-store-0.9 "0.7.3" - msgpackr "^0.5.4" + msgpackr "^0.5.0" nan "^2.14.1" node-gyp-build "^4.2.3" - weak-lru-cache "^0.3.9" + weak-lru-cache "^0.2.0" load-bmfont@^1.3.1, load-bmfont@^1.4.0: version "1.4.0" @@ -20335,20 +20323,20 @@ ms@2.1.1, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -msgpackr-extract@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-0.3.5.tgz#0f206da058bd3dad0f8605d324de001a8f4de967" - integrity sha512-zHhstybu+m/j3H6CVBMcILVIzATK6dWRGtlePJjsnSAj8kLT5joMa9i0v21Uc80BPNDcwFsnG/dz2318tfI81w== +msgpackr-extract@^0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-0.3.4.tgz#8ee5e73d1135340e564c498e8c593134365eb060" + integrity sha512-d3+qwTJzgqqsq2L2sQuH0SoO4StvpUhMqMAKy6tMimn7XdBaRtDlquFzRJsp0iMGt2hnU4UOqD8Tz9mb0KglTA== dependencies: nan "^2.14.1" node-gyp-build "^4.2.3" -msgpackr@^0.5.3, msgpackr@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-0.5.4.tgz#c21c03d5e132d2e54d0b9ced02a75b1f48413380" - integrity sha512-ILEWtIWwd5ESWHKoVjJ4GP7JWkpuAUJ20qi2j2qEC6twecBmK4E6YG3QW847OpmvdAhMJGq2LoDJRn/kNERTeQ== +msgpackr@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-0.5.1.tgz#7eecbf342645b7718dd2e3386894368d06732b3f" + integrity sha512-nK2uJl67Q5KU3MWkYBUlYynqKS1UUzJ5M1h6TQejuJtJzD3hW2Suv2T1pf01E9lUEr93xaLokf/xC+jwBShMPQ== optionalDependencies: - msgpackr-extract "^0.3.5" + msgpackr-extract "^0.3.4" multicast-dns-service-types@^1.1.0: version "1.1.0" @@ -29141,10 +29129,10 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" -weak-lru-cache@^0.3.9: - version "0.3.9" - resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-0.3.9.tgz#9e56920d4115e8542625d8ef8cc278cbd97f7624" - integrity sha512-WqAu3wzbHQvjSi/vgYhidZkf2p7L3Z8iDEIHnqvE31EQQa7Vh7PDOphrRJ1oxlW8JIjgr2HvMcRe9Q1GhW2NPw== +weak-lru-cache@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-0.2.0.tgz#447379ccff6dfda1b7a9566c9ef168260be859d1" + integrity sha512-M1l5CzKvM7maa7tCbtL0NW6sOnp8gqup853+9Aq7GL0XNWKNnFOkeE3v3Z5X2IeMzedPwQyPbi4RlFvD6rxs7A== web-namespaces@^1.0.0: version "1.1.4"