diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index 83408d2121c56..f581c21901ebd 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -159,6 +159,7 @@ enabled: - x-pack/test/examples/config.ts - x-pack/test/fleet_api_integration/config.ts - x-pack/test/fleet_functional/config.ts + - x-pack/test/ftr_apis/security_and_spaces/config.ts - x-pack/test/functional_basic/config.ts - x-pack/test/functional_cors/config.ts - x-pack/test/functional_embedded/config.ts diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 06666957c9ee4..4d0e8e1999749 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -184,6 +184,10 @@ for use in their own application. |Minimal interface for admins to manage files in Kibana. +|{kib-repo}blob/{branch}/src/plugins/ftr_apis/README.md[ftrApis] +|This plugin exposes a set of APIs used internally during functional tests by the FTR. + + |{kib-repo}blob/{branch}/src/plugins/guided_onboarding/README.md[guidedOnboarding] |This plugin contains the code for the Guided Onboarding project. Guided onboarding consists of guides for Solutions (Enterprise Search, Observability, Security) that can be completed as a checklist of steps. The guides help users to ingest their data and to navigate to the correct Solutions pages. diff --git a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts index 33360bf82ef0c..b896aef3d2f56 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_saved_objects.ts @@ -7,7 +7,6 @@ */ import { inspect } from 'util'; - import * as Rx from 'rxjs'; import { mergeMap } from 'rxjs/operators'; import { isAxiosResponseError } from '@kbn/dev-utils'; @@ -90,6 +89,11 @@ async function concurrently(maxConcurrency: number, arr: T[], fn: (item: T) = } } +/** + * SO client for FTR. + * + * @remarks: Leverage the `ftrApis` plugin under the hood. + */ export class KbnClientSavedObjects { constructor(private readonly log: ToolingLog, private readonly requester: KbnClientRequester) {} @@ -117,8 +121,8 @@ export class KbnClientSavedObjects { const { data } = await this.requester.request>({ description: 'get saved object', path: options.space - ? uriencode`/s/${options.space}/api/saved_objects/${options.type}/${options.id}` - : uriencode`/api/saved_objects/${options.type}/${options.id}`, + ? uriencode`/s/${options.space}/internal/ftr/kbn_client_so/${options.type}/${options.id}` + : uriencode`/internal/ftr/kbn_client_so/${options.type}/${options.id}`, method: 'GET', }); return data; @@ -133,8 +137,8 @@ export class KbnClientSavedObjects { const { data } = await this.requester.request>({ description: 'update saved object', path: options.id - ? uriencode`/api/saved_objects/${options.type}/${options.id}` - : uriencode`/api/saved_objects/${options.type}`, + ? uriencode`/internal/ftr/kbn_client_so/${options.type}/${options.id}` + : uriencode`/internal/ftr/kbn_client_so/${options.type}`, query: { overwrite: options.overwrite, }, @@ -156,7 +160,7 @@ export class KbnClientSavedObjects { const { data } = await this.requester.request>({ description: 'update saved object', - path: uriencode`/api/saved_objects/${options.type}/${options.id}`, + path: uriencode`/internal/ftr/kbn_client_so/${options.type}/${options.id}`, query: { overwrite: options.overwrite, }, @@ -179,8 +183,8 @@ export class KbnClientSavedObjects { const { data } = await this.requester.request({ description: 'delete saved object', path: options.space - ? uriencode`/s/${options.space}/api/saved_objects/${options.type}/${options.id}` - : uriencode`/api/saved_objects/${options.type}/${options.id}`, + ? uriencode`/s/${options.space}/internal/ftr/kbn_client_so/${options.type}/${options.id}` + : uriencode`/internal/ftr/kbn_client_so/${options.type}/${options.id}`, method: 'DELETE', }); @@ -196,8 +200,8 @@ export class KbnClientSavedObjects { const resp = await this.requester.request({ method: 'GET', path: options.space - ? uriencode`/s/${options.space}/api/saved_objects/_find` - : '/api/saved_objects/_find', + ? uriencode`/s/${options.space}/internal/ftr/kbn_client_so/_find` + : `/internal/ftr/kbn_client_so/_find`, query: { per_page: 1000, type: options.types, @@ -270,8 +274,8 @@ export class KbnClientSavedObjects { await this.requester.request({ method: 'DELETE', path: options.space - ? uriencode`/s/${options.space}/api/saved_objects/${obj.type}/${obj.id}?force=true` - : uriencode`/api/saved_objects/${obj.type}/${obj.id}?force=true`, + ? uriencode`/s/${options.space}/internal/ftr/kbn_client_so/${obj.type}/${obj.id}` + : uriencode`/internal/ftr/kbn_client_so/${obj.type}/${obj.id}`, }); deleted++; } catch (error) { diff --git a/src/plugins/ftr_apis/README.md b/src/plugins/ftr_apis/README.md new file mode 100644 index 0000000000000..fc620c9ac4f56 --- /dev/null +++ b/src/plugins/ftr_apis/README.md @@ -0,0 +1,8 @@ +# ftrApis plugin + +This plugin exposes a set of APIs used internally during functional tests by the FTR. + +The APIs currently exposed are: +1. APIs used by the `KbnClientSavedObjects` (SO service of the FTR) + +**Remark: these APIs shouldn't be called directly for any reason** diff --git a/src/plugins/ftr_apis/jest.config.js b/src/plugins/ftr_apis/jest.config.js new file mode 100644 index 0000000000000..e9dd449c2e308 --- /dev/null +++ b/src/plugins/ftr_apis/jest.config.js @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/ftr_apis'], + coverageDirectory: '/target/kibana-coverage/jest/src/plugins/ftr_apis', + coverageReporters: ['text', 'html'], + collectCoverageFrom: ['/src/plugins/ftr_apis/{common,public,server}/**/*.{js,ts,tsx}'], +}; diff --git a/src/plugins/ftr_apis/kibana.json b/src/plugins/ftr_apis/kibana.json new file mode 100644 index 0000000000000..44e77c3900b31 --- /dev/null +++ b/src/plugins/ftr_apis/kibana.json @@ -0,0 +1,11 @@ +{ + "id": "ftrApis", + "owner": { + "name": "Core", + "githubTeam": "kibana-core" + }, + "version": "kibana", + "configPath": ["ftr_apis"], + "server": true, + "ui": false +} diff --git a/src/plugins/ftr_apis/server/config.ts b/src/plugins/ftr_apis/server/config.ts new file mode 100644 index 0000000000000..14d2cbafed9c8 --- /dev/null +++ b/src/plugins/ftr_apis/server/config.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema, type TypeOf } from '@kbn/config-schema'; +import type { PluginConfigDescriptor } from '@kbn/core/server'; + +const configSchema = schema.object({ + disableApis: schema.boolean({ defaultValue: false }), +}); + +export type ConfigType = TypeOf; + +export const config: PluginConfigDescriptor = { + schema: configSchema, +}; diff --git a/src/plugins/ftr_apis/server/index.ts b/src/plugins/ftr_apis/server/index.ts new file mode 100644 index 0000000000000..5c33f6c4a067a --- /dev/null +++ b/src/plugins/ftr_apis/server/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { PluginInitializerContext } from '@kbn/core/server'; +import { FtrApisPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new FtrApisPlugin(initializerContext); +} + +export { config } from './config'; diff --git a/src/plugins/ftr_apis/server/plugin.ts b/src/plugins/ftr_apis/server/plugin.ts new file mode 100644 index 0000000000000..42fd40212f346 --- /dev/null +++ b/src/plugins/ftr_apis/server/plugin.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import { registerRoutes } from './routes'; +import type { ConfigType } from './config'; + +export class FtrApisPlugin implements Plugin { + private readonly config: ConfigType; + + constructor(initializerContext: PluginInitializerContext) { + this.config = initializerContext.config.get(); + } + + public setup({ http, savedObjects }: CoreSetup) { + const router = http.createRouter(); + if (!this.config.disableApis) { + registerRoutes(router); + } + } + + public start() {} +} diff --git a/src/plugins/ftr_apis/server/routes/index.ts b/src/plugins/ftr_apis/server/routes/index.ts new file mode 100644 index 0000000000000..ae71eb3ee2673 --- /dev/null +++ b/src/plugins/ftr_apis/server/routes/index.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IRouter } from '@kbn/core/server'; +import { registerKbnClientSoRoutes } from './kbn_client_so'; + +export const registerRoutes = (router: IRouter) => { + registerKbnClientSoRoutes(router); +}; diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/bulk_delete.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/bulk_delete.ts new file mode 100644 index 0000000000000..1d0af8c799c97 --- /dev/null +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/bulk_delete.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IRouter } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; +import { KBN_CLIENT_API_PREFIX, listHiddenTypes, catchAndReturnBoomErrors } from './utils'; + +export const registerBulkDeleteRoute = (router: IRouter) => { + router.post( + { + path: `${KBN_CLIENT_API_PREFIX}/_bulk_delete`, + options: { + tags: ['access:ftrApis'], + }, + validate: { + body: schema.arrayOf( + schema.object({ + type: schema.string(), + id: schema.string(), + }) + ), + }, + }, + catchAndReturnBoomErrors(async (ctx, req, res) => { + const { savedObjects } = await ctx.core; + const hiddenTypes = listHiddenTypes(savedObjects.typeRegistry); + const soClient = savedObjects.getClient({ includedHiddenTypes: hiddenTypes }); + + const statuses = await soClient.bulkDelete(req.body, { force: true }); + return res.ok({ body: statuses }); + }) + ); +}; diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/create.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/create.ts new file mode 100644 index 0000000000000..479c0403aaabd --- /dev/null +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/create.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IRouter } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; +import { KBN_CLIENT_API_PREFIX, listHiddenTypes, catchAndReturnBoomErrors } from './utils'; + +export const registerCreateRoute = (router: IRouter) => { + router.post( + { + path: `${KBN_CLIENT_API_PREFIX}/{type}/{id?}`, + options: { + tags: ['access:ftrApis'], + }, + validate: { + params: schema.object({ + type: schema.string(), + id: schema.maybe(schema.string()), + }), + query: schema.object({ + overwrite: schema.boolean({ defaultValue: false }), + }), + body: schema.object({ + attributes: schema.recordOf(schema.string(), schema.any()), + migrationVersion: schema.maybe(schema.recordOf(schema.string(), schema.string())), + references: schema.maybe( + schema.arrayOf( + schema.object({ + name: schema.string(), + type: schema.string(), + id: schema.string(), + }) + ) + ), + }), + }, + }, + catchAndReturnBoomErrors(async (ctx, req, res) => { + const { type, id } = req.params; + const { overwrite } = req.query; + const { attributes, migrationVersion, references } = req.body; + const { savedObjects } = await ctx.core; + + const hiddenTypes = listHiddenTypes(savedObjects.typeRegistry); + const soClient = savedObjects.getClient({ includedHiddenTypes: hiddenTypes }); + + const options = { + id, + overwrite, + migrationVersion, + references, + }; + const result = await soClient.create(type, attributes, options); + return res.ok({ body: result }); + }) + ); +}; diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/delete.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/delete.ts new file mode 100644 index 0000000000000..35d16611c48dd --- /dev/null +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/delete.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IRouter } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; +import { KBN_CLIENT_API_PREFIX, listHiddenTypes, catchAndReturnBoomErrors } from './utils'; + +export const registerDeleteRoute = (router: IRouter) => { + router.delete( + { + path: `${KBN_CLIENT_API_PREFIX}/{type}/{id}`, + options: { + tags: ['access:ftrApis'], + }, + validate: { + params: schema.object({ + type: schema.string(), + id: schema.string(), + }), + }, + }, + catchAndReturnBoomErrors(async (ctx, req, res) => { + const { type, id } = req.params; + const { savedObjects } = await ctx.core; + + const hiddenTypes = listHiddenTypes(savedObjects.typeRegistry); + const soClient = savedObjects.getClient({ includedHiddenTypes: hiddenTypes }); + + const result = await soClient.delete(type, id, { force: true }); + return res.ok({ body: result }); + }) + ); +}; diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/find.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/find.ts new file mode 100644 index 0000000000000..4ded164377646 --- /dev/null +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/find.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IRouter } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; +import { KBN_CLIENT_API_PREFIX, listHiddenTypes, catchAndReturnBoomErrors } from './utils'; + +export const registerFindRoute = (router: IRouter) => { + router.get( + { + path: `${KBN_CLIENT_API_PREFIX}/_find`, + options: { + tags: ['access:ftrApis'], + }, + validate: { + query: schema.object({ + per_page: schema.number({ min: 0, defaultValue: 20 }), + page: schema.number({ min: 0, defaultValue: 1 }), + type: schema.oneOf([schema.string(), schema.arrayOf(schema.string())]), + search: schema.maybe(schema.string()), + fields: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), + }), + }, + }, + catchAndReturnBoomErrors(async (ctx, req, res) => { + const query = req.query; + + const { savedObjects } = await ctx.core; + const hiddenTypes = listHiddenTypes(savedObjects.typeRegistry); + const soClient = savedObjects.getClient({ includedHiddenTypes: hiddenTypes }); + + const result = await soClient.find({ + perPage: query.per_page, + page: query.page, + type: Array.isArray(query.type) ? query.type : [query.type], + search: query.search, + fields: typeof query.fields === 'string' ? [query.fields] : query.fields, + }); + + return res.ok({ body: result }); + }) + ); +}; diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/get.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/get.ts new file mode 100644 index 0000000000000..ae03e528501a1 --- /dev/null +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/get.ts @@ -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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IRouter } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; +import { KBN_CLIENT_API_PREFIX, listHiddenTypes, catchAndReturnBoomErrors } from './utils'; + +export const registerGetRoute = (router: IRouter) => { + router.get( + { + path: `${KBN_CLIENT_API_PREFIX}/{type}/{id}`, + options: { + tags: ['access:ftrApis'], + }, + validate: { + params: schema.object({ + type: schema.string(), + id: schema.string(), + }), + }, + }, + catchAndReturnBoomErrors(async (ctx, req, res) => { + const { type, id } = req.params; + const { savedObjects } = await ctx.core; + + const hiddenTypes = listHiddenTypes(savedObjects.typeRegistry); + const soClient = savedObjects.getClient({ includedHiddenTypes: hiddenTypes }); + + const object = await soClient.get(type, id); + return res.ok({ body: object }); + }) + ); +}; diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/index.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/index.ts new file mode 100644 index 0000000000000..b22f234d8455b --- /dev/null +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IRouter } from '@kbn/core/server'; +import { registerBulkDeleteRoute } from './bulk_delete'; +import { registerCreateRoute } from './create'; +import { registerDeleteRoute } from './delete'; +import { registerFindRoute } from './find'; +import { registerGetRoute } from './get'; +import { registerUpdateRoute } from './update'; + +export const registerKbnClientSoRoutes = (router: IRouter) => { + registerBulkDeleteRoute(router); + registerCreateRoute(router); + registerDeleteRoute(router); + registerFindRoute(router); + registerGetRoute(router); + registerUpdateRoute(router); +}; diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/update.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/update.ts new file mode 100644 index 0000000000000..e6d568771631e --- /dev/null +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/update.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IRouter } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; +import { KBN_CLIENT_API_PREFIX, listHiddenTypes, catchAndReturnBoomErrors } from './utils'; + +export const registerUpdateRoute = (router: IRouter) => { + router.put( + { + path: `${KBN_CLIENT_API_PREFIX}/{type}/{id}`, + options: { + tags: ['access:ftrApis'], + }, + validate: { + params: schema.object({ + type: schema.string(), + id: schema.string(), + }), + body: schema.object({ + attributes: schema.recordOf(schema.string(), schema.any()), + migrationVersion: schema.maybe(schema.string()), + references: schema.maybe( + schema.arrayOf( + schema.object({ + name: schema.string(), + type: schema.string(), + id: schema.string(), + }) + ) + ), + }), + }, + }, + catchAndReturnBoomErrors(async (ctx, req, res) => { + const { type, id } = req.params; + const { attributes, migrationVersion, references } = req.body; + const { savedObjects } = await ctx.core; + + const hiddenTypes = listHiddenTypes(savedObjects.typeRegistry); + const soClient = savedObjects.getClient({ includedHiddenTypes: hiddenTypes }); + + const options = { version: migrationVersion, references }; + const result = await soClient.update(type, id, attributes, options); + return res.ok({ body: result }); + }) + ); +}; diff --git a/src/plugins/ftr_apis/server/routes/kbn_client_so/utils.ts b/src/plugins/ftr_apis/server/routes/kbn_client_so/utils.ts new file mode 100644 index 0000000000000..f4bab0fd7f31c --- /dev/null +++ b/src/plugins/ftr_apis/server/routes/kbn_client_so/utils.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Boom from '@hapi/boom'; +import type { ISavedObjectTypeRegistry, RequestHandlerWrapper } from '@kbn/core/server'; + +export const KBN_CLIENT_API_PREFIX = '/internal/ftr/kbn_client_so'; + +export const listHiddenTypes = (registry: ISavedObjectTypeRegistry): string[] => { + return registry + .getAllTypes() + .map((type) => type.name) + .filter((typeName) => registry.isHidden(typeName)); +}; + +export const catchAndReturnBoomErrors: RequestHandlerWrapper = (handler) => { + return async (context, request, response) => { + try { + return await handler(context, request, response); + } catch (e) { + if (Boom.isBoom(e) && e.output.statusCode !== 500) { + return response.customError({ + body: e.output.payload, + statusCode: e.output.statusCode, + headers: e.output.headers as { [key: string]: string }, + }); + } + throw e; + } + }; +}; diff --git a/src/plugins/ftr_apis/tsconfig.json b/src/plugins/ftr_apis/tsconfig.json new file mode 100644 index 0000000000000..30044452e0a33 --- /dev/null +++ b/src/plugins/ftr_apis/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "isolatedModules": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*" + ], + "exclude": [ + "target/**/*", + ], + "kbn_references": [ + "@kbn/core", + "@kbn/config-schema", + ] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 1d3dd3cd33b82..fd9ea4a507cc5 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -698,6 +698,8 @@ "@kbn/fleet-plugin/*": ["x-pack/plugins/fleet/*"], "@kbn/flot-charts": ["packages/kbn-flot-charts"], "@kbn/flot-charts/*": ["packages/kbn-flot-charts/*"], + "@kbn/ftr-apis-plugin": ["src/plugins/ftr_apis"], + "@kbn/ftr-apis-plugin/*": ["src/plugins/ftr_apis/*"], "@kbn/ftr-common-functional-services": ["packages/kbn-ftr-common-functional-services"], "@kbn/ftr-common-functional-services/*": ["packages/kbn-ftr-common-functional-services/*"], "@kbn/ftr-screenshot-filename": ["packages/kbn-ftr-screenshot-filename"], diff --git a/x-pack/test/ftr_apis/common/fixtures/es_archiver/base_data/default_space.json b/x-pack/test/ftr_apis/common/fixtures/es_archiver/base_data/default_space.json new file mode 100644 index 0000000000000..2591ad96d87a3 --- /dev/null +++ b/x-pack/test/ftr_apis/common/fixtures/es_archiver/base_data/default_space.json @@ -0,0 +1,142 @@ +{ + "id": "tag-1", + "type": "tag", + "attributes": { + "name": "tag-1", + "description": "My first tag!", + "color": "#FF00FF" + }, + "references": [], + "updated_at": "2021-06-17T18:57:58.076Z" +} + +{ + "id": "tag-2", + "type": "tag", + "attributes": { + "name": "tag-2", + "description": "Another awesome tag", + "color": "#123456" + }, + "references": [], + "updated_at": "2021-06-17T18:57:58.076Z" +} + +{ + "id": "tag-3", + "type": "tag", + "attributes": { + "name": "tag-3", + "description": "Last but not least", + "color": "#000000" + }, + "references": [], + "updated_at": "2021-06-17T18:57:58.076Z" +} + +{ + "id": "logstash-*", + "type": "index-pattern", + "attributes": { + "fieldFormatMap": "{\"bytes\":{\"id\":\"bytes\"}}", + "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-*" + }, + "references": [], + "updated_at": "2021-06-17T18:57:58.076Z" +} + +{ + "id": "vis-area-1", + "type": "visualization", + "attributes": { + "title": "Visualization 1 (tag-1)", + "description": "AreaChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"Visualization AreaChart\",\"type\":\"area\"}" + }, + "references": [ + { + "type": "tag", + "id": "tag-1", + "name": "tag-1-ref" + } + ], + "updated_at": "2021-06-17T18:57:58.076Z" +} + +{ + "id": "vis-area-2", + "type": "visualization", + "attributes": { + "title": "Visualization 2 (tag-2)", + "description": "AreaChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"Visualization AreaChart\",\"type\":\"area\"}" + }, + "references": [ + { + "type": "tag", + "id": "tag-2", + "name": "tag-2-ref" + } + ], + "updated_at": "2021-06-17T18:57:58.076Z" +} + +{ + "id": "vis-area-3", + "type": "visualization", + "attributes": { + "title": "Visualization 3 (tag-1 + tag-3)", + "description": "AreaChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"Visualization AreaChart\",\"type\":\"area\"}" + }, + "references": [ + { "type": "tag", + "id": "tag-1", + "name": "tag-1-ref" + }, + { "type": "tag", + "id": "tag-3", + "name": "tag-3-ref" + } + ], + "updated_at": "2021-06-17T18:57:58.076Z" +} + +{ + "id": "vis-area-4", + "type": "visualization", + "attributes": { + "title": "Visualization 4 (tag-2)", + "description": "AreaChart", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}" + }, + "uiStateJSON": "{}", + "version": 1, + "visState": "{\"title\":\"Visualization AreaChart\",\"type\":\"area\"}" + }, + "references": [ + { "type": "tag", + "id": "tag-2", + "name": "tag-2-ref" + } + ], + "updated_at": "2021-06-17T18:57:58.076Z" +} diff --git a/x-pack/test/ftr_apis/common/fixtures/es_archiver/base_data/space_1.json b/x-pack/test/ftr_apis/common/fixtures/es_archiver/base_data/space_1.json new file mode 100644 index 0000000000000..2706fcf932108 --- /dev/null +++ b/x-pack/test/ftr_apis/common/fixtures/es_archiver/base_data/space_1.json @@ -0,0 +1,11 @@ +{ + "id": "space_1-tag-3", + "type": "tag", + "attributes": { + "name": "tag-3", + "description": "Tag 3 in space 1", + "color": "#117744" + }, + "references": [], + "updated_at": "2021-06-17T18:57:58.076Z" +} diff --git a/x-pack/test/ftr_apis/common/lib/authentication.ts b/x-pack/test/ftr_apis/common/lib/authentication.ts new file mode 100644 index 0000000000000..7f4802ecfb372 --- /dev/null +++ b/x-pack/test/ftr_apis/common/lib/authentication.ts @@ -0,0 +1,243 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const ROLES = { + KIBANA_RBAC_DEFAULT_SPACE_READ_USER: { + name: 'kibana_rbac_default_space_read_user', + privileges: { + kibana: [ + { + base: ['read'], + spaces: ['default'], + }, + ], + }, + }, + KIBANA_RBAC_DEFAULT_SPACE_WRITE_USER: { + name: 'kibana_rbac_default_space_write_user', + privileges: { + kibana: [ + { + base: ['all'], + spaces: ['default'], + }, + ], + }, + }, + KIBANA_RBAC_DEFAULT_SPACE_SO_MANAGEMENT_WRITE_USER: { + name: 'kibana_rbac_default_space_so_management_write_user', + privileges: { + kibana: [ + { + feature: { + savedObjectsManagement: ['all'], + }, + spaces: ['default'], + }, + ], + }, + }, + KIBANA_RBAC_DEFAULT_SPACE_SO_MANAGEMENT_READ_USER: { + name: 'kibana_rbac_default_space_so_management_read_user', + privileges: { + kibana: [ + { + feature: { + savedObjectsManagement: ['read'], + }, + spaces: ['default'], + }, + ], + }, + }, + KIBANA_RBAC_DEFAULT_SPACE_SO_TAGGING_READ_USER: { + name: 'kibana_rbac_default_space_so_tagging_read_user', + privileges: { + kibana: [ + { + feature: { + savedObjectsTagging: ['read'], + }, + spaces: ['default'], + }, + ], + }, + }, + KIBANA_RBAC_DEFAULT_SPACE_SO_TAGGING_WRITE_USER: { + name: 'kibana_rbac_default_space_so_tagging_write_user', + privileges: { + kibana: [ + { + feature: { + savedObjectsTagging: ['all'], + }, + spaces: ['default'], + }, + ], + }, + }, + KIBANA_RBAC_DEFAULT_SPACE_DASHBOARD_READ_USER: { + name: 'kibana_rbac_default_space_dashboard_read_user', + privileges: { + kibana: [ + { + feature: { + dashboard: ['read'], + }, + spaces: ['default'], + }, + ], + }, + }, + KIBANA_RBAC_DEFAULT_SPACE_DASHBOARD_WRITE_USER: { + name: 'kibana_rbac_default_space_dashboard_write_user', + privileges: { + kibana: [ + { + feature: { + dashboard: ['all'], + }, + spaces: ['default'], + }, + ], + }, + }, + KIBANA_RBAC_DEFAULT_SPACE_VISUALIZE_READ_USER: { + name: 'kibana_rbac_default_space_visualize_read_user', + privileges: { + kibana: [ + { + feature: { + visualize: ['read'], + }, + spaces: ['default'], + }, + ], + }, + }, + KIBANA_RBAC_DEFAULT_SPACE_VISUALIZE_WRITE_USER: { + name: 'kibana_rbac_default_space_visualize_write_user', + privileges: { + kibana: [ + { + feature: { + visualize: ['all'], + }, + spaces: ['default'], + }, + ], + }, + }, + KIBANA_RBAC_DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER: { + name: 'kibana_rbac_default_space_advanced_settings_read_user', + privileges: { + kibana: [ + { + feature: { + advancedSettings: ['read'], + }, + spaces: ['default'], + }, + ], + }, + }, + KIBANA_RBAC_DEFAULT_SPACE_MAPS_READ_USER: { + name: 'kibana_rbac_default_space_maps_read_user', + privileges: { + kibana: [ + { + feature: { + maps: ['read'], + }, + spaces: ['default'], + }, + ], + }, + }, +}; + +export const USERS = { + NOT_A_KIBANA_USER: { + username: 'not_a_kibana_user', + password: 'password', + roles: [], + description: 'user with no access', + }, + SUPERUSER: { + username: 'elastic', + password: 'changeme', + roles: [], + superuser: true, + description: 'superuser', + }, + DEFAULT_SPACE_READ_USER: { + username: 'a_kibana_rbac_default_space_read_user', + password: 'password', + roles: [ROLES.KIBANA_RBAC_DEFAULT_SPACE_READ_USER.name], + description: 'rbac user with read on default space', + }, + DEFAULT_SPACE_WRITE_USER: { + username: 'a_kibana_rbac_default_space_write_user', + password: 'password', + roles: [ROLES.KIBANA_RBAC_DEFAULT_SPACE_WRITE_USER.name], + description: 'rbac user with all on default space', + }, + DEFAULT_SPACE_SO_MANAGEMENT_WRITE_USER: { + username: 'a_kibana_rbac_default_space_so_management_write_user', + password: 'password', + roles: [ROLES.KIBANA_RBAC_DEFAULT_SPACE_SO_MANAGEMENT_WRITE_USER.name], + description: 'rbac user with all on SO management on default space', + }, + DEFAULT_SPACE_SO_TAGGING_READ_USER: { + username: 'a_kibana_rbac_default_space_so_tagging_read_user', + password: 'password', + roles: [ROLES.KIBANA_RBAC_DEFAULT_SPACE_SO_TAGGING_READ_USER.name], + }, + DEFAULT_SPACE_SO_TAGGING_READ_SO_MANAGEMENT_READ_USER: { + username: 'a_kibana_rbac_default_space_so_tagging_read_so_management_read_user', + password: 'password', + roles: [ + ROLES.KIBANA_RBAC_DEFAULT_SPACE_SO_TAGGING_READ_USER.name, + ROLES.KIBANA_RBAC_DEFAULT_SPACE_SO_MANAGEMENT_READ_USER.name, + ], + }, + DEFAULT_SPACE_SO_TAGGING_WRITE_USER: { + username: 'a_kibana_rbac_default_space_so_tagging_write_user', + password: 'password', + roles: [ROLES.KIBANA_RBAC_DEFAULT_SPACE_SO_TAGGING_WRITE_USER.name], + }, + DEFAULT_SPACE_DASHBOARD_READ_USER: { + username: 'a_kibana_rbac_default_space_dashboard_read_user', + password: 'password', + roles: [ROLES.KIBANA_RBAC_DEFAULT_SPACE_DASHBOARD_READ_USER.name], + }, + DEFAULT_SPACE_VISUALIZE_READ_USER: { + username: 'a_kibana_rbac_default_space_visualize_read_user', + password: 'password', + roles: [ROLES.KIBANA_RBAC_DEFAULT_SPACE_VISUALIZE_READ_USER.name], + }, + DEFAULT_SPACE_DASHBOARD_WRITE_USER: { + username: 'a_kibana_rbac_default_space_dashboard_write_user', + password: 'password', + roles: [ROLES.KIBANA_RBAC_DEFAULT_SPACE_DASHBOARD_WRITE_USER.name], + }, + DEFAULT_SPACE_VISUALIZE_WRITE_USER: { + username: 'a_kibana_rbac_default_space_visualize_write_user', + password: 'password', + roles: [ROLES.KIBANA_RBAC_DEFAULT_SPACE_VISUALIZE_WRITE_USER.name], + }, + DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER: { + username: 'a_kibana_rbac_default_space_advanced_settings_read_user', + password: 'password', + roles: [ROLES.KIBANA_RBAC_DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER.name], + }, + DEFAULT_SPACE_MAPS_READ_USER: { + username: 'a_kibana_rbac_default_space_maps_read_user', + password: 'password', + roles: [ROLES.KIBANA_RBAC_DEFAULT_SPACE_MAPS_READ_USER.name], + }, +}; diff --git a/x-pack/test/ftr_apis/common/lib/create_users_and_roles.ts b/x-pack/test/ftr_apis/common/lib/create_users_and_roles.ts new file mode 100644 index 0000000000000..2987d561f7762 --- /dev/null +++ b/x-pack/test/ftr_apis/common/lib/create_users_and_roles.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext as CommonFtrProviderContext } from '../../../common/ftr_provider_context'; +import { USERS, ROLES } from './authentication'; +import { User, Role } from './types'; + +export const createUsersAndRoles = async (getService: CommonFtrProviderContext['getService']) => { + const security = getService('security'); + + const createRole = async ({ name, privileges }: Role) => { + return await security.role.create(name, privileges); + }; + + const createUser = async ({ username, password, roles, superuser }: User) => { + // no need to create superuser + if (superuser) { + return; + } + + return await security.user.create(username, { + password, + roles, + full_name: username.replace('_', ' '), + email: `${username}@elastic.co`, + }); + }; + + for (const role of Object.values(ROLES)) { + await createRole(role); + } + + for (const user of Object.values(USERS)) { + await createUser(user); + } +}; diff --git a/x-pack/test/ftr_apis/common/lib/index.ts b/x-pack/test/ftr_apis/common/lib/index.ts new file mode 100644 index 0000000000000..9d23dc2541f8c --- /dev/null +++ b/x-pack/test/ftr_apis/common/lib/index.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export type { Role, User, ExpectedResponse } from './types'; +export { ROLES, USERS } from './authentication'; +export { createUsersAndRoles } from './create_users_and_roles'; diff --git a/x-pack/test/ftr_apis/common/lib/types.ts b/x-pack/test/ftr_apis/common/lib/types.ts new file mode 100644 index 0000000000000..76cdeba36d0a9 --- /dev/null +++ b/x-pack/test/ftr_apis/common/lib/types.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export interface User { + username: string; + password: string; + roles: string[]; + superuser?: boolean; + description?: string; +} + +export interface Role { + name: string; + privileges: any; +} + +export interface ExpectedResponse { + httpCode: number; + expectResponse: (body: Record) => void | Promise; +} diff --git a/x-pack/test/ftr_apis/security_and_spaces/apis/bulk_delete.ts b/x-pack/test/ftr_apis/security_and_spaces/apis/bulk_delete.ts new file mode 100644 index 0000000000000..1ba800413237e --- /dev/null +++ b/x-pack/test/ftr_apis/security_and_spaces/apis/bulk_delete.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { USERS, User, ExpectedResponse } from '../../common/lib'; +import { FtrProviderContext } from '../services'; +import { createTestSpaces, deleteTestSpaces, createData, deleteData } from './test_utils'; + +// eslint-disable-next-line import/no-default-export +export default function (ftrContext: FtrProviderContext) { + const supertest = ftrContext.getService('supertestWithoutAuth'); + + describe('POST /internal/ftr/kbn_client_so/_bulk_delete', () => { + before(async () => { + await createTestSpaces(ftrContext); + }); + + after(async () => { + await deleteTestSpaces(ftrContext); + }); + + beforeEach(async () => { + await createData(ftrContext); + }); + + afterEach(async () => { + await deleteData(ftrContext); + }); + + const responses: Record = { + authorized: { + httpCode: 200, + expectResponse: ({ body }) => { + expect(body.statuses.length).to.eql(1); + }, + }, + unauthorized: { + httpCode: 403, + expectResponse: ({ body }) => { + expect(body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: 'Forbidden', + }); + }, + }, + }; + + const expectedResults: Record = { + authorized: [USERS.SUPERUSER], + unauthorized: [ + USERS.DEFAULT_SPACE_SO_MANAGEMENT_WRITE_USER, + USERS.DEFAULT_SPACE_SO_TAGGING_WRITE_USER, + USERS.DEFAULT_SPACE_READ_USER, + USERS.DEFAULT_SPACE_SO_TAGGING_READ_USER, + USERS.DEFAULT_SPACE_DASHBOARD_READ_USER, + USERS.DEFAULT_SPACE_VISUALIZE_READ_USER, + USERS.DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER, + USERS.NOT_A_KIBANA_USER, + ], + }; + + const createUserTest = ( + { username, password, description }: User, + { httpCode, expectResponse }: ExpectedResponse + ) => { + it(`returns expected ${httpCode} response for ${description ?? username}`, async () => { + await supertest + .post(`/internal/ftr/kbn_client_so/_bulk_delete`) + .send([{ type: 'tag', id: 'tag-1' }]) + .auth(username, password) + .expect(httpCode) + .then(expectResponse); + }); + }; + + const createTestSuite = () => { + Object.entries(expectedResults).forEach(([responseId, users]) => { + const response: ExpectedResponse = responses[responseId]; + users.forEach((user) => { + createUserTest(user, response); + }); + }); + }; + + createTestSuite(); + }); +} diff --git a/x-pack/test/ftr_apis/security_and_spaces/apis/create.ts b/x-pack/test/ftr_apis/security_and_spaces/apis/create.ts new file mode 100644 index 0000000000000..50aee16f1b9f0 --- /dev/null +++ b/x-pack/test/ftr_apis/security_and_spaces/apis/create.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { USERS, User, ExpectedResponse } from '../../common/lib'; +import { FtrProviderContext } from '../services'; +import { createTestSpaces, deleteTestSpaces, createData, deleteData } from './test_utils'; + +// eslint-disable-next-line import/no-default-export +export default function (ftrContext: FtrProviderContext) { + const supertest = ftrContext.getService('supertestWithoutAuth'); + + describe('POST /internal/ftr/kbn_client_so/{type}', () => { + before(async () => { + await createTestSpaces(ftrContext); + }); + + after(async () => { + await deleteTestSpaces(ftrContext); + }); + + beforeEach(async () => { + await createData(ftrContext); + }); + + afterEach(async () => { + await deleteData(ftrContext); + }); + + const responses: Record = { + authorized: { + httpCode: 200, + expectResponse: ({ body }) => { + expect(body.attributes).to.eql({ + name: 'My new tag', + description: 'I just created that', + color: '#009000', + }); + }, + }, + unauthorized: { + httpCode: 403, + expectResponse: ({ body }) => { + expect(body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: 'Forbidden', + }); + }, + }, + }; + + const expectedResults: Record = { + authorized: [USERS.SUPERUSER], + unauthorized: [ + USERS.DEFAULT_SPACE_SO_MANAGEMENT_WRITE_USER, + USERS.DEFAULT_SPACE_SO_TAGGING_WRITE_USER, + USERS.DEFAULT_SPACE_READ_USER, + USERS.DEFAULT_SPACE_SO_TAGGING_READ_USER, + USERS.DEFAULT_SPACE_DASHBOARD_READ_USER, + USERS.DEFAULT_SPACE_VISUALIZE_READ_USER, + USERS.DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER, + USERS.DEFAULT_SPACE_MAPS_READ_USER, + USERS.NOT_A_KIBANA_USER, + ], + }; + + const createUserTest = ( + { username, password, description }: User, + { httpCode, expectResponse }: ExpectedResponse + ) => { + it(`returns expected ${httpCode} response for ${description ?? username}`, async () => { + await supertest + .post(`/internal/ftr/kbn_client_so/tag`) + .send({ + attributes: { + name: 'My new tag', + description: 'I just created that', + color: '#009000', + }, + }) + .auth(username, password) + .expect(httpCode) + .then(expectResponse); + }); + }; + + const createTestSuite = () => { + Object.entries(expectedResults).forEach(([responseId, users]) => { + const response: ExpectedResponse = responses[responseId]; + users.forEach((user) => { + createUserTest(user, response); + }); + }); + }; + + createTestSuite(); + }); +} diff --git a/x-pack/test/ftr_apis/security_and_spaces/apis/delete.ts b/x-pack/test/ftr_apis/security_and_spaces/apis/delete.ts new file mode 100644 index 0000000000000..f3b04c3cab492 --- /dev/null +++ b/x-pack/test/ftr_apis/security_and_spaces/apis/delete.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { USERS, User, ExpectedResponse } from '../../common/lib'; +import { FtrProviderContext } from '../services'; +import { createTestSpaces, deleteTestSpaces, createData, deleteData } from './test_utils'; + +// eslint-disable-next-line import/no-default-export +export default function (ftrContext: FtrProviderContext) { + const supertest = ftrContext.getService('supertestWithoutAuth'); + + describe('DELETE /internal/ftr/kbn_client_so/{type}/{id}', () => { + before(async () => { + await createTestSpaces(ftrContext); + }); + + after(async () => { + await deleteTestSpaces(ftrContext); + }); + + beforeEach(async () => { + await createData(ftrContext); + }); + + afterEach(async () => { + await deleteData(ftrContext); + }); + + const responses: Record = { + authorized: { + httpCode: 200, + expectResponse: ({ body }) => { + expect(body).to.eql({}); + }, + }, + unauthorized: { + httpCode: 403, + expectResponse: ({ body }) => { + expect(body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: 'Forbidden', + }); + }, + }, + }; + + const expectedResults: Record = { + authorized: [USERS.SUPERUSER], + unauthorized: [ + USERS.DEFAULT_SPACE_SO_MANAGEMENT_WRITE_USER, + USERS.DEFAULT_SPACE_SO_TAGGING_WRITE_USER, + USERS.DEFAULT_SPACE_READ_USER, + USERS.DEFAULT_SPACE_SO_TAGGING_READ_USER, + USERS.DEFAULT_SPACE_DASHBOARD_READ_USER, + USERS.DEFAULT_SPACE_VISUALIZE_READ_USER, + USERS.DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER, + USERS.DEFAULT_SPACE_MAPS_READ_USER, + USERS.NOT_A_KIBANA_USER, + ], + }; + + const createUserTest = ( + { username, password, description }: User, + { httpCode, expectResponse }: ExpectedResponse + ) => { + it(`returns expected ${httpCode} response for ${description ?? username}`, async () => { + await supertest + .delete(`/internal/ftr/kbn_client_so/visualization/vis-area-1`) + .auth(username, password) + .expect(httpCode) + .then(expectResponse); + }); + }; + + const createTestSuite = () => { + Object.entries(expectedResults).forEach(([responseId, users]) => { + const response: ExpectedResponse = responses[responseId]; + users.forEach((user) => { + createUserTest(user, response); + }); + }); + }; + + createTestSuite(); + }); +} diff --git a/x-pack/test/ftr_apis/security_and_spaces/apis/find.ts b/x-pack/test/ftr_apis/security_and_spaces/apis/find.ts new file mode 100644 index 0000000000000..803db0affbfa7 --- /dev/null +++ b/x-pack/test/ftr_apis/security_and_spaces/apis/find.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { USERS, User, ExpectedResponse } from '../../common/lib'; +import { FtrProviderContext } from '../services'; +import { createData, createTestSpaces, deleteData, deleteTestSpaces } from './test_utils'; + +// eslint-disable-next-line import/no-default-export +export default function (ftrContext: FtrProviderContext) { + const supertest = ftrContext.getService('supertestWithoutAuth'); + + describe('GET /internal/ftr/kbn_client_so/_find', () => { + before(async () => { + await createTestSpaces(ftrContext); + }); + + after(async () => { + await deleteTestSpaces(ftrContext); + }); + + beforeEach(async () => { + await createData(ftrContext); + }); + + afterEach(async () => { + await deleteData(ftrContext); + }); + + const responses: Record = { + authorized: { + httpCode: 200, + expectResponse: ({ body }) => { + expect(body.saved_objects.length).to.be.greaterThan(0); + }, + }, + unauthorized: { + httpCode: 403, + expectResponse: ({ body }) => { + expect(body).to.eql({ + error: 'Forbidden', + message: 'Forbidden', + statusCode: 403, + }); + }, + }, + }; + const expectedResults: Record = { + authorized: [USERS.SUPERUSER], + unauthorized: [ + USERS.DEFAULT_SPACE_READ_USER, + USERS.DEFAULT_SPACE_SO_MANAGEMENT_WRITE_USER, + USERS.DEFAULT_SPACE_SO_TAGGING_READ_USER, + USERS.DEFAULT_SPACE_SO_TAGGING_WRITE_USER, + USERS.DEFAULT_SPACE_DASHBOARD_READ_USER, + USERS.DEFAULT_SPACE_VISUALIZE_READ_USER, + USERS.DEFAULT_SPACE_MAPS_READ_USER, + USERS.DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER, + USERS.NOT_A_KIBANA_USER, + ], + }; + + const createUserTest = ( + { username, password, description }: User, + { httpCode, expectResponse }: ExpectedResponse + ) => { + it(`returns expected ${httpCode} response for ${description ?? username}`, async () => { + await supertest + .get(`/internal/ftr/kbn_client_so/_find`) + .query({ + type: 'tag', + }) + .auth(username, password) + .expect(httpCode) + .then(expectResponse); + }); + }; + + const createTestSuite = () => { + Object.entries(expectedResults).forEach(([responseId, users]) => { + const response: ExpectedResponse = responses[responseId]; + users.forEach((user) => { + createUserTest(user, response); + }); + }); + }; + + createTestSuite(); + }); +} diff --git a/x-pack/test/ftr_apis/security_and_spaces/apis/get.ts b/x-pack/test/ftr_apis/security_and_spaces/apis/get.ts new file mode 100644 index 0000000000000..db4f507a64645 --- /dev/null +++ b/x-pack/test/ftr_apis/security_and_spaces/apis/get.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { USERS, User, ExpectedResponse } from '../../common/lib'; +import { FtrProviderContext } from '../services'; +import { createTestSpaces, deleteTestSpaces, createData, deleteData } from './test_utils'; + +// eslint-disable-next-line import/no-default-export +export default function (ftrContext: FtrProviderContext) { + const supertest = ftrContext.getService('supertestWithoutAuth'); + + describe('GET /internal/ftr/kbn_client_so/{type}/{id}', () => { + before(async () => { + await createTestSpaces(ftrContext); + }); + + after(async () => { + await deleteTestSpaces(ftrContext); + }); + + beforeEach(async () => { + await createData(ftrContext); + }); + + afterEach(async () => { + await deleteData(ftrContext); + }); + + const responses: Record = { + authorized: { + httpCode: 200, + expectResponse: ({ body }) => { + expect(body.id).to.eql('vis-area-4'); + }, + }, + unauthorized: { + httpCode: 403, + expectResponse: ({ body }) => { + expect(body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: 'Forbidden', + }); + }, + }, + }; + + const expectedResults: Record = { + authorized: [USERS.SUPERUSER], + unauthorized: [ + USERS.NOT_A_KIBANA_USER, + USERS.DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER, + USERS.DEFAULT_SPACE_READ_USER, + USERS.DEFAULT_SPACE_SO_MANAGEMENT_WRITE_USER, + USERS.DEFAULT_SPACE_SO_TAGGING_READ_USER, + USERS.DEFAULT_SPACE_SO_TAGGING_WRITE_USER, + USERS.DEFAULT_SPACE_DASHBOARD_READ_USER, + USERS.DEFAULT_SPACE_VISUALIZE_READ_USER, + USERS.DEFAULT_SPACE_MAPS_READ_USER, + ], + }; + + const createUserTest = ( + { username, password, description }: User, + { httpCode, expectResponse }: ExpectedResponse + ) => { + it(`returns expected ${httpCode} response for ${description ?? username}`, async () => { + await supertest + .get(`/internal/ftr/kbn_client_so/visualization/vis-area-4`) + .auth(username, password) + .expect(httpCode) + .then(expectResponse); + }); + }; + + const createTestSuite = () => { + Object.entries(expectedResults).forEach(([responseId, users]) => { + const response: ExpectedResponse = responses[responseId]; + users.forEach((user) => { + createUserTest(user, response); + }); + }); + }; + + createTestSuite(); + }); +} diff --git a/x-pack/test/ftr_apis/security_and_spaces/apis/index.ts b/x-pack/test/ftr_apis/security_and_spaces/apis/index.ts new file mode 100644 index 0000000000000..ca5bf0cb89221 --- /dev/null +++ b/x-pack/test/ftr_apis/security_and_spaces/apis/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../services'; +import { createUsersAndRoles } from '../../common/lib'; + +// eslint-disable-next-line import/no-default-export +export default function ({ getService, loadTestFile }: FtrProviderContext) { + describe('FTR API - security and spaces integration', function () { + before(async () => { + await createUsersAndRoles(getService); + }); + + loadTestFile(require.resolve('./get')); + loadTestFile(require.resolve('./create')); + loadTestFile(require.resolve('./update')); + loadTestFile(require.resolve('./delete')); + loadTestFile(require.resolve('./find')); + loadTestFile(require.resolve('./bulk_delete')); + }); +} diff --git a/x-pack/test/ftr_apis/security_and_spaces/apis/test_utils.ts b/x-pack/test/ftr_apis/security_and_spaces/apis/test_utils.ts new file mode 100644 index 0000000000000..7ab99302043fa --- /dev/null +++ b/x-pack/test/ftr_apis/security_and_spaces/apis/test_utils.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrProviderContext } from '../services'; + +export const createTestSpaces = async ({ getService }: FtrProviderContext) => { + const spaceService = getService('spaces'); + await spaceService.create({ + id: 'space_1', + name: 'Space 1', + description: 'This is the first test space', + }); + await spaceService.create({ + id: 'space_2', + name: 'Space 2', + description: 'This is the second test space', + }); +}; + +export const deleteTestSpaces = async ({ getService }: FtrProviderContext) => { + const spaceService = getService('spaces'); + await spaceService.delete('space_1'); + await spaceService.delete('space_2'); +}; + +export const createData = async ({ getService }: FtrProviderContext) => { + const kibanaServer = getService('kibanaServer'); + await kibanaServer.importExport.load( + 'x-pack/test/ftr_apis/common/fixtures/es_archiver/base_data/default_space.json' + ); + await kibanaServer.importExport.load( + 'x-pack/test/ftr_apis/common/fixtures/es_archiver/base_data/space_1.json', + { space: 'space_1' } + ); +}; + +export const deleteData = async ({ getService }: FtrProviderContext) => { + const kibanaServer = getService('kibanaServer'); + await kibanaServer.importExport.unload( + 'x-pack/test/ftr_apis/common/fixtures/es_archiver/base_data/default_space.json' + ); + await kibanaServer.importExport.unload( + 'x-pack/test/ftr_apis/common/fixtures/es_archiver/base_data/space_1.json', + { space: 'space_1' } + ); +}; diff --git a/x-pack/test/ftr_apis/security_and_spaces/apis/update.ts b/x-pack/test/ftr_apis/security_and_spaces/apis/update.ts new file mode 100644 index 0000000000000..fe3ade47195a6 --- /dev/null +++ b/x-pack/test/ftr_apis/security_and_spaces/apis/update.ts @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { USERS, User, ExpectedResponse } from '../../common/lib'; +import { FtrProviderContext } from '../services'; +import { createTestSpaces, deleteTestSpaces, createData, deleteData } from './test_utils'; + +// eslint-disable-next-line import/no-default-export +export default function (ftrContext: FtrProviderContext) { + const supertest = ftrContext.getService('supertestWithoutAuth'); + + describe('PUT /internal/ftr/kbn_client_so/{type}/{id}', () => { + before(async () => { + await createTestSpaces(ftrContext); + }); + + after(async () => { + await deleteTestSpaces(ftrContext); + }); + + beforeEach(async () => { + await createData(ftrContext); + }); + + afterEach(async () => { + await deleteData(ftrContext); + }); + + const responses: Record = { + authorized: { + httpCode: 200, + expectResponse: ({ body }) => { + expect(body.attributes.name).to.eql('Updated title'); + }, + }, + unauthorized: { + httpCode: 403, + expectResponse: ({ body }) => { + expect(body).to.eql({ + statusCode: 403, + error: 'Forbidden', + message: 'Forbidden', + }); + }, + }, + }; + + const expectedResults: Record = { + authorized: [USERS.SUPERUSER], + unauthorized: [ + USERS.DEFAULT_SPACE_SO_MANAGEMENT_WRITE_USER, + USERS.DEFAULT_SPACE_SO_TAGGING_WRITE_USER, + USERS.DEFAULT_SPACE_READ_USER, + USERS.DEFAULT_SPACE_SO_TAGGING_READ_USER, + USERS.DEFAULT_SPACE_DASHBOARD_READ_USER, + USERS.DEFAULT_SPACE_VISUALIZE_READ_USER, + USERS.DEFAULT_SPACE_ADVANCED_SETTINGS_READ_USER, + USERS.DEFAULT_SPACE_MAPS_READ_USER, + USERS.NOT_A_KIBANA_USER, + ], + }; + + const createUserTest = ( + { username, password, description }: User, + { httpCode, expectResponse }: ExpectedResponse + ) => { + it(`returns expected ${httpCode} response for ${description ?? username}`, async () => { + await supertest + .put(`/internal/ftr/kbn_client_so/tag/tag-1`) + .send({ + attributes: { name: 'Updated title' }, + }) + .auth(username, password) + .expect(httpCode) + .then(expectResponse); + }); + }; + + const createTestSuite = () => { + Object.entries(expectedResults).forEach(([responseId, users]) => { + const response: ExpectedResponse = responses[responseId]; + users.forEach((user) => { + createUserTest(user, response); + }); + }); + }; + + createTestSuite(); + }); +} diff --git a/x-pack/test/ftr_apis/security_and_spaces/config.ts b/x-pack/test/ftr_apis/security_and_spaces/config.ts new file mode 100644 index 0000000000000..8cfc662bb0b96 --- /dev/null +++ b/x-pack/test/ftr_apis/security_and_spaces/config.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; +import { services } from './services'; + +// eslint-disable-next-line import/no-default-export +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const apiIntegrationConfig = await readConfigFile( + require.resolve('../../api_integration/config.ts') + ); + + return { + testFiles: [require.resolve('./apis')], + servers: apiIntegrationConfig.get('servers'), + services, + junit: { + reportName: 'X-Pack FTR API Integration Tests - Security and Spaces', + }, + esTestCluster: { + ...apiIntegrationConfig.get('esTestCluster'), + license: 'trial', + }, + kbnTestServer: { + ...apiIntegrationConfig.get('kbnTestServer'), + serverArgs: [ + ...apiIntegrationConfig.get('kbnTestServer.serverArgs'), + '--server.xsrf.disableProtection=true', + `--xpack.fleet.registryUrl=http://localhost:12345`, // setting to invalid registry url to prevent installing preconfigured packages + ], + }, + }; +} diff --git a/x-pack/test/ftr_apis/security_and_spaces/services.ts b/x-pack/test/ftr_apis/security_and_spaces/services.ts new file mode 100644 index 0000000000000..cb1db3f8469ee --- /dev/null +++ b/x-pack/test/ftr_apis/security_and_spaces/services.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { GenericFtrProviderContext } from '@kbn/test'; +import { services as apiIntegrationServices } from '../../api_integration/services'; + +export const services = { + ...apiIntegrationServices, +}; + +export type FtrProviderContext = GenericFtrProviderContext;