diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f6c4571cbd5181..e7d6e8001f1de4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -38,6 +38,7 @@ /src/legacy/server/saved_objects/ @elastic/kibana-platform /src/legacy/ui/public/saved_objects @elastic/kibana-platform /config/kibana.yml @elastic/kibana-platform +/x-pack/plugins/features/ @elastic/kibana-platform # Security /x-pack/legacy/plugins/security/ @elastic/kibana-security diff --git a/docs/api/features/get.asciidoc b/docs/api/features/get.asciidoc index e5c48938e433d2..c4e4ff182448f5 100644 --- a/docs/api/features/get.asciidoc +++ b/docs/api/features/get.asciidoc @@ -11,7 +11,7 @@ experimental[This API is *experimental* and may be changed or removed completely [[features-api-get-request]] ==== Request -`GET /api/features/v1` +`GET /api/features` [[features-api-get-codes]] ==== Response code diff --git a/docs/development/plugin/development-plugin-feature-registration.asciidoc b/docs/development/plugin/development-plugin-feature-registration.asciidoc index 5a852515148614..f9078440cff2b7 100644 --- a/docs/development/plugin/development-plugin-feature-registration.asciidoc +++ b/docs/development/plugin/development-plugin-feature-registration.asciidoc @@ -22,7 +22,7 @@ init(server) { ----------- ===== Feature details -Registering a feature consists of the following fields. For more information, consult the {repo}blob/{branch}/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts[feature registry interface]. +Registering a feature consists of the following fields. For more information, consult the {repo}blob/{branch}/x-pack/plugins/features/server/feature_registry.ts[feature registry interface]. [cols="1a, 1a, 1a, 1a"] @@ -45,7 +45,7 @@ Registering a feature consists of the following fields. For more information, co |An array of applications this feature enables. Typically, all of your plugin's apps (from `uiExports`) will be included here. |`privileges` (required) -|{repo}blob/{branch}/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts[`FeatureWithAllOrReadPrivileges`]. +|{repo}blob/{branch}/x-pack/plugins/features/server/feature.ts[`FeatureWithAllOrReadPrivileges`]. |see examples below |The set of privileges this feature requires to function. @@ -63,7 +63,7 @@ Registering a feature consists of the following fields. For more information, co ===== Privilege definition The `privileges` section of feature registration allows plugins to implement read/write and read-only modes for their applications. -For a full explanation of fields and options, consult the {repo}blob/{branch}/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts[feature registry interface]. +For a full explanation of fields and options, consult the {repo}blob/{branch}/x-pack/plugins/features/server/feature_registry.ts[feature registry interface]. ==== Using UI Capabilities @@ -142,7 +142,7 @@ init(server) { const xpackMainPlugin = server.plugins.xpack_main; xpackMainPlugin.registerFeature({ id: 'dev_tools', - name: i18n.translate('xpack.main.featureRegistry.devToolsFeatureName', { + name: i18n.translate('xpack.features.devToolsFeatureName', { defaultMessage: 'Dev Tools', }), icon: 'devToolsApp', @@ -167,7 +167,7 @@ init(server) { ui: ['show'], }, }, - privilegesTooltip: i18n.translate('xpack.main.featureRegistry.devToolsPrivilegesTooltip', { + privilegesTooltip: i18n.translate('xpack.features.devToolsPrivilegesTooltip', { defaultMessage: 'User should also be granted the appropriate Elasticsearch cluster and index privileges', }), diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index eb2fc3f47273ae..e834e19fc77d80 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -99,4 +99,5 @@ export const httpServiceMock = { createSetupContract: createSetupContractMock, createOnPreAuthToolkit: createOnPreAuthToolkitMock, createAuthToolkit: createAuthToolkitMock, + createRouter: createRouterMock, }; diff --git a/src/plugins/timelion/kibana.json b/src/plugins/timelion/kibana.json new file mode 100644 index 00000000000000..fe6e425f76c05d --- /dev/null +++ b/src/plugins/timelion/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "timelion", + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["timelion"], + "server": true, + "ui": false +} diff --git a/src/plugins/timelion/server/config.ts b/src/plugins/timelion/server/config.ts new file mode 100644 index 00000000000000..a47d39beb7555b --- /dev/null +++ b/src/plugins/timelion/server/config.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema } from '@kbn/config-schema'; + +export const ConfigSchema = schema.object( + { + ui: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), + }, + // This option should be removed as soon as we entirely migrate config from legacy Timelion plugin. + { allowUnknowns: true } +); diff --git a/src/plugins/timelion/server/index.ts b/src/plugins/timelion/server/index.ts new file mode 100644 index 00000000000000..690544f0b9f5ca --- /dev/null +++ b/src/plugins/timelion/server/index.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from '../../../../src/core/server'; +import { ConfigSchema } from './config'; +import { Plugin } from './plugin'; + +export { PluginSetupContract } from './plugin'; + +export const config = { schema: ConfigSchema }; +export const plugin = (initializerContext: PluginInitializerContext) => + new Plugin(initializerContext); diff --git a/src/plugins/timelion/server/plugin.ts b/src/plugins/timelion/server/plugin.ts new file mode 100644 index 00000000000000..4436c1539fc5bc --- /dev/null +++ b/src/plugins/timelion/server/plugin.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { first } from 'rxjs/operators'; +import { TypeOf } from '@kbn/config-schema'; +import { PluginInitializerContext, RecursiveReadonly } from '../../../../src/core/server'; +import { deepFreeze } from '../../../../src/core/utils'; +import { ConfigSchema } from './config'; + +/** + * Describes public Timelion plugin contract returned at the `setup` stage. + */ +export interface PluginSetupContract { + uiEnabled: boolean; +} + +/** + * Represents Timelion Plugin instance that will be managed by the Kibana plugin system. + */ +export class Plugin { + constructor(private readonly initializerContext: PluginInitializerContext) {} + + public async setup(): Promise> { + const config = await this.initializerContext.config + .create>() + .pipe(first()) + .toPromise(); + + return deepFreeze({ uiEnabled: config.ui.enabled }); + } + + public start() { + this.initializerContext.logger.get().debug('Starting plugin'); + } + + public stop() { + this.initializerContext.logger.get().debug('Stopping plugin'); + } +} diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 8aa39499f1c0d4..26e72882a08a9c 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -10,6 +10,7 @@ "xpack.code": "legacy/plugins/code", "xpack.crossClusterReplication": "legacy/plugins/cross_cluster_replication", "xpack.dashboardMode": "legacy/plugins/dashboard_mode", + "xpack.features": "plugins/features", "xpack.fileUpload": "legacy/plugins/file_upload", "xpack.graph": "legacy/plugins/graph", "xpack.grokDebugger": "legacy/plugins/grokdebugger", diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx index 80c8c079b90017..75f9520cef64b7 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { UICapabilities } from 'ui/capabilities'; import { Space } from '../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../../plugins/features/server'; import { RawKibanaPrivileges, Role } from '../../../../../common/model'; import { actionsFactory } from '../../../../../server/lib/authorization/actions'; import { privilegesFactory } from '../../../../../server/lib/authorization/privileges'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx index 9996427f59b249..b91c67b8f7d0ae 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx @@ -23,7 +23,7 @@ import React, { ChangeEvent, Component, Fragment, HTMLProps } from 'react'; import { UICapabilities } from 'ui/capabilities'; import { toastNotifications } from 'ui/notify'; import { Space } from '../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../../plugins/features/server'; import { KibanaPrivileges, RawKibanaPrivileges, diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx index aacbd8f2ef90bd..8425826235f0bf 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx @@ -17,7 +17,7 @@ import { import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import _ from 'lodash'; import React, { Component } from 'react'; -import { Feature } from '../../../../../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../../../../../plugins/features/server'; import { FeaturesPrivileges, KibanaPrivileges, Role } from '../../../../../../../../common/model'; import { AllowedPrivilege, diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/kibana_privileges_region.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/kibana_privileges_region.tsx index 2ea4d4ac8f4087..199067b2e74697 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/kibana_privileges_region.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/kibana_privileges_region.tsx @@ -8,7 +8,7 @@ import { InjectedIntl } from '@kbn/i18n/react'; import React, { Component } from 'react'; import { UICapabilities } from 'ui/capabilities'; import { Space } from '../../../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../../../../plugins/features/server'; import { KibanaPrivileges, Role } from '../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../../../../../../lib/kibana_privilege_calculator'; import { RoleValidator } from '../../../lib/validate_role'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx index c12f23e6a6d32e..74d62b0c867580 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx @@ -7,7 +7,7 @@ import { EuiButtonGroup, EuiButtonGroupProps, EuiComboBox, EuiSuperSelect } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { Feature } from '../../../../../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../../../../../plugins/features/server'; import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; import { SimplePrivilegeSection } from './simple_privilege_section'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx index 5510fa6291c08c..d564179798ad87 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx @@ -15,7 +15,7 @@ import { import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; -import { Feature } from '../../../../../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../../../../../plugins/features/server'; import { KibanaPrivileges, Role, RoleKibanaPrivilege } from '../../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; import { isGlobalPrivilegeDefinition } from '../../../../../../../lib/privilege_utils'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx index fb8a67144aa8e2..3d4a0d89ed7a1f 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx @@ -8,7 +8,7 @@ import { EuiButtonEmpty, EuiInMemoryTable } from '@elastic/eui'; import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../../../../../plugins/features/server'; import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../../../../../../..//lib/kibana_privilege_calculator'; import { PrivilegeMatrix } from './privilege_matrix'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx index 4fb0d5f38a18cb..be49494efbe9a8 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx @@ -24,7 +24,7 @@ import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; import { Space } from '../../../../../../../../../spaces/common/model/space'; import { SpaceAvatar } from '../../../../../../../../../spaces/public/components'; -import { Feature } from '../../../../../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../../../../../plugins/features/server'; import { FeaturesPrivileges, Role } from '../../../../../../../../common/model'; import { CalculatedPrivilege } from '../../../../../../../lib/kibana_privilege_calculator'; import { isGlobalPrivilegeDefinition } from '../../../../../../../lib/privilege_utils'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx index e32fe04b197479..a616d3537cee30 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx @@ -25,7 +25,7 @@ import { import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../../../../../plugins/features/server'; import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; import { AllowedPrivilege, diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx index 3ce7b0f72aee3b..cdb5521bd0c863 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx @@ -16,7 +16,7 @@ import _ from 'lodash'; import React, { Component, Fragment } from 'react'; import { UICapabilities } from 'ui/capabilities'; import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { Feature } from '../../../../../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../../../../../plugins/features/server'; import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; import { isReservedRole } from '../../../../../../../lib/role_utils'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js index 3f8c6ceb0e719e..1abb299c993900 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js @@ -92,7 +92,7 @@ const routeDefinition = (action) => ({ return kfetch({ method: 'get', pathname: '/api/security/v1/esPrivileges/builtin' }); }, features() { - return kfetch({ method: 'get', pathname: '/api/features/v1' }).catch(e => { + return kfetch({ method: 'get', pathname: '/api/features' }).catch(e => { // TODO: This check can be removed once all of these `resolve` entries are moved out of Angular and into the React app. const unauthorizedForFeatures = _.get(e, 'body.statusCode') === 404; if (unauthorizedForFeatures) { diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/actions/ui.ts b/x-pack/legacy/plugins/security/server/lib/authorization/actions/ui.ts index 6e352895aec0e1..ec5af3496eae63 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/actions/ui.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/actions/ui.ts @@ -5,7 +5,7 @@ */ import { isString } from 'lodash'; import { UICapabilities } from 'ui/capabilities'; -import { uiCapabilitiesRegex } from '../../../../../xpack_main/types'; +import { uiCapabilitiesRegex } from '../../../../../../../plugins/features/server'; export class UIActions { private readonly prefix: string; diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/app_authorization.test.ts b/x-pack/legacy/plugins/security/server/lib/authorization/app_authorization.test.ts index dcb5c5fcb06824..52bc6de63146a2 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/app_authorization.test.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/app_authorization.test.ts @@ -7,7 +7,7 @@ import { Server } from 'hapi'; import { AuthorizationService } from './service'; -import { Feature } from '../../../../xpack_main/types'; +import { Feature } from '../../../../../../plugins/features/server'; import { XPackMainPlugin } from '../../../../xpack_main/xpack_main'; import { actionsFactory } from './actions'; import { initAppAuthorization } from './app_authorization'; diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/disable_ui_capabilities.test.ts b/x-pack/legacy/plugins/security/server/lib/authorization/disable_ui_capabilities.test.ts index c0163ec76f3c34..198a36177c55ac 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/disable_ui_capabilities.test.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/disable_ui_capabilities.test.ts @@ -5,7 +5,7 @@ */ import { Actions } from '.'; -import { Feature } from '../../../../xpack_main/types'; +import { Feature } from '../../../../../../plugins/features/server'; import { disableUICapabilitesFactory } from './disable_ui_capabilities'; interface MockServerOptions { diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/disable_ui_capabilities.ts b/x-pack/legacy/plugins/security/server/lib/authorization/disable_ui_capabilities.ts index 9df26091b7dbdf..4d952bca20a3da 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/disable_ui_capabilities.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/disable_ui_capabilities.ts @@ -6,7 +6,7 @@ import { flatten, isObject, mapValues } from 'lodash'; import { UICapabilities } from 'ui/capabilities'; -import { Feature } from '../../../../xpack_main/types'; +import { Feature } from '../../../../../../plugins/features/server'; import { Actions } from './actions'; import { CheckPrivilegesAtResourceResponse } from './check_privileges'; import { CheckPrivilegesDynamically } from './check_privileges_dynamically'; diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/api.ts b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/api.ts index 56a66b3748001c..901c002bfde06d 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/api.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/api.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Feature, - FeatureKibanaPrivileges, -} from '../../../../../../xpack_main/server/lib/feature_registry/feature_registry'; +import { Feature, FeatureKibanaPrivileges } from '../../../../../../../../plugins/features/server'; import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder'; export class FeaturePrivilegeApiBuilder extends BaseFeaturePrivilegeBuilder { diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/app.ts b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/app.ts index ac2772b6372d2f..4362c79dc550e9 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/app.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/app.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Feature, - FeatureKibanaPrivileges, -} from '../../../../../../xpack_main/server/lib/feature_registry/feature_registry'; +import { Feature, FeatureKibanaPrivileges } from '../../../../../../../../plugins/features/server'; import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder'; export class FeaturePrivilegeAppBuilder extends BaseFeaturePrivilegeBuilder { diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/catalogue.ts b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/catalogue.ts index 2f111069c289fa..5ed649b2726c2f 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/catalogue.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/catalogue.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Feature, - FeatureKibanaPrivileges, -} from '../../../../../../xpack_main/server/lib/feature_registry/feature_registry'; +import { Feature, FeatureKibanaPrivileges } from '../../../../../../../../plugins/features/server'; import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder'; export class FeaturePrivilegeCatalogueBuilder extends BaseFeaturePrivilegeBuilder { diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/feature_privilege_builder.ts b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/feature_privilege_builder.ts index 4634179603d8b8..48078a26839bb7 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/feature_privilege_builder.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/feature_privilege_builder.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature, FeatureKibanaPrivileges } from '../../../../../../xpack_main/types'; +import { Feature, FeatureKibanaPrivileges } from '../../../../../../../../plugins/features/server'; import { Actions } from '../../actions'; export interface FeaturePrivilegeBuilder { diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/index.ts b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/index.ts index c6c11b628f19e5..78e1db7a980f3c 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/index.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/index.ts @@ -5,7 +5,7 @@ */ import { flatten } from 'lodash'; -import { Feature, FeatureKibanaPrivileges } from '../../../../../../xpack_main/types'; +import { Feature, FeatureKibanaPrivileges } from '../../../../../../../../plugins/features/server'; import { Actions } from '../../actions'; import { FeaturePrivilegeApiBuilder } from './api'; import { FeaturePrivilegeAppBuilder } from './app'; diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/management.ts b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/management.ts index 327b9abc3a94b3..4a008fdb096195 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/management.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/management.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Feature, - FeatureKibanaPrivileges, -} from '../../../../../../xpack_main/server/lib/feature_registry/feature_registry'; +import { Feature, FeatureKibanaPrivileges } from '../../../../../../../../plugins/features/server'; import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder'; export class FeaturePrivilegeManagementBuilder extends BaseFeaturePrivilegeBuilder { diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/navlink.ts b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/navlink.ts index 445a57c8ae0afd..3cd75233beffb1 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/navlink.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/navlink.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Feature, - FeatureKibanaPrivileges, -} from '../../../../../../xpack_main/server/lib/feature_registry/feature_registry'; +import { Feature, FeatureKibanaPrivileges } from '../../../../../../../../plugins/features/server'; import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder'; export class FeaturePrivilegeNavlinkBuilder extends BaseFeaturePrivilegeBuilder { diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/saved_object.ts b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/saved_object.ts index 86226d2bfd0306..d154acddf4f356 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/saved_object.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/saved_object.ts @@ -5,10 +5,7 @@ */ import { flatten, uniq } from 'lodash'; -import { - Feature, - FeatureKibanaPrivileges, -} from '../../../../../../xpack_main/server/lib/feature_registry/feature_registry'; +import { Feature, FeatureKibanaPrivileges } from '../../../../../../../../plugins/features/server'; import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder'; const readOperations: string[] = ['bulk_get', 'get', 'find']; diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/ui.ts b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/ui.ts index ac8fe48d60a32b..fd770b4c6263b9 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/ui.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/ui.ts @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Feature, - FeatureKibanaPrivileges, -} from '../../../../../../xpack_main/server/lib/feature_registry/feature_registry'; +import { Feature, FeatureKibanaPrivileges } from '../../../../../../../../plugins/features/server'; import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder'; export class FeaturePrivilegeUIBuilder extends BaseFeaturePrivilegeBuilder { diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/privileges.test.ts b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/privileges.test.ts index b6b2b56c9a2e49..642488042f1cf8 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/privileges.test.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/privileges.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature } from '../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../plugins/features/server'; import { Actions } from '../actions'; import { privilegesFactory } from './privileges'; diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/privileges.ts b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/privileges.ts index 8d28b144f6c1fa..aad48584a9fca5 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/privileges/privileges.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/privileges/privileges.ts @@ -5,7 +5,7 @@ */ import { flatten, mapValues, uniq } from 'lodash'; -import { Feature } from '../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../plugins/features/server'; import { XPackMainPlugin } from '../../../../../xpack_main/xpack_main'; import { RawKibanaFeaturePrivileges, RawKibanaPrivileges } from '../../../../common/model'; import { Actions } from '../actions'; diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/validate_feature_privileges.test.ts b/x-pack/legacy/plugins/security/server/lib/authorization/validate_feature_privileges.test.ts index eee2d3cf97ee3a..6745a00091ceef 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/validate_feature_privileges.test.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/validate_feature_privileges.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature } from '../../../../xpack_main/types'; +import { Feature } from '../../../../../../plugins/features/server'; import { actionsFactory } from './actions'; import { validateFeaturePrivileges } from './validate_feature_privileges'; diff --git a/x-pack/legacy/plugins/security/server/lib/authorization/validate_feature_privileges.ts b/x-pack/legacy/plugins/security/server/lib/authorization/validate_feature_privileges.ts index 818df17df17c9f..0e40ae36c4f729 100644 --- a/x-pack/legacy/plugins/security/server/lib/authorization/validate_feature_privileges.ts +++ b/x-pack/legacy/plugins/security/server/lib/authorization/validate_feature_privileges.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature } from '../../../../xpack_main/types'; +import { Feature } from '../../../../../../plugins/features/server'; import { areActionsFullyCovered } from '../../../common/privilege_calculator_utils'; import { Actions } from './actions'; import { featurePrivilegeBuilderFactory } from './privileges/feature_privilege_builder'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx index 7de4ab23cb2566..4485491f5cd892 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx @@ -7,7 +7,7 @@ import { EuiLink } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { Feature } from '../../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../../plugins/features/server'; import { Space } from '../../../../../common/model/space'; import { SectionPanel } from '../section_panel'; import { EnabledFeatures } from './enabled_features'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx index a1a1655e3f0e38..1c1925a6a4ee0b 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx @@ -8,7 +8,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } from import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment, ReactNode } from 'react'; import { UICapabilities } from 'ui/capabilities'; -import { Feature } from '../../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../../plugins/features/server'; import { Space } from '../../../../../common/model/space'; import { getEnabledFeatures } from '../../lib/feature_utils'; import { SectionPanel } from '../section_panel'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/feature_table.tsx b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/feature_table.tsx index e7d1844d0aff3c..e40bd161b68b2d 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/feature_table.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/feature_table.tsx @@ -8,7 +8,7 @@ import { EuiCheckbox, EuiIcon, EuiInMemoryTable, EuiSwitch, EuiText, IconType } import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import _ from 'lodash'; import React, { ChangeEvent, Component } from 'react'; -import { Feature } from '../../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../../plugins/features/server'; import { Space } from '../../../../../common/model/space'; import { ToggleAllFeatures } from './toggle_all_features'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx index 2ee860744fddab..c629e01fe85a8c 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx @@ -22,7 +22,7 @@ import { capabilities } from 'ui/capabilities'; import { Breadcrumb } from 'ui/chrome'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; -import { Feature } from '../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../plugins/features/server'; import { isReservedSpace } from '../../../../common'; import { Space } from '../../../../common/model/space'; import { SpacesManager } from '../../../lib'; @@ -79,7 +79,7 @@ class ManageSpacePageUI extends Component { const { spaceId, spacesManager, intl, setBreadcrumbs } = this.props; - const getFeatures = kfetch({ method: 'get', pathname: '/api/features/v1' }); + const getFeatures = kfetch({ method: 'get', pathname: '/api/features' }); if (spaceId) { try { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts b/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts index 6846b6697435d5..3420a4ccd7278d 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts +++ b/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature } from '../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../plugins/features/server'; import { getEnabledFeatures } from './feature_utils'; const buildFeatures = () => diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts b/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts index 1693c9b5c89b68..0ff428c7117841 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts +++ b/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature } from '../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../plugins/features/server'; import { Space } from '../../../../common/model/space'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx b/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx index 803b786b2a5b68..38d333099c37e3 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx +++ b/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx @@ -23,7 +23,7 @@ import { capabilities } from 'ui/capabilities'; import { kfetch } from 'ui/kfetch'; // @ts-ignore import { toastNotifications } from 'ui/notify'; -import { Feature } from '../../../../../xpack_main/types'; +import { Feature } from '../../../../../../../plugins/features/server'; import { isReservedSpace } from '../../../../common'; import { DEFAULT_SPACE_ID } from '../../../../common/constants'; import { Space } from '../../../../common/model/space'; @@ -234,7 +234,7 @@ class SpacesGridPageUI extends Component { }); const getSpaces = spacesManager.getSpaces(); - const getFeatures = kfetch({ method: 'get', pathname: '/api/features/v1' }); + const getFeatures = kfetch({ method: 'get', pathname: '/api/features' }); try { const [spaces, features] = await Promise.all([getSpaces, getFeatures]); diff --git a/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts b/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts index ecd196a5a5a7b3..fdc6fda26effd4 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts @@ -6,7 +6,7 @@ import * as Rx from 'rxjs'; import { SavedObject, SavedObjectsService } from 'src/core/server'; -import { Feature } from '../../../../xpack_main/types'; +import { Feature } from '../../../../../../plugins/features/server'; import { convertSavedObjectToSpace } from '../../routes/lib'; import { initSpacesOnPostAuthRequestInterceptor } from './on_post_auth_interceptor'; import { initSpacesOnRequestInterceptor } from './on_request_interceptor'; diff --git a/x-pack/legacy/plugins/spaces/server/lib/toggle_ui_capabilities.test.ts b/x-pack/legacy/plugins/spaces/server/lib/toggle_ui_capabilities.test.ts index fb9c8a85a4e2e0..18de89e33cb9a5 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/toggle_ui_capabilities.test.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/toggle_ui_capabilities.test.ts @@ -5,7 +5,7 @@ */ import { UICapabilities } from 'ui/capabilities'; -import { Feature } from '../../../xpack_main/types'; +import { Feature } from '../../../../../plugins/features/server'; import { Space } from '../../common/model/space'; import { toggleUICapabilities } from './toggle_ui_capabilities'; diff --git a/x-pack/legacy/plugins/spaces/server/lib/toggle_ui_capabilities.ts b/x-pack/legacy/plugins/spaces/server/lib/toggle_ui_capabilities.ts index 4cec018959ab3e..c80ebdf88ef467 100644 --- a/x-pack/legacy/plugins/spaces/server/lib/toggle_ui_capabilities.ts +++ b/x-pack/legacy/plugins/spaces/server/lib/toggle_ui_capabilities.ts @@ -5,7 +5,7 @@ */ import _ from 'lodash'; import { UICapabilities } from 'ui/capabilities'; -import { Feature } from '../../../xpack_main/types'; +import { Feature } from '../../../../../plugins/features/server'; import { Space } from '../../common/model/space'; export function toggleUICapabilities( diff --git a/x-pack/legacy/plugins/xpack_main/index.js b/x-pack/legacy/plugins/xpack_main/index.js index 2fadcf06932f20..e589caa2819e6e 100644 --- a/x-pack/legacy/plugins/xpack_main/index.js +++ b/x-pack/legacy/plugins/xpack_main/index.js @@ -13,14 +13,8 @@ import { getXpackConfigWithDeprecated } from '../telemetry/common/get_xpack_conf import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status'; import { replaceInjectedVars } from './server/lib/replace_injected_vars'; import { setupXPackMain } from './server/lib/setup_xpack_main'; -import { - xpackInfoRoute, - featuresRoute, - settingsRoute, -} from './server/routes/api/v1'; +import { xpackInfoRoute, settingsRoute } from './server/routes/api/v1'; -import { registerOssFeatures } from './server/lib/register_oss_features'; -import { uiCapabilitiesForFeatures } from './server/lib/ui_capabilities_for_features'; import { has } from 'lodash'; function movedToTelemetry(configPath) { @@ -52,7 +46,11 @@ export const xpackMain = (kibana) => { }, uiCapabilities(server) { - return uiCapabilitiesForFeatures(server.plugins.xpack_main); + const featuresPlugin = server.newPlatform.setup.plugins.features; + if (!featuresPlugin) { + throw new Error('New Platform XPack Features plugin is not available.'); + } + return featuresPlugin.getFeaturesUICapabilities(); }, uiExports: { @@ -81,18 +79,21 @@ export const xpackMain = (kibana) => { }, init(server) { + const featuresPlugin = server.newPlatform.setup.plugins.features; + if (!featuresPlugin) { + throw new Error('New Platform XPack Features plugin is not available.'); + } + mirrorPluginStatus(server.plugins.elasticsearch, this, 'yellow', 'red'); - setupXPackMain(server); - const { types: savedObjectTypes } = server.savedObjects; - const config = server.config(); - const isTimelionUiEnabled = config.get('timelion.enabled') && config.get('timelion.ui.enabled'); - registerOssFeatures(server.plugins.xpack_main.registerFeature, savedObjectTypes, isTimelionUiEnabled); + featuresPlugin.registerLegacyAPI({ + xpackInfo: setupXPackMain(server), + savedObjectTypes: server.savedObjects.types + }); // register routes xpackInfoRoute(server); settingsRoute(server, this.kbnServer); - featuresRoute(server); }, deprecations: () => [ movedToTelemetry('telemetry.config'), diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js index 7bc42296b0df4f..5b2c6612d2a87c 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js +++ b/x-pack/legacy/plugins/xpack_main/server/lib/__tests__/setup_xpack_main.js @@ -39,6 +39,7 @@ describe('setupXPackMain()', () => { elasticsearch: mockElasticsearchPlugin, xpack_main: mockXPackMainPlugin }, + newPlatform: { setup: { plugins: { features: {} } } }, events: { on() {} }, log() {}, config() {}, diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts b/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts deleted file mode 100644 index 7ca72b610ec971..00000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts +++ /dev/null @@ -1,401 +0,0 @@ -/* - * 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 Joi from 'joi'; -import { cloneDeep, difference, uniq } from 'lodash'; -import { UICapabilities } from 'ui/capabilities'; -/** - * Feature privilege definition - */ -export interface FeatureKibanaPrivileges { - /** - * Whether or not this specific privilege should be excluded from the base privileges. - */ - excludeFromBasePrivileges?: boolean; - - /** - * If this feature includes management sections, you can specify them here to control visibility of those - * pages based on user privileges. - * - * Example: - * // Enables access to the "Advanced Settings" management page within the Kibana section - * management: { - * kibana: ['settings'] - * } - */ - management?: { - [sectionId: string]: string[]; - }; - - /** - * If this feature includes a catalogue entry, you can specify them here to control visibility based on user permissions. - */ - catalogue?: string[]; - - /** - * If your feature includes server-side APIs, you can tag those routes to secure access based on user permissions. - * - * Example: - * // Configure your routes with a tag starting with the 'access:' prefix - * server.route({ - * path: '/api/my-route', - * method: 'GET', - * handler: () => { ...}, - * options: { - * tags: ['access:my_feature-admin'] - * } - * }); - * - * Then, specify the tags here (without the 'access:' prefix) which should be secured: - * - * { - * api: ['my_feature-admin'] - * } - * - * NOTE: It is important to name your tags in a way that will not collide with other plugins/features. - * A generic tag name like "access:read" could be used elsewhere, and access to that API endpoint would also - * extend to any routes you have also tagged with that name. - */ - api?: string[]; - - /** - * If your feature exposes a client-side application (most of them do!), then you can control access to them here. - * - * Example: - * { - * app: ['my-app', 'kibana'] - * } - * - */ - app?: string[]; - - /** - * If your feature requires access to specific saved objects, then specify your access needs here. - */ - savedObject: { - /** - * List of saved object types which users should have full read/write access to when granted this privilege. - * Example: - * { - * all: ['my-saved-object-type'] - * } - */ - all: string[]; - - /** - * List of saved object types which users should have read-only access to when granted this privilege. - * Example: - * { - * read: ['config'] - * } - */ - read: string[]; - }; - /** - * A list of UI Capabilities that should be granted to users with this privilege. - * These capabilities will automatically be namespaces within your feature id. - * - * Example: - * { - * ui: ['show', 'save'] - * } - * - * This translates in the UI to the following (assuming a feature id of "foo"): - * import { uiCapabilities } from 'ui/capabilities'; - * - * const canShowApp = uiCapabilities.foo.show; - * const canSave = uiCapabilities.foo.save; - * - * Note: Since these are automatically namespaced, you are free to use generic names like "show" and "save". - * - * @see UICapabilities - */ - ui: string[]; -} - -type PrivilegesSet = Record; - -export type FeatureWithAllOrReadPrivileges = Feature<{ - all?: FeatureKibanaPrivileges; - read?: FeatureKibanaPrivileges; -}>; - -/** - * Interface for registering a feature. - * Feature registration allows plugins to hide their applications with spaces, - * and secure access when configured for security. - */ -export interface Feature = PrivilegesSet> { - /** - * Unique identifier for this feature. - * This identifier is also used when generating UI Capabilities. - * - * @see UICapabilities - */ - id: string; - - /** - * Display name for this feature. - * This will be displayed to end-users, so a translatable string is advised for i18n. - */ - name: string; - - /** - * Whether or not this feature should be excluded from the base privileges. - * This is primarily helpful when migrating applications with a "legacy" privileges model - * to use Kibana privileges. We don't want these features to be considered part of the `all` - * or `read` base privileges in a minor release if the user was previously granted access - * using an additional reserved role. - */ - excludeFromBasePrivileges?: boolean; - - /** - * Optional array of supported licenses. - * If omitted, all licenses are allowed. - * This does not restrict access to your feature based on license. - * Its only purpose is to inform the space and roles UIs on which features to display. - */ - validLicenses?: Array<'basic' | 'standard' | 'gold' | 'platinum'>; - - /** - * An optional EUI Icon to be used when displaying your feature. - */ - icon?: string; - - /** - * The optional Nav Link ID for feature. - * If specified, your link will be automatically hidden if needed based on the current space and user permissions. - */ - navLinkId?: string; - - /** - * An array of app ids that are enabled when this feature is enabled. - * Apps specified here will automatically cascade to the privileges defined below, unless specified differently there. - */ - app: string[]; - - /** - * If this feature includes management sections, you can specify them here to control visibility of those - * pages based on the current space. - * - * Items specified here will automatically cascade to the privileges defined below, unless specified differently there. - * - * Example: - * // Enables access to the "Advanced Settings" management page within the Kibana section - * management: { - * kibana: ['settings'] - * } - */ - management?: { - [sectionId: string]: string[]; - }; - /** - * If this feature includes a catalogue entry, you can specify them here to control visibility based on the current space. - * - * Items specified here will automatically cascade to the privileges defined below, unless specified differently there. - */ - catalogue?: string[]; - - /** - * Feature privilege definition. - * - * Example: - * { - * all: {...}, - * read: {...} - * } - * @see FeatureKibanaPrivileges - */ - privileges: TPrivileges; - - /** - * Optional message to display on the Role Management screen when configuring permissions for this feature. - */ - privilegesTooltip?: string; - - /** - * @private - */ - reserved?: { - privilege: FeatureKibanaPrivileges; - description: string; - }; -} - -// Each feature gets its own property on the UICapabilities object, -// but that object has a few built-in properties which should not be overwritten. -const prohibitedFeatureIds: Array = ['catalogue', 'management', 'navLinks']; - -const featurePrivilegePartRegex = /^[a-zA-Z0-9_-]+$/; -const managementSectionIdRegex = /^[a-zA-Z0-9_-]+$/; -export const uiCapabilitiesRegex = /^[a-zA-Z0-9:_-]+$/; - -const managementSchema = Joi.object().pattern( - managementSectionIdRegex, - Joi.array().items(Joi.string().regex(uiCapabilitiesRegex)) -); -const catalogueSchema = Joi.array().items(Joi.string().regex(uiCapabilitiesRegex)); - -const privilegeSchema = Joi.object({ - excludeFromBasePrivileges: Joi.boolean(), - management: managementSchema, - catalogue: catalogueSchema, - api: Joi.array().items(Joi.string()), - app: Joi.array().items(Joi.string()), - savedObject: Joi.object({ - all: Joi.array() - .items(Joi.string()) - .required(), - read: Joi.array() - .items(Joi.string()) - .required(), - }).required(), - ui: Joi.array() - .items(Joi.string().regex(uiCapabilitiesRegex)) - .required(), -}); - -const schema = Joi.object({ - id: Joi.string() - .regex(featurePrivilegePartRegex) - .invalid(...prohibitedFeatureIds) - .required(), - name: Joi.string().required(), - excludeFromBasePrivileges: Joi.boolean(), - validLicenses: Joi.array().items(Joi.string().valid('basic', 'standard', 'gold', 'platinum')), - icon: Joi.string(), - description: Joi.string(), - navLinkId: Joi.string().regex(uiCapabilitiesRegex), - app: Joi.array() - .items(Joi.string()) - .required(), - management: managementSchema, - catalogue: catalogueSchema, - privileges: Joi.object({ - all: privilegeSchema, - read: privilegeSchema, - }).required(), - privilegesTooltip: Joi.string(), - reserved: Joi.object({ - privilege: privilegeSchema.required(), - description: Joi.string().required(), - }), -}); - -export class FeatureRegistry { - private locked = false; - private features: Record = {}; - - public register(feature: FeatureWithAllOrReadPrivileges) { - if (this.locked) { - throw new Error(`Features are locked, can't register new features`); - } - - validateFeature(feature); - - if (feature.id in this.features) { - throw new Error(`Feature with id ${feature.id} is already registered.`); - } - - const featureCopy: Feature = cloneDeep(feature as Feature); - - this.features[feature.id] = applyAutomaticPrivilegeGrants(featureCopy as Feature); - } - - public getAll(): Feature[] { - this.locked = true; - return cloneDeep(Object.values(this.features)); - } -} - -function validateFeature(feature: FeatureWithAllOrReadPrivileges) { - const validateResult = Joi.validate(feature, schema); - if (validateResult.error) { - throw validateResult.error; - } - // the following validation can't be enforced by the Joi schema, since it'd require us looking "up" the object graph for the list of valid value, which they explicitly forbid. - const { app = [], management = {}, catalogue = [] } = feature; - - const privilegeEntries = [...Object.entries(feature.privileges)]; - if (feature.reserved) { - privilegeEntries.push(['reserved', feature.reserved.privilege]); - } - - privilegeEntries.forEach(([privilegeId, privilegeDefinition]) => { - if (!privilegeDefinition) { - throw new Error('Privilege definition may not be null or undefined'); - } - - const unknownAppEntries = difference(privilegeDefinition.app || [], app); - if (unknownAppEntries.length > 0) { - throw new Error( - `Feature privilege ${ - feature.id - }.${privilegeId} has unknown app entries: ${unknownAppEntries.join(', ')}` - ); - } - - const unknownCatalogueEntries = difference(privilegeDefinition.catalogue || [], catalogue); - if (unknownCatalogueEntries.length > 0) { - throw new Error( - `Feature privilege ${ - feature.id - }.${privilegeId} has unknown catalogue entries: ${unknownCatalogueEntries.join(', ')}` - ); - } - - Object.entries(privilegeDefinition.management || {}).forEach( - ([managementSectionId, managementEntry]) => { - if (!management[managementSectionId]) { - throw new Error( - `Feature privilege ${feature.id}.${privilegeId} has unknown management section: ${managementSectionId}` - ); - } - - const unknownSectionEntries = difference(managementEntry, management[managementSectionId]); - - if (unknownSectionEntries.length > 0) { - throw new Error( - `Feature privilege ${ - feature.id - }.${privilegeId} has unknown management entries for section ${managementSectionId}: ${unknownSectionEntries.join( - ', ' - )}` - ); - } - } - ); - }); -} - -function applyAutomaticPrivilegeGrants(feature: Feature): Feature { - const { all: allPrivilege, read: readPrivilege } = feature.privileges; - const reservedPrivilege = feature.reserved ? feature.reserved.privilege : null; - - applyAutomaticAllPrivilegeGrants(allPrivilege, reservedPrivilege); - applyAutomaticReadPrivilegeGrants(readPrivilege); - - return feature; -} - -function applyAutomaticAllPrivilegeGrants(...allPrivileges: Array) { - allPrivileges.forEach(allPrivilege => { - if (allPrivilege) { - allPrivilege.savedObject.all = uniq([...allPrivilege.savedObject.all, 'telemetry']); - allPrivilege.savedObject.read = uniq([...allPrivilege.savedObject.read, 'config', 'url']); - } - }); -} - -function applyAutomaticReadPrivilegeGrants( - ...readPrivileges: Array -) { - readPrivileges.forEach(readPrivilege => { - if (readPrivilege) { - readPrivilege.savedObject.read = uniq([...readPrivilege.savedObject.read, 'config', 'url']); - } - }); -} diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/register_oss_features.test.ts b/x-pack/legacy/plugins/xpack_main/server/lib/register_oss_features.test.ts deleted file mode 100644 index 4456565f0dddf1..00000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/register_oss_features.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 { FeatureRegistry } from './feature_registry'; -import { registerOssFeatures } from './register_oss_features'; - -describe('registerOssFeatures', () => { - it('registers features including timelion', () => { - const registry = new FeatureRegistry(); - const savedObjectTypes = ['foo', 'bar']; - registerOssFeatures(feature => registry.register(feature), savedObjectTypes, true); - - const features = registry.getAll(); - expect(features.map(f => f.id)).toMatchInlineSnapshot(` -Array [ - "discover", - "visualize", - "dashboard", - "dev_tools", - "advancedSettings", - "indexPatterns", - "savedObjectsManagement", - "timelion", -] -`); - }); - - it('registers features excluding timelion', () => { - const registry = new FeatureRegistry(); - const savedObjectTypes = ['foo', 'bar']; - registerOssFeatures(feature => registry.register(feature), savedObjectTypes, false); - - const features = registry.getAll(); - expect(features.map(f => f.id)).toMatchInlineSnapshot(` -Array [ - "discover", - "visualize", - "dashboard", - "dev_tools", - "advancedSettings", - "indexPatterns", - "savedObjectsManagement", -] -`); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js b/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js index bd93283739d939..0155def677c2a6 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js +++ b/x-pack/legacy/plugins/xpack_main/server/lib/setup_xpack_main.js @@ -6,7 +6,6 @@ import { injectXPackInfoSignature } from './inject_xpack_info_signature'; import { XPackInfo } from './xpack_info'; -import { FeatureRegistry } from './feature_registry'; /** * Setup the X-Pack Main plugin. This is fired every time that the Elasticsearch plugin becomes Green. @@ -25,9 +24,9 @@ export function setupXPackMain(server) { server.expose('createXPackInfo', (options) => new XPackInfo(server, options)); server.ext('onPreResponse', (request, h) => injectXPackInfoSignature(info, request, h)); - const featureRegistry = new FeatureRegistry(); - server.expose('registerFeature', (feature) => featureRegistry.register(feature)); - server.expose('getFeatures', () => featureRegistry.getAll()); + const { registerFeature, getFeatures } = server.newPlatform.setup.plugins.features; + server.expose('registerFeature', registerFeature); + server.expose('getFeatures', getFeatures); const setPluginStatus = () => { if (info.isAvailable()) { @@ -46,4 +45,6 @@ export function setupXPackMain(server) { // whenever the license info is updated, regardless of the elasticsearch plugin status // changes, reflect the change in our plugin status. See https://github.com/elastic/kibana/issues/20017 info.onLicenseInfoChange(setPluginStatus); + + return info; } diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/ui_capabilities_for_features.test.ts b/x-pack/legacy/plugins/xpack_main/server/lib/ui_capabilities_for_features.test.ts deleted file mode 100644 index 297ad47a29e27a..00000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/lib/ui_capabilities_for_features.test.ts +++ /dev/null @@ -1,196 +0,0 @@ -/* - * 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 { Feature } from './feature_registry'; -import { uiCapabilitiesForFeatures } from './ui_capabilities_for_features'; - -function getMockXpackMainPlugin(features: Feature[]) { - return { - getFeatures: () => features, - }; -} - -function createFeaturePrivilege(key: string, capabilities: string[] = []) { - return { - [key]: { - savedObject: { - all: [], - read: [], - }, - app: [], - ui: [...capabilities], - }, - }; -} - -describe('populateUICapabilities', () => { - it('handles no original uiCapabilites and no registered features gracefully', () => { - const xpackMainPlugin = getMockXpackMainPlugin([]); - - expect(uiCapabilitiesForFeatures(xpackMainPlugin)).toEqual({}); - }); - - it('handles features with no registered capabilities', () => { - const xpackMainPlugin = getMockXpackMainPlugin([ - { - id: 'newFeature', - name: 'my new feature', - app: ['bar-app'], - privileges: { - ...createFeaturePrivilege('all'), - }, - }, - ]); - - expect(uiCapabilitiesForFeatures(xpackMainPlugin)).toEqual({ - catalogue: {}, - newFeature: {}, - }); - }); - - it('augments the original uiCapabilities with registered feature capabilities', () => { - const xpackMainPlugin = getMockXpackMainPlugin([ - { - id: 'newFeature', - name: 'my new feature', - navLinkId: 'newFeatureNavLink', - app: ['bar-app'], - privileges: { - ...createFeaturePrivilege('all', ['capability1', 'capability2']), - }, - }, - ]); - - expect(uiCapabilitiesForFeatures(xpackMainPlugin)).toEqual({ - catalogue: {}, - newFeature: { - capability1: true, - capability2: true, - }, - }); - }); - - it('combines catalogue entries from multiple features', () => { - const xpackMainPlugin = getMockXpackMainPlugin([ - { - id: 'newFeature', - name: 'my new feature', - navLinkId: 'newFeatureNavLink', - app: ['bar-app'], - catalogue: ['anotherFooEntry', 'anotherBarEntry'], - privileges: { - ...createFeaturePrivilege('foo', ['capability1', 'capability2']), - ...createFeaturePrivilege('bar', ['capability3', 'capability4']), - ...createFeaturePrivilege('baz'), - }, - }, - ]); - - expect(uiCapabilitiesForFeatures(xpackMainPlugin)).toEqual({ - catalogue: { - anotherFooEntry: true, - anotherBarEntry: true, - }, - newFeature: { - capability1: true, - capability2: true, - capability3: true, - capability4: true, - }, - }); - }); - - it(`merges capabilities from all feature privileges`, () => { - const xpackMainPlugin = getMockXpackMainPlugin([ - { - id: 'newFeature', - name: 'my new feature', - navLinkId: 'newFeatureNavLink', - app: ['bar-app'], - privileges: { - ...createFeaturePrivilege('foo', ['capability1', 'capability2']), - ...createFeaturePrivilege('bar', ['capability3', 'capability4']), - ...createFeaturePrivilege('baz', ['capability1', 'capability5']), - }, - }, - ]); - - expect(uiCapabilitiesForFeatures(xpackMainPlugin)).toEqual({ - catalogue: {}, - newFeature: { - capability1: true, - capability2: true, - capability3: true, - capability4: true, - capability5: true, - }, - }); - }); - - it('supports merging multiple features with multiple privileges each', () => { - const xpackMainPlugin = getMockXpackMainPlugin([ - { - id: 'newFeature', - name: 'my new feature', - navLinkId: 'newFeatureNavLink', - app: ['bar-app'], - privileges: { - ...createFeaturePrivilege('foo', ['capability1', 'capability2']), - ...createFeaturePrivilege('bar', ['capability3', 'capability4']), - ...createFeaturePrivilege('baz', ['capability1', 'capability5']), - }, - }, - { - id: 'anotherNewFeature', - name: 'another new feature', - app: ['bar-app'], - privileges: { - ...createFeaturePrivilege('foo', ['capability1', 'capability2']), - ...createFeaturePrivilege('bar', ['capability3', 'capability4']), - }, - }, - { - id: 'yetAnotherNewFeature', - name: 'yet another new feature', - navLinkId: 'yetAnotherNavLink', - app: ['bar-app'], - privileges: { - ...createFeaturePrivilege('all', ['capability1', 'capability2']), - ...createFeaturePrivilege('read', []), - ...createFeaturePrivilege('somethingInBetween', [ - 'something1', - 'something2', - 'something3', - ]), - }, - }, - ]); - - expect(uiCapabilitiesForFeatures(xpackMainPlugin)).toEqual({ - anotherNewFeature: { - capability1: true, - capability2: true, - capability3: true, - capability4: true, - }, - catalogue: {}, - newFeature: { - capability1: true, - capability2: true, - capability3: true, - capability4: true, - capability5: true, - }, - yetAnotherNewFeature: { - capability1: true, - capability2: true, - something1: true, - something2: true, - something3: true, - }, - }); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/features/__snapshots__/features.test.ts.snap b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/features/__snapshots__/features.test.ts.snap deleted file mode 100644 index 856ca089d878e1..00000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/features/__snapshots__/features.test.ts.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GET /api/features/v1 does not return features that arent allowed by current license 1`] = ` -Array [ - Object { - "app": Array [], - "id": "feature_1", - "name": "Feature 1", - "privileges": Object {}, - }, -] -`; - -exports[`GET /api/features/v1 returns a list of available features 1`] = ` -Array [ - Object { - "app": Array [], - "id": "feature_1", - "name": "Feature 1", - "privileges": Object {}, - }, - Object { - "app": Array [ - "bar-app", - ], - "id": "licensed_feature", - "name": "Licensed Feature", - "privileges": Object {}, - "validLicenses": Array [ - "gold", - ], - }, -] -`; diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/features/features.test.ts b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/features/features.test.ts deleted file mode 100644 index c66f15b4698fbb..00000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/features/features.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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 { Server } from 'hapi'; -import { KibanaConfig } from 'src/legacy/server/kbn_server'; -import { FeatureRegistry } from '../../../../lib/feature_registry'; -// @ts-ignore -import { setupXPackMain } from '../../../../lib/setup_xpack_main'; -import { featuresRoute } from './features'; - -let server: Server; -let currentLicenseLevel: string = 'gold'; - -describe('GET /api/features/v1', () => { - beforeAll(() => { - server = new Server(); - - const config: Record = {}; - server.config = () => { - return { - get: (key: string) => { - return config[key]; - }, - } as KibanaConfig; - }; - const featureRegistry = new FeatureRegistry(); - // @ts-ignore - server.plugins.xpack_main = { - getFeatures: () => featureRegistry.getAll(), - info: { - // @ts-ignore - license: { - isOneOf: (candidateLicenses: string[]) => { - return candidateLicenses.includes(currentLicenseLevel); - }, - }, - }, - }; - - featuresRoute(server); - - featureRegistry.register({ - id: 'feature_1', - name: 'Feature 1', - app: [], - privileges: {}, - }); - - featureRegistry.register({ - id: 'licensed_feature', - name: 'Licensed Feature', - app: ['bar-app'], - validLicenses: ['gold'], - privileges: {}, - }); - }); - - it('returns a list of available features', async () => { - const response = await server.inject({ - url: '/api/features/v1', - }); - - expect(response.statusCode).toEqual(200); - expect(JSON.parse(response.payload)).toMatchSnapshot(); - }); - - it(`does not return features that arent allowed by current license`, async () => { - currentLicenseLevel = 'basic'; - - const response = await server.inject({ - url: '/api/features/v1', - }); - - expect(response.statusCode).toEqual(200); - expect(JSON.parse(response.payload)).toMatchSnapshot(); - }); -}); diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/features/features.ts b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/features/features.ts deleted file mode 100644 index 1b707c2a788437..00000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/features/features.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 { Feature } from '../../../../../types'; - -export function featuresRoute(server: Record) { - server.route({ - path: '/api/features/v1', - method: 'GET', - options: { - tags: ['access:features'], - }, - async handler(request: Record) { - const xpackInfo = server.plugins.xpack_main.info; - - const allFeatures: Feature[] = server.plugins.xpack_main.getFeatures(); - - return allFeatures.filter( - feature => - !feature.validLicenses || - !feature.validLicenses.length || - xpackInfo.license.isOneOf(feature.validLicenses) - ); - }, - }); -} diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/features/index.ts b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/features/index.ts deleted file mode 100644 index 856f0b583d7850..00000000000000 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/features/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * 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 { featuresRoute } from './features'; diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js index 863cb8340a0479..c0e59b4ea4ab2c 100644 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js +++ b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/index.js @@ -5,5 +5,4 @@ */ export { xpackInfoRoute } from './xpack_info'; -export { featuresRoute } from './features'; export { settingsRoute } from './settings'; diff --git a/x-pack/legacy/plugins/xpack_main/types.ts b/x-pack/legacy/plugins/xpack_main/types.ts deleted file mode 100644 index 61dd43970d008b..00000000000000 --- a/x-pack/legacy/plugins/xpack_main/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * 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 { - Feature, - FeatureKibanaPrivileges, - uiCapabilitiesRegex, -} from './server/lib/feature_registry'; diff --git a/x-pack/legacy/plugins/xpack_main/xpack_main.d.ts b/x-pack/legacy/plugins/xpack_main/xpack_main.d.ts index 8a2471352c5ff7..8ae2801dededb2 100644 --- a/x-pack/legacy/plugins/xpack_main/xpack_main.d.ts +++ b/x-pack/legacy/plugins/xpack_main/xpack_main.d.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature, FeatureWithAllOrReadPrivileges } from './server/lib/feature_registry'; +import { Feature, FeatureWithAllOrReadPrivileges } from '../../../plugins/features/server'; import { XPackInfo, XPackInfoOptions } from './server/lib/xpack_info'; export { XPackFeature } from './server/lib/xpack_info'; diff --git a/x-pack/plugins/features/kibana.json b/x-pack/plugins/features/kibana.json new file mode 100644 index 00000000000000..553e920f0e720d --- /dev/null +++ b/x-pack/plugins/features/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "features", + "version": "8.0.0", + "kibanaVersion": "kibana", + "optionalPlugins": ["timelion"], + "server": true, + "ui": false +} diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/__snapshots__/feature_registry.test.ts.snap b/x-pack/plugins/features/server/__snapshots__/feature_registry.test.ts.snap similarity index 100% rename from x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/__snapshots__/feature_registry.test.ts.snap rename to x-pack/plugins/features/server/__snapshots__/feature_registry.test.ts.snap diff --git a/x-pack/plugins/features/server/feature.ts b/x-pack/plugins/features/server/feature.ts new file mode 100644 index 00000000000000..592ac654655d88 --- /dev/null +++ b/x-pack/plugins/features/server/feature.ts @@ -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 { FeatureKibanaPrivileges, FeatureKibanaPrivilegesSet } from './feature_kibana_privileges'; + +/** + * Interface for registering a feature. + * Feature registration allows plugins to hide their applications with spaces, + * and secure access when configured for security. + */ +export interface Feature< + TPrivileges extends Partial = FeatureKibanaPrivilegesSet +> { + /** + * Unique identifier for this feature. + * This identifier is also used when generating UI Capabilities. + * + * @see UICapabilities + */ + id: string; + + /** + * Display name for this feature. + * This will be displayed to end-users, so a translatable string is advised for i18n. + */ + name: string; + + /** + * Whether or not this feature should be excluded from the base privileges. + * This is primarily helpful when migrating applications with a "legacy" privileges model + * to use Kibana privileges. We don't want these features to be considered part of the `all` + * or `read` base privileges in a minor release if the user was previously granted access + * using an additional reserved role. + */ + excludeFromBasePrivileges?: boolean; + + /** + * Optional array of supported licenses. + * If omitted, all licenses are allowed. + * This does not restrict access to your feature based on license. + * Its only purpose is to inform the space and roles UIs on which features to display. + */ + validLicenses?: Array<'basic' | 'standard' | 'gold' | 'platinum'>; + + /** + * An optional EUI Icon to be used when displaying your feature. + */ + icon?: string; + + /** + * The optional Nav Link ID for feature. + * If specified, your link will be automatically hidden if needed based on the current space and user permissions. + */ + navLinkId?: string; + + /** + * An array of app ids that are enabled when this feature is enabled. + * Apps specified here will automatically cascade to the privileges defined below, unless specified differently there. + */ + app: string[]; + + /** + * If this feature includes management sections, you can specify them here to control visibility of those + * pages based on the current space. + * + * Items specified here will automatically cascade to the privileges defined below, unless specified differently there. + * + * @example + * ```ts + * // Enables access to the "Advanced Settings" management page within the Kibana section + * management: { + * kibana: ['settings'] + * } + * ``` + */ + management?: { + [sectionId: string]: string[]; + }; + /** + * If this feature includes a catalogue entry, you can specify them here to control visibility based on the current space. + * + * Items specified here will automatically cascade to the privileges defined below, unless specified differently there. + */ + catalogue?: string[]; + + /** + * Feature privilege definition. + * + * @example + * ```ts + * { + * all: {...}, + * read: {...} + * } + * ``` + * @see FeatureKibanaPrivileges + */ + privileges: TPrivileges; + + /** + * Optional message to display on the Role Management screen when configuring permissions for this feature. + */ + privilegesTooltip?: string; + + /** + * @private + */ + reserved?: { + privilege: FeatureKibanaPrivileges; + description: string; + }; +} + +export type FeatureWithAllOrReadPrivileges = Feature<{ + all?: FeatureKibanaPrivileges; + read?: FeatureKibanaPrivileges; +}>; diff --git a/x-pack/plugins/features/server/feature_kibana_privileges.ts b/x-pack/plugins/features/server/feature_kibana_privileges.ts new file mode 100644 index 00000000000000..1d14f3728282cd --- /dev/null +++ b/x-pack/plugins/features/server/feature_kibana_privileges.ts @@ -0,0 +1,127 @@ +/* + * 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. + */ + +/** + * Feature privilege definition + */ +export interface FeatureKibanaPrivileges { + /** + * Whether or not this specific privilege should be excluded from the base privileges. + */ + excludeFromBasePrivileges?: boolean; + + /** + * If this feature includes management sections, you can specify them here to control visibility of those + * pages based on user privileges. + * + * @example + * ```ts + * // Enables access to the "Advanced Settings" management page within the Kibana section + * management: { + * kibana: ['settings'] + * } + * ``` + */ + management?: { + [sectionId: string]: string[]; + }; + + /** + * If this feature includes a catalogue entry, you can specify them here to control visibility based on user permissions. + */ + catalogue?: string[]; + + /** + * If your feature includes server-side APIs, you can tag those routes to secure access based on user permissions. + * + * @example + * ```ts + * // Configure your routes with a tag starting with the 'access:' prefix + * server.route({ + * path: '/api/my-route', + * method: 'GET', + * handler: () => { ...}, + * options: { + * tags: ['access:my_feature-admin'] + * } + * }); + * + * Then, specify the tags here (without the 'access:' prefix) which should be secured: + * + * { + * api: ['my_feature-admin'] + * } + * ``` + * + * NOTE: It is important to name your tags in a way that will not collide with other plugins/features. + * A generic tag name like "access:read" could be used elsewhere, and access to that API endpoint would also + * extend to any routes you have also tagged with that name. + */ + api?: string[]; + + /** + * If your feature exposes a client-side application (most of them do!), then you can control access to them here. + * + * @example + * ```ts + * { + * app: ['my-app', 'kibana'] + * } + * ``` + * + */ + app?: string[]; + + /** + * If your feature requires access to specific saved objects, then specify your access needs here. + */ + savedObject: { + /** + * List of saved object types which users should have full read/write access to when granted this privilege. + * @example + * ```ts + * { + * all: ['my-saved-object-type'] + * } + * ``` + */ + all: string[]; + + /** + * List of saved object types which users should have read-only access to when granted this privilege. + * @example + * ```ts + * { + * read: ['config'] + * } + * ``` + */ + read: string[]; + }; + /** + * A list of UI Capabilities that should be granted to users with this privilege. + * These capabilities will automatically be namespaces within your feature id. + * + * @example + * ```ts + * { + * ui: ['show', 'save'] + * } + * + * This translates in the UI to the following (assuming a feature id of "foo"): + * import { uiCapabilities } from 'ui/capabilities'; + * + * const canShowApp = uiCapabilities.foo.show; + * const canSave = uiCapabilities.foo.save; + * ``` + * Note: Since these are automatically namespaced, you are free to use generic names like "show" and "save". + * + * @see UICapabilities + */ + ui: string[]; +} + +export type FeatureKibanaPrivilegesSet = Record; diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts b/x-pack/plugins/features/server/feature_registry.test.ts similarity index 99% rename from x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts rename to x-pack/plugins/features/server/feature_registry.test.ts index 0dac8c55869955..1e2814021f0f65 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts +++ b/x-pack/plugins/features/server/feature_registry.test.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature, FeatureRegistry } from './feature_registry'; +import { FeatureRegistry } from './feature_registry'; +import { Feature } from './feature'; describe('FeatureRegistry', () => { it('allows a minimal feature to be registered', () => { diff --git a/x-pack/plugins/features/server/feature_registry.ts b/x-pack/plugins/features/server/feature_registry.ts new file mode 100644 index 00000000000000..c430811f2ead5e --- /dev/null +++ b/x-pack/plugins/features/server/feature_registry.ts @@ -0,0 +1,65 @@ +/* + * 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 { cloneDeep, uniq } from 'lodash'; +import { FeatureKibanaPrivileges } from './feature_kibana_privileges'; +import { Feature, FeatureWithAllOrReadPrivileges } from './feature'; +import { validateFeature } from './feature_schema'; + +export class FeatureRegistry { + private locked = false; + private features: Record = {}; + + public register(feature: FeatureWithAllOrReadPrivileges) { + if (this.locked) { + throw new Error(`Features are locked, can't register new features`); + } + + validateFeature(feature); + + if (feature.id in this.features) { + throw new Error(`Feature with id ${feature.id} is already registered.`); + } + + const featureCopy: Feature = cloneDeep(feature as Feature); + + this.features[feature.id] = applyAutomaticPrivilegeGrants(featureCopy as Feature); + } + + public getAll(): Feature[] { + this.locked = true; + return cloneDeep(Object.values(this.features)); + } +} + +function applyAutomaticPrivilegeGrants(feature: Feature): Feature { + const { all: allPrivilege, read: readPrivilege } = feature.privileges; + const reservedPrivilege = feature.reserved ? feature.reserved.privilege : null; + + applyAutomaticAllPrivilegeGrants(allPrivilege, reservedPrivilege); + applyAutomaticReadPrivilegeGrants(readPrivilege); + + return feature; +} + +function applyAutomaticAllPrivilegeGrants(...allPrivileges: Array) { + allPrivileges.forEach(allPrivilege => { + if (allPrivilege) { + allPrivilege.savedObject.all = uniq([...allPrivilege.savedObject.all, 'telemetry']); + allPrivilege.savedObject.read = uniq([...allPrivilege.savedObject.read, 'config', 'url']); + } + }); +} + +function applyAutomaticReadPrivilegeGrants( + ...readPrivileges: Array +) { + readPrivileges.forEach(readPrivilege => { + if (readPrivilege) { + readPrivilege.savedObject.read = uniq([...readPrivilege.savedObject.read, 'config', 'url']); + } + }); +} diff --git a/x-pack/plugins/features/server/feature_schema.ts b/x-pack/plugins/features/server/feature_schema.ts new file mode 100644 index 00000000000000..9ae55a61c910fb --- /dev/null +++ b/x-pack/plugins/features/server/feature_schema.ts @@ -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 Joi from 'joi'; + +import { difference } from 'lodash'; +import { Capabilities as UICapabilities } from '../../../../src/core/public'; +import { FeatureWithAllOrReadPrivileges } from './feature'; + +// Each feature gets its own property on the UICapabilities object, +// but that object has a few built-in properties which should not be overwritten. +const prohibitedFeatureIds: Array = ['catalogue', 'management', 'navLinks']; + +const featurePrivilegePartRegex = /^[a-zA-Z0-9_-]+$/; +const managementSectionIdRegex = /^[a-zA-Z0-9_-]+$/; +export const uiCapabilitiesRegex = /^[a-zA-Z0-9:_-]+$/; + +const managementSchema = Joi.object().pattern( + managementSectionIdRegex, + Joi.array().items(Joi.string().regex(uiCapabilitiesRegex)) +); +const catalogueSchema = Joi.array().items(Joi.string().regex(uiCapabilitiesRegex)); + +const privilegeSchema = Joi.object({ + excludeFromBasePrivileges: Joi.boolean(), + management: managementSchema, + catalogue: catalogueSchema, + api: Joi.array().items(Joi.string()), + app: Joi.array().items(Joi.string()), + savedObject: Joi.object({ + all: Joi.array() + .items(Joi.string()) + .required(), + read: Joi.array() + .items(Joi.string()) + .required(), + }).required(), + ui: Joi.array() + .items(Joi.string().regex(uiCapabilitiesRegex)) + .required(), +}); + +const schema = Joi.object({ + id: Joi.string() + .regex(featurePrivilegePartRegex) + .invalid(...prohibitedFeatureIds) + .required(), + name: Joi.string().required(), + excludeFromBasePrivileges: Joi.boolean(), + validLicenses: Joi.array().items(Joi.string().valid('basic', 'standard', 'gold', 'platinum')), + icon: Joi.string(), + description: Joi.string(), + navLinkId: Joi.string().regex(uiCapabilitiesRegex), + app: Joi.array() + .items(Joi.string()) + .required(), + management: managementSchema, + catalogue: catalogueSchema, + privileges: Joi.object({ + all: privilegeSchema, + read: privilegeSchema, + }).required(), + privilegesTooltip: Joi.string(), + reserved: Joi.object({ + privilege: privilegeSchema.required(), + description: Joi.string().required(), + }), +}); + +export function validateFeature(feature: FeatureWithAllOrReadPrivileges) { + const validateResult = Joi.validate(feature, schema); + if (validateResult.error) { + throw validateResult.error; + } + // the following validation can't be enforced by the Joi schema, since it'd require us looking "up" the object graph for the list of valid value, which they explicitly forbid. + const { app = [], management = {}, catalogue = [] } = feature; + + const privilegeEntries = [...Object.entries(feature.privileges)]; + if (feature.reserved) { + privilegeEntries.push(['reserved', feature.reserved.privilege]); + } + + privilegeEntries.forEach(([privilegeId, privilegeDefinition]) => { + if (!privilegeDefinition) { + throw new Error('Privilege definition may not be null or undefined'); + } + + const unknownAppEntries = difference(privilegeDefinition.app || [], app); + if (unknownAppEntries.length > 0) { + throw new Error( + `Feature privilege ${ + feature.id + }.${privilegeId} has unknown app entries: ${unknownAppEntries.join(', ')}` + ); + } + + const unknownCatalogueEntries = difference(privilegeDefinition.catalogue || [], catalogue); + if (unknownCatalogueEntries.length > 0) { + throw new Error( + `Feature privilege ${ + feature.id + }.${privilegeId} has unknown catalogue entries: ${unknownCatalogueEntries.join(', ')}` + ); + } + + Object.entries(privilegeDefinition.management || {}).forEach( + ([managementSectionId, managementEntry]) => { + if (!management[managementSectionId]) { + throw new Error( + `Feature privilege ${feature.id}.${privilegeId} has unknown management section: ${managementSectionId}` + ); + } + + const unknownSectionEntries = difference(managementEntry, management[managementSectionId]); + + if (unknownSectionEntries.length > 0) { + throw new Error( + `Feature privilege ${ + feature.id + }.${privilegeId} has unknown management entries for section ${managementSectionId}: ${unknownSectionEntries.join( + ', ' + )}` + ); + } + } + ); + }); +} diff --git a/x-pack/plugins/features/server/index.ts b/x-pack/plugins/features/server/index.ts new file mode 100644 index 00000000000000..6a08c25fb7780b --- /dev/null +++ b/x-pack/plugins/features/server/index.ts @@ -0,0 +1,21 @@ +/* + * 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 { PluginInitializerContext } from '../../../../src/core/server'; +import { Plugin } from './plugin'; + +// These exports are part of public Features plugin contract, any change in signature of exported +// functions or removal of exports should be considered as a breaking change. Ideally we should +// reduce number of such exports to zero and provide everything we want to expose via Setup/Start +// run-time contracts. +export { uiCapabilitiesRegex } from './feature_schema'; + +export { Feature, FeatureWithAllOrReadPrivileges } from './feature'; +export { FeatureKibanaPrivileges } from './feature_kibana_privileges'; +export { PluginSetupContract } from './plugin'; + +export const plugin = (initializerContext: PluginInitializerContext) => + new Plugin(initializerContext); diff --git a/x-pack/plugins/features/server/oss_features.test.ts b/x-pack/plugins/features/server/oss_features.test.ts new file mode 100644 index 00000000000000..987af08fe7cda6 --- /dev/null +++ b/x-pack/plugins/features/server/oss_features.test.ts @@ -0,0 +1,42 @@ +/* + * 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 { buildOSSFeatures } from './oss_features'; + +describe('buildOSSFeatures', () => { + it('returns features including timelion', () => { + expect( + buildOSSFeatures({ savedObjectTypes: ['foo', 'bar'], includeTimelion: true }).map(f => f.id) + ).toMatchInlineSnapshot(` +Array [ + "discover", + "visualize", + "dashboard", + "dev_tools", + "advancedSettings", + "indexPatterns", + "savedObjectsManagement", + "timelion", +] +`); + }); + + it('returns features excluding timelion', () => { + expect( + buildOSSFeatures({ savedObjectTypes: ['foo', 'bar'], includeTimelion: false }).map(f => f.id) + ).toMatchInlineSnapshot(` +Array [ + "discover", + "visualize", + "dashboard", + "dev_tools", + "advancedSettings", + "indexPatterns", + "savedObjectsManagement", +] +`); + }); +}); diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/register_oss_features.ts b/x-pack/plugins/features/server/oss_features.ts similarity index 83% rename from x-pack/legacy/plugins/xpack_main/server/lib/register_oss_features.ts rename to x-pack/plugins/features/server/oss_features.ts index 8573798b57b842..9125e1924a702a 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/register_oss_features.ts +++ b/x-pack/plugins/features/server/oss_features.ts @@ -4,13 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { Feature } from './feature_registry'; +import { Feature } from './feature'; -const buildKibanaFeatures = (savedObjectTypes: string[]) => { +export interface BuildOSSFeaturesParams { + savedObjectTypes: string[]; + includeTimelion: boolean; +} + +export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSSFeaturesParams) => { return [ { id: 'discover', - name: i18n.translate('xpack.main.featureRegistry.discoverFeatureName', { + name: i18n.translate('xpack.features.discoverFeatureName', { defaultMessage: 'Discover', }), icon: 'discoverApp', @@ -36,7 +41,7 @@ const buildKibanaFeatures = (savedObjectTypes: string[]) => { }, { id: 'visualize', - name: i18n.translate('xpack.main.featureRegistry.visualizeFeatureName', { + name: i18n.translate('xpack.features.visualizeFeatureName', { defaultMessage: 'Visualize', }), icon: 'visualizeApp', @@ -62,7 +67,7 @@ const buildKibanaFeatures = (savedObjectTypes: string[]) => { }, { id: 'dashboard', - name: i18n.translate('xpack.main.featureRegistry.dashboardFeatureName', { + name: i18n.translate('xpack.features.dashboardFeatureName', { defaultMessage: 'Dashboard', }), icon: 'dashboardApp', @@ -104,7 +109,7 @@ const buildKibanaFeatures = (savedObjectTypes: string[]) => { }, { id: 'dev_tools', - name: i18n.translate('xpack.main.featureRegistry.devToolsFeatureName', { + name: i18n.translate('xpack.features.devToolsFeatureName', { defaultMessage: 'Dev Tools', }), icon: 'devToolsApp', @@ -129,14 +134,14 @@ const buildKibanaFeatures = (savedObjectTypes: string[]) => { ui: ['show'], }, }, - privilegesTooltip: i18n.translate('xpack.main.featureRegistry.devToolsPrivilegesTooltip', { + privilegesTooltip: i18n.translate('xpack.features.devToolsPrivilegesTooltip', { defaultMessage: 'User should also be granted the appropriate Elasticsearch cluster and index privileges', }), }, { id: 'advancedSettings', - name: i18n.translate('xpack.main.featureRegistry.advancedSettingsFeatureName', { + name: i18n.translate('xpack.features.advancedSettingsFeatureName', { defaultMessage: 'Advanced Settings', }), icon: 'advancedSettingsApp', @@ -164,7 +169,7 @@ const buildKibanaFeatures = (savedObjectTypes: string[]) => { }, { id: 'indexPatterns', - name: i18n.translate('xpack.main.featureRegistry.indexPatternFeatureName', { + name: i18n.translate('xpack.features.indexPatternFeatureName', { defaultMessage: 'Index Pattern Management', }), icon: 'indexPatternApp', @@ -192,7 +197,7 @@ const buildKibanaFeatures = (savedObjectTypes: string[]) => { }, { id: 'savedObjectsManagement', - name: i18n.translate('xpack.main.featureRegistry.savedObjectsManagementFeatureName', { + name: i18n.translate('xpack.features.savedObjectsManagementFeatureName', { defaultMessage: 'Saved Objects Management', }), icon: 'savedObjectsApp', @@ -220,6 +225,7 @@ const buildKibanaFeatures = (savedObjectTypes: string[]) => { }, }, }, + ...(includeTimelion ? [timelionFeature] : []), ]; }; @@ -247,17 +253,3 @@ const timelionFeature: Feature = { }, }, }; - -export function registerOssFeatures( - registerFeature: (feature: Feature) => void, - savedObjectTypes: string[], - includeTimelion: boolean -) { - for (const feature of buildKibanaFeatures(savedObjectTypes)) { - registerFeature(feature); - } - - if (includeTimelion) { - registerFeature(timelionFeature); - } -} diff --git a/x-pack/plugins/features/server/plugin.ts b/x-pack/plugins/features/server/plugin.ts new file mode 100644 index 00000000000000..ce7fea129bb6f2 --- /dev/null +++ b/x-pack/plugins/features/server/plugin.ts @@ -0,0 +1,98 @@ +/* + * 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 { + CoreSetup, + Logger, + PluginInitializerContext, + RecursiveReadonly, +} from '../../../../src/core/server'; +import { Capabilities as UICapabilities } from '../../../../src/core/public'; +import { deepFreeze } from '../../../../src/core/utils'; +import { XPackInfo } from '../../../legacy/plugins/xpack_main/server/lib/xpack_info'; +import { PluginSetupContract as TimelionSetupContract } from '../../../../src/plugins/timelion/server'; +import { FeatureRegistry } from './feature_registry'; +import { Feature, FeatureWithAllOrReadPrivileges } from './feature'; +import { uiCapabilitiesForFeatures } from './ui_capabilities_for_features'; +import { buildOSSFeatures } from './oss_features'; +import { defineRoutes } from './routes'; + +/** + * Describes public Features plugin contract returned at the `setup` stage. + */ +export interface PluginSetupContract { + registerFeature(feature: FeatureWithAllOrReadPrivileges): void; + getFeatures(): Feature[]; + getFeaturesUICapabilities(): UICapabilities; + registerLegacyAPI: (legacyAPI: LegacyAPI) => void; +} + +/** + * Describes a set of APIs that are available in the legacy platform only and required by this plugin + * to function properly. + */ +export interface LegacyAPI { + xpackInfo: Pick; + savedObjectTypes: string[]; +} + +/** + * Represents Features Plugin instance that will be managed by the Kibana plugin system. + */ +export class Plugin { + private readonly logger: Logger; + + private legacyAPI?: LegacyAPI; + private readonly getLegacyAPI = () => { + if (!this.legacyAPI) { + throw new Error('Legacy API is not registered!'); + } + return this.legacyAPI; + }; + + constructor(private readonly initializerContext: PluginInitializerContext) { + this.logger = this.initializerContext.logger.get(); + } + + public async setup( + core: CoreSetup, + { timelion }: { timelion?: TimelionSetupContract } + ): Promise> { + const featureRegistry = new FeatureRegistry(); + + defineRoutes({ + router: core.http.createRouter(), + featureRegistry, + getLegacyAPI: this.getLegacyAPI, + }); + + return deepFreeze({ + registerFeature: featureRegistry.register.bind(featureRegistry), + getFeatures: featureRegistry.getAll.bind(featureRegistry), + getFeaturesUICapabilities: () => uiCapabilitiesForFeatures(featureRegistry.getAll()), + + registerLegacyAPI: (legacyAPI: LegacyAPI) => { + this.legacyAPI = legacyAPI; + + // Register OSS features. + for (const feature of buildOSSFeatures({ + savedObjectTypes: this.legacyAPI.savedObjectTypes, + includeTimelion: timelion !== undefined && timelion.uiEnabled, + })) { + featureRegistry.register(feature); + } + }, + }); + } + + public start() { + this.logger.debug('Starting plugin'); + } + + public stop() { + this.logger.debug('Stopping plugin'); + } +} diff --git a/x-pack/plugins/features/server/routes/index.test.ts b/x-pack/plugins/features/server/routes/index.test.ts new file mode 100644 index 00000000000000..98a23a61d542c8 --- /dev/null +++ b/x-pack/plugins/features/server/routes/index.test.ts @@ -0,0 +1,110 @@ +/* + * 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 { FeatureRegistry } from '../feature_registry'; +import { defineRoutes } from './index'; + +import { httpServerMock, httpServiceMock } from '../../../../../src/core/server/mocks'; +import { XPackInfoLicense } from '../../../../legacy/plugins/xpack_main/server/lib/xpack_info_license'; +import { RequestHandler } from '../../../../../src/core/server'; + +let currentLicenseLevel: string = 'gold'; + +describe('GET /api/features', () => { + let routeHandler: RequestHandler; + beforeEach(() => { + const featureRegistry = new FeatureRegistry(); + featureRegistry.register({ + id: 'feature_1', + name: 'Feature 1', + app: [], + privileges: {}, + }); + + featureRegistry.register({ + id: 'licensed_feature', + name: 'Licensed Feature', + app: ['bar-app'], + validLicenses: ['gold'], + privileges: {}, + }); + + const routerMock = httpServiceMock.createRouter(); + defineRoutes({ + router: routerMock, + featureRegistry, + getLegacyAPI: () => ({ + xpackInfo: { + license: { + isOneOf(candidateLicenses: string[]) { + return candidateLicenses.includes(currentLicenseLevel); + }, + } as XPackInfoLicense, + }, + savedObjectTypes: [], + }), + }); + + routeHandler = routerMock.get.mock.calls[0][1]; + }); + + it('returns a list of available features', async () => { + const mockResponse = httpServerMock.createResponseFactory(); + routeHandler(undefined as any, undefined as any, mockResponse); + + expect(mockResponse.ok.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "body": Array [ + Object { + "app": Array [], + "id": "feature_1", + "name": "Feature 1", + "privileges": Object {}, + }, + Object { + "app": Array [ + "bar-app", + ], + "id": "licensed_feature", + "name": "Licensed Feature", + "privileges": Object {}, + "validLicenses": Array [ + "gold", + ], + }, + ], + }, + ], + ] + `); + }); + + it(`does not return features that arent allowed by current license`, async () => { + currentLicenseLevel = 'basic'; + + const mockResponse = httpServerMock.createResponseFactory(); + routeHandler(undefined as any, undefined as any, mockResponse); + + expect(mockResponse.ok.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "body": Array [ + Object { + "app": Array [], + "id": "feature_1", + "name": "Feature 1", + "privileges": Object {}, + }, + ], + }, + ], + ] + `); + }); +}); diff --git a/x-pack/plugins/features/server/routes/index.ts b/x-pack/plugins/features/server/routes/index.ts new file mode 100644 index 00000000000000..51869c39cf83c1 --- /dev/null +++ b/x-pack/plugins/features/server/routes/index.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from '../../../../../src/core/server'; +import { LegacyAPI } from '../plugin'; +import { FeatureRegistry } from '../feature_registry'; + +/** + * Describes parameters used to define HTTP routes. + */ +export interface RouteDefinitionParams { + router: IRouter; + featureRegistry: FeatureRegistry; + getLegacyAPI: () => LegacyAPI; +} + +export function defineRoutes({ router, featureRegistry, getLegacyAPI }: RouteDefinitionParams) { + router.get( + { path: '/api/features', options: { tags: ['access:features'] }, validate: false }, + (context, request, response) => { + const allFeatures = featureRegistry.getAll(); + + return response.ok({ + body: allFeatures.filter( + feature => + !feature.validLicenses || + !feature.validLicenses.length || + getLegacyAPI().xpackInfo.license.isOneOf(feature.validLicenses) + ), + }); + } + ); +} diff --git a/x-pack/plugins/features/server/ui_capabilities_for_features.test.ts b/x-pack/plugins/features/server/ui_capabilities_for_features.test.ts new file mode 100644 index 00000000000000..bb2cd82891a150 --- /dev/null +++ b/x-pack/plugins/features/server/ui_capabilities_for_features.test.ts @@ -0,0 +1,187 @@ +/* + * 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 { uiCapabilitiesForFeatures } from './ui_capabilities_for_features'; + +function createFeaturePrivilege(key: string, capabilities: string[] = []) { + return { + [key]: { + savedObject: { + all: [], + read: [], + }, + app: [], + ui: [...capabilities], + }, + }; +} + +describe('populateUICapabilities', () => { + it('handles no original uiCapabilities and no registered features gracefully', () => { + expect(uiCapabilitiesForFeatures([])).toEqual({}); + }); + + it('handles features with no registered capabilities', () => { + expect( + uiCapabilitiesForFeatures([ + { + id: 'newFeature', + name: 'my new feature', + app: ['bar-app'], + privileges: { + ...createFeaturePrivilege('all'), + }, + }, + ]) + ).toEqual({ + catalogue: {}, + newFeature: {}, + }); + }); + + it('augments the original uiCapabilities with registered feature capabilities', () => { + expect( + uiCapabilitiesForFeatures([ + { + id: 'newFeature', + name: 'my new feature', + navLinkId: 'newFeatureNavLink', + app: ['bar-app'], + privileges: { + ...createFeaturePrivilege('all', ['capability1', 'capability2']), + }, + }, + ]) + ).toEqual({ + catalogue: {}, + newFeature: { + capability1: true, + capability2: true, + }, + }); + }); + + it('combines catalogue entries from multiple features', () => { + expect( + uiCapabilitiesForFeatures([ + { + id: 'newFeature', + name: 'my new feature', + navLinkId: 'newFeatureNavLink', + app: ['bar-app'], + catalogue: ['anotherFooEntry', 'anotherBarEntry'], + privileges: { + ...createFeaturePrivilege('foo', ['capability1', 'capability2']), + ...createFeaturePrivilege('bar', ['capability3', 'capability4']), + ...createFeaturePrivilege('baz'), + }, + }, + ]) + ).toEqual({ + catalogue: { + anotherFooEntry: true, + anotherBarEntry: true, + }, + newFeature: { + capability1: true, + capability2: true, + capability3: true, + capability4: true, + }, + }); + }); + + it(`merges capabilities from all feature privileges`, () => { + expect( + uiCapabilitiesForFeatures([ + { + id: 'newFeature', + name: 'my new feature', + navLinkId: 'newFeatureNavLink', + app: ['bar-app'], + privileges: { + ...createFeaturePrivilege('foo', ['capability1', 'capability2']), + ...createFeaturePrivilege('bar', ['capability3', 'capability4']), + ...createFeaturePrivilege('baz', ['capability1', 'capability5']), + }, + }, + ]) + ).toEqual({ + catalogue: {}, + newFeature: { + capability1: true, + capability2: true, + capability3: true, + capability4: true, + capability5: true, + }, + }); + }); + + it('supports merging multiple features with multiple privileges each', () => { + expect( + uiCapabilitiesForFeatures([ + { + id: 'newFeature', + name: 'my new feature', + navLinkId: 'newFeatureNavLink', + app: ['bar-app'], + privileges: { + ...createFeaturePrivilege('foo', ['capability1', 'capability2']), + ...createFeaturePrivilege('bar', ['capability3', 'capability4']), + ...createFeaturePrivilege('baz', ['capability1', 'capability5']), + }, + }, + { + id: 'anotherNewFeature', + name: 'another new feature', + app: ['bar-app'], + privileges: { + ...createFeaturePrivilege('foo', ['capability1', 'capability2']), + ...createFeaturePrivilege('bar', ['capability3', 'capability4']), + }, + }, + { + id: 'yetAnotherNewFeature', + name: 'yet another new feature', + navLinkId: 'yetAnotherNavLink', + app: ['bar-app'], + privileges: { + ...createFeaturePrivilege('all', ['capability1', 'capability2']), + ...createFeaturePrivilege('read', []), + ...createFeaturePrivilege('somethingInBetween', [ + 'something1', + 'something2', + 'something3', + ]), + }, + }, + ]) + ).toEqual({ + anotherNewFeature: { + capability1: true, + capability2: true, + capability3: true, + capability4: true, + }, + catalogue: {}, + newFeature: { + capability1: true, + capability2: true, + capability3: true, + capability4: true, + capability5: true, + }, + yetAnotherNewFeature: { + capability1: true, + capability2: true, + something1: true, + something2: true, + something3: true, + }, + }); + }); +}); diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/ui_capabilities_for_features.ts b/x-pack/plugins/features/server/ui_capabilities_for_features.ts similarity index 89% rename from x-pack/legacy/plugins/xpack_main/server/lib/ui_capabilities_for_features.ts rename to x-pack/plugins/features/server/ui_capabilities_for_features.ts index c63a5fb026d0d5..0b3bdfb5993431 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/ui_capabilities_for_features.ts +++ b/x-pack/plugins/features/server/ui_capabilities_for_features.ts @@ -5,8 +5,8 @@ */ import _ from 'lodash'; -import { UICapabilities } from 'ui/capabilities'; -import { Feature } from '../../types'; +import { Capabilities as UICapabilities } from '../../../../src/core/public'; +import { Feature } from './feature'; const ELIGIBLE_FLAT_MERGE_KEYS = ['catalogue']; @@ -14,9 +14,7 @@ interface FeatureCapabilities { [featureId: string]: Record; } -export function uiCapabilitiesForFeatures(xpackMainPlugin: Record): UICapabilities { - const features: Feature[] = xpackMainPlugin.getFeatures(); - +export function uiCapabilitiesForFeatures(features: Feature[]): UICapabilities { const featureCapabilities: FeatureCapabilities[] = features.map(getCapabilitiesFromFeature); return buildCapabilities(...featureCapabilities); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9cabd72e00f81c..df857c63ea2faa 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4503,6 +4503,14 @@ "xpack.dashboardMode.dashboardViewerTitle": "ダッシュボードビューアー", "xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDescription": "ダッシュボード表示専用モードのロールです", "xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle": "ダッシュボード専用ロール", + "xpack.features.advancedSettingsFeatureName": "高度な設定", + "xpack.features.dashboardFeatureName": "ダッシュボード", + "xpack.features.devToolsFeatureName": "開発ツール", + "xpack.features.devToolsPrivilegesTooltip": "また、ユーザーに適切な Elasticsearch クラスターとインデックスの権限が与えられている必要があります。", + "xpack.features.discoverFeatureName": "ディスカバー", + "xpack.features.indexPatternFeatureName": "インデックスパターン管理", + "xpack.features.savedObjectsManagementFeatureName": "保存されたオブジェクトの管理", + "xpack.features.visualizeFeatureName": "可視化", "xpack.graph.badge.readOnly.text": "読み込み専用", "xpack.graph.badge.readOnly.tooltip": "Graph ワークスペースを保存できません", "xpack.graph.clearWorkspace.confirmButtonLabel": "ワークスペースを消去", @@ -5373,14 +5381,6 @@ "xpack.logstash.upgradeFailureActions.goBackButtonLabel": "戻る", "xpack.logstash.upstreamPipelineArgumentMustContainAnIdPropertyErrorMessage": "upstreamPipeline 引数には id プロパティを含める必要があります", "xpack.logstash.workersTooltip": "パイプラインのフィルターとアウトプットステージを同時に実行するワーカーの数です。イベントが詰まってしまう場合や CPU が飽和状態ではない場合は、マシンの処理能力をより有効に活用するため、この数字を上げてみてください。\n\nデフォルト値:ホストの CPU コア数です", - "xpack.main.featureRegistry.advancedSettingsFeatureName": "高度な設定", - "xpack.main.featureRegistry.dashboardFeatureName": "ダッシュボード", - "xpack.main.featureRegistry.devToolsFeatureName": "開発ツール", - "xpack.main.featureRegistry.devToolsPrivilegesTooltip": "また、ユーザーに適切な Elasticsearch クラスターとインデックスの権限が与えられている必要があります。", - "xpack.main.featureRegistry.discoverFeatureName": "ディスカバー", - "xpack.main.featureRegistry.indexPatternFeatureName": "インデックスパターン管理", - "xpack.main.featureRegistry.savedObjectsManagementFeatureName": "保存されたオブジェクトの管理", - "xpack.main.featureRegistry.visualizeFeatureName": "可視化", "xpack.main.welcomeBanner.licenseIsExpiredDescription": "管理者または {updateYourLicenseLinkText} に直接お問い合わせください。", "xpack.main.welcomeBanner.licenseIsExpiredDescription.updateYourLicenseLinkText": "ライセンスを更新", "xpack.main.welcomeBanner.licenseIsExpiredTitle": "ご使用の {licenseType} ライセンスは期限切れです", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 38c5abe6eb5989..e6ab8acfeeca9a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4646,6 +4646,14 @@ "xpack.dashboardMode.dashboardViewerTitle": "仪表板查看器", "xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDescription": "属于“仅查看仪表板”模式的角色", "xpack.dashboardMode.uiSettings.dashboardsOnlyRolesTitle": "仅限仪表板的角色", + "xpack.features.advancedSettingsFeatureName": "高级设置", + "xpack.features.dashboardFeatureName": "仪表板", + "xpack.features.devToolsFeatureName": "开发工具", + "xpack.features.devToolsPrivilegesTooltip": "还应向用户授予适当的 Elasticsearch 集群和索引权限", + "xpack.features.discoverFeatureName": "Discover", + "xpack.features.indexPatternFeatureName": "索引模式管理", + "xpack.features.savedObjectsManagementFeatureName": "已保存对象管理", + "xpack.features.visualizeFeatureName": "可视化", "xpack.graph.badge.readOnly.text": "只读", "xpack.graph.badge.readOnly.tooltip": "无法保存 Graph 工作空间", "xpack.graph.clearWorkspace.confirmButtonLabel": "清除工作空间", @@ -5516,14 +5524,6 @@ "xpack.logstash.upgradeFailureActions.goBackButtonLabel": "返回", "xpack.logstash.upstreamPipelineArgumentMustContainAnIdPropertyErrorMessage": "upstreamPipeline 参数必须包含 id 属性", "xpack.logstash.workersTooltip": "并行执行管道的筛选和输出阶段的工作线程数目。如果您发现事件出现积压或 CPU 未饱和,请考虑增大此数值,以更好地利用机器处理能力。\n\n默认值:主机的 CPU 核心数", - "xpack.main.featureRegistry.advancedSettingsFeatureName": "高级设置", - "xpack.main.featureRegistry.dashboardFeatureName": "仪表板", - "xpack.main.featureRegistry.devToolsFeatureName": "开发工具", - "xpack.main.featureRegistry.devToolsPrivilegesTooltip": "还应向用户授予适当的 Elasticsearch 集群和索引权限", - "xpack.main.featureRegistry.discoverFeatureName": "Discover", - "xpack.main.featureRegistry.indexPatternFeatureName": "索引模式管理", - "xpack.main.featureRegistry.savedObjectsManagementFeatureName": "已保存对象管理", - "xpack.main.featureRegistry.visualizeFeatureName": "可视化", "xpack.main.welcomeBanner.licenseIsExpiredDescription": "联系您的管理员或直接{updateYourLicenseLinkText}。", "xpack.main.welcomeBanner.licenseIsExpiredDescription.updateYourLicenseLinkText": "更新您的许可", "xpack.main.welcomeBanner.licenseIsExpiredTitle": "您的{licenseType}许可已过期", diff --git a/x-pack/test/api_integration/apis/xpack_main/features/features.ts b/x-pack/test/api_integration/apis/features/features/features.ts similarity index 76% rename from x-pack/test/api_integration/apis/xpack_main/features/features.ts rename to x-pack/test/api_integration/apis/features/features/features.ts index 4a650886815969..fb17c76cb6bc8d 100644 --- a/x-pack/test/api_integration/apis/xpack_main/features/features.ts +++ b/x-pack/test/api_integration/apis/features/features/features.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import { SecurityService } from '../../../../common/services'; -import { Feature } from '../../../../../legacy/plugins/xpack_main/types'; +import { Feature } from '../../../../../plugins/features/server'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getService }: FtrProviderContext) { @@ -15,18 +15,6 @@ export default function({ getService }: FtrProviderContext) { const supertestWithoutAuth = getService('supertestWithoutAuth'); const security: SecurityService = getService('security'); - const expect404 = (result: any) => { - expect(result.error).to.be(undefined); - expect(result.response).not.to.be(undefined); - expect(result.response).to.have.property('statusCode', 404); - }; - - const expect200 = (result: any) => { - expect(result.error).to.be(undefined); - expect(result.response).not.to.be(undefined); - expect(result.response).to.have.property('statusCode', 200); - }; - describe('/api/features', () => { describe('with the "global all" privilege', () => { it('should return a 200', async () => { @@ -50,14 +38,11 @@ export default function({ getService }: FtrProviderContext) { full_name: 'a kibana user', }); - const result = await supertestWithoutAuth - .get(`/api/features/v1`) + await supertestWithoutAuth + .get('/api/features') .auth(username, password) .set('kbn-xsrf', 'foo') - .then((response: any) => ({ error: undefined, response })) - .catch((error: any) => ({ error, response: undefined })); - - expect200(result); + .expect(200); } finally { await security.role.delete(roleName); await security.user.delete(username); @@ -88,14 +73,11 @@ export default function({ getService }: FtrProviderContext) { full_name: 'a kibana user', }); - const result = await supertestWithoutAuth - .get(`/api/features/v1`) + await supertestWithoutAuth + .get('/api/features') .auth(username, password) .set('kbn-xsrf', 'foo') - .then((response: any) => ({ error: undefined, response })) - .catch((error: any) => ({ error, response: undefined })); - - expect404(result); + .expect(404); } finally { await security.role.delete(roleName); await security.user.delete(username); @@ -106,7 +88,7 @@ export default function({ getService }: FtrProviderContext) { describe('with trial license', () => { it('should return a full feature set', async () => { const { body } = await supertest - .get('/api/features/v1') + .get('/api/features') .set('kbn-xsrf', 'xxx') .expect(200); diff --git a/x-pack/test/api_integration/apis/xpack_main/features/index.ts b/x-pack/test/api_integration/apis/features/features/index.ts similarity index 100% rename from x-pack/test/api_integration/apis/xpack_main/features/index.ts rename to x-pack/test/api_integration/apis/features/features/index.ts diff --git a/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/index.ts b/x-pack/test/api_integration/apis/features/index.ts similarity index 52% rename from x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/index.ts rename to x-pack/test/api_integration/apis/features/index.ts index 8cbff5df3b2b4d..3113c1235436e9 100644 --- a/x-pack/legacy/plugins/xpack_main/server/lib/feature_registry/index.ts +++ b/x-pack/test/api_integration/apis/features/index.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -export { - Feature, - FeatureKibanaPrivileges, - FeatureRegistry, - FeatureWithAllOrReadPrivileges, - uiCapabilitiesRegex, -} from './feature_registry'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('features', () => { + loadTestFile(require.resolve('./features')); + }); +} diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index 99694c250ef3f9..09186f4a605021 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -13,6 +13,7 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./spaces')); loadTestFile(require.resolve('./monitoring')); loadTestFile(require.resolve('./xpack_main')); + loadTestFile(require.resolve('./features')); loadTestFile(require.resolve('./telemetry')); loadTestFile(require.resolve('./logstash')); loadTestFile(require.resolve('./kibana')); diff --git a/x-pack/test/api_integration/apis/xpack_main/index.js b/x-pack/test/api_integration/apis/xpack_main/index.js index cef6a49a10ff63..c011ef870df4ae 100644 --- a/x-pack/test/api_integration/apis/xpack_main/index.js +++ b/x-pack/test/api_integration/apis/xpack_main/index.js @@ -6,7 +6,6 @@ export default function ({ loadTestFile }) { describe('xpack_main', () => { - loadTestFile(require.resolve('./features')); loadTestFile(require.resolve('./settings')); }); } diff --git a/x-pack/test/ui_capabilities/common/services/features.ts b/x-pack/test/ui_capabilities/common/services/features.ts index 774cb59292d3b6..9f644fd6d0f6e6 100644 --- a/x-pack/test/ui_capabilities/common/services/features.ts +++ b/x-pack/test/ui_capabilities/common/services/features.ts @@ -23,8 +23,8 @@ export class FeaturesService { } public async get(): Promise { - this.log.debug(`requesting /api/features/v1 to get the features`); - const response = await this.axios.get('/api/features/v1'); + this.log.debug('requesting /api/features to get the features'); + const response = await this.axios.get('/api/features'); if (response.status !== 200) { throw new Error(