From 8ae173a7157fdd5d6b0f04a6c3dd138f87c90d78 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 1 Feb 2019 12:28:59 -0800 Subject: [PATCH 01/62] Removing feature privileges from ml/monitoring/apm --- x-pack/plugins/apm/index.js | 16 +--------------- x-pack/plugins/ml/index.js | 15 +-------------- x-pack/plugins/monitoring/init.js | 15 +-------------- .../public/views/management/edit_role/index.js | 4 +++- 4 files changed, 6 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/apm/index.js b/x-pack/plugins/apm/index.js index d6bd8bcccd27e..5d0f897bfacf1 100644 --- a/x-pack/plugins/apm/index.js +++ b/x-pack/plugins/apm/index.js @@ -78,21 +78,7 @@ export function apm(kibana) { navLinkId: 'apm', app: ['apm', 'kibana'], catalogue: ['apm'], - privileges: { - all: { - grantWithBaseRead: true, - catalogue: ['apm'], - savedObject: { - all: [], - read: ['config'] - }, - ui: [] - } - }, - privilegesTooltip: i18n.translate('xpack.apm.privileges.tooltip', { - defaultMessage: - 'A role with access to the apm-* indicies should be assigned to users to grant access' - }) + privileges: {} }); initTransactionsApi(server); diff --git a/x-pack/plugins/ml/index.js b/x-pack/plugins/ml/index.js index 3b07e1b4dd950..e18d039cbcc02 100644 --- a/x-pack/plugins/ml/index.js +++ b/x-pack/plugins/ml/index.js @@ -80,20 +80,7 @@ export const ml = (kibana) => { navLinkId: 'ml', app: ['ml', 'kibana'], catalogue: ['ml'], - privileges: { - all: { - catalogue: ['ml'], - grantWithBaseRead: true, - savedObject: { - all: [], - read: ['config'] - }, - ui: [], - }, - }, - privilegesTooltip: i18n.translate('xpack.ml.privileges.tooltip', { - defaultMessage: 'To grant users access, you should also assign either the machine_learning_user or machine_learning_admin role.' - }) + privileges: {}, }); // Add server routes and initialize the plugin here diff --git a/x-pack/plugins/monitoring/init.js b/x-pack/plugins/monitoring/init.js index c2df169a46a2f..df08e9af409dd 100644 --- a/x-pack/plugins/monitoring/init.js +++ b/x-pack/plugins/monitoring/init.js @@ -64,20 +64,7 @@ export const init = (monitoringPlugin, server) => { navLinkId: 'monitoring', app: ['monitoring', 'kibana'], catalogue: ['monitoring'], - privileges: { - all: { - catalogue: ['monitoring'], - grantWithBaseRead: true, - savedObject: { - all: [], - read: ['config'], - }, - ui: [], - }, - }, - privilegesTooltip: i18n.translate('xpack.monitoring.privileges.tooltip', { - defaultMessage: 'To grant users access, you should also assign the monitoring_user role.' - }) + privileges: {}, }); const bulkUploader = initBulkUploader(kbnServer, server); diff --git a/x-pack/plugins/security/public/views/management/edit_role/index.js b/x-pack/plugins/security/public/views/management/edit_role/index.js index e23debab08ed9..a967720a83e2e 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/index.js +++ b/x-pack/plugins/security/public/views/management/edit_role/index.js @@ -135,6 +135,8 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, { $scope.$$postDigest(async () => { const domNode = document.getElementById('editRoleReactRoot'); + // we filter out the features here which don't have any privileges to simplify the login within + const featuresWithPrivileges = features.filter(feature => Object.keys(feature.privileges).length > 0); render( , domNode); From 49b39ce929df58d09249bdc69df6afbdf77e935c Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 1 Feb 2019 15:11:02 -0800 Subject: [PATCH 02/62] Adding monitoring/ml/apm as hard-coded global privileges --- .../lib/authorization/privilege_serializer.ts | 4 ++-- .../server/lib/authorization/privileges.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts index f27276902c94e..1ddba2747d9cd 100644 --- a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts +++ b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts @@ -7,7 +7,7 @@ const featurePrefix = 'feature_'; const spacePrefix = 'space_'; const basePrivilegeNames = ['all', 'read']; -const globalBasePrivileges = [...basePrivilegeNames]; +const globalBasePrivileges = [...basePrivilegeNames, 'apm', 'ml', 'monitoring']; const spaceBasePrivileges = basePrivilegeNames.map( privilegeName => `${spacePrefix}${privilegeName}` ); @@ -30,7 +30,7 @@ export class PrivilegeSerializer { } public static serializeGlobalBasePrivilege(privilegeName: string) { - if (!basePrivilegeNames.includes(privilegeName)) { + if (!globalBasePrivileges.includes(privilegeName)) { throw new Error('Unrecognized global base privilege'); } diff --git a/x-pack/plugins/security/server/lib/authorization/privileges.ts b/x-pack/plugins/security/server/lib/authorization/privileges.ts index b3a0b55280b7f..6494e1e4a7699 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges.ts @@ -56,6 +56,24 @@ export function privilegesFactory( ...featuresPrivilegesBuilder.getUICatalogueReadActions(features), actions.ui.allNavLinks, ], + apm: [ + actions.version, + actions.app.get('apm'), + ...actions.savedObject.readOperations('config'), + actions.ui.get('navLinks', 'apm'), + ], + ml: [ + actions.version, + actions.app.get('ml'), + ...actions.savedObject.readOperations('config'), + actions.ui.get('navLinks', 'ml'), + ], + monitoring: [ + actions.version, + actions.app.get('monitoring'), + ...actions.savedObject.readOperations('config'), + actions.ui.get('navLinks', 'monitoring'), + ], }, space: { all: [ From b36b1b23495c19d26bffc58dc925f6086213a042 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 1 Feb 2019 17:21:33 -0800 Subject: [PATCH 03/62] A poorly named abstraction enters the room --- .../features_privileges_builder.ts | 199 ++++++++++++------ 1 file changed, 131 insertions(+), 68 deletions(-) diff --git a/x-pack/plugins/security/server/lib/authorization/features_privileges_builder.ts b/x-pack/plugins/security/server/lib/authorization/features_privileges_builder.ts index 291474ad2e732..2cfadee3496ee 100644 --- a/x-pack/plugins/security/server/lib/authorization/features_privileges_builder.ts +++ b/x-pack/plugins/security/server/lib/authorization/features_privileges_builder.ts @@ -6,14 +6,129 @@ import { flatten, mapValues, uniq } from 'lodash'; import { FeatureKibanaPrivileges } from 'x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry'; import { Feature } from '../../../../xpack_main/types'; -import { RawKibanaFeaturePrivileges } from '../../../common/model'; import { Actions } from './actions'; +import { RawKibanaFeaturePrivileges } from 'x-pack/plugins/security/common/model'; + +interface FeatureDerivedPrivileges +{ + getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] +} + +class FeatureDerivedAPIPrivileges implements FeatureDerivedPrivileges { + constructor(private readonly actions: Actions) { } + + getActions(privilegeDefinition: FeatureKibanaPrivileges) : string[] { + if (privilegeDefinition.api) { + return privilegeDefinition.api.map(operation => this.actions.api.get(operation)); + } + + return []; + } +} + +class FeatureDerivedAppPrivileges implements FeatureDerivedPrivileges { + constructor(private readonly actions: Actions) { } + + getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) : string[] { + const appIds = privilegeDefinition.app || feature.app; + + if (!appIds) { + return []; + } + + return appIds.map(appId => this.actions.app.get(appId)); + } +} + +class FeatureDerivedSavedObjectPrivileges implements FeatureDerivedPrivileges { + constructor(private readonly actions: Actions) { } + + getActions(privilegeDefinition: FeatureKibanaPrivileges) : string[] { + return [ + ...flatten( + privilegeDefinition.savedObject.all.map(types => + this.actions.savedObject.allOperations(types) + ) + ), + ...flatten( + privilegeDefinition.savedObject.read.map(types => + this.actions.savedObject.readOperations(types) + ) + ), + ]; + } +} + +class FeatureDerivedUIPrivileges implements FeatureDerivedPrivileges { + constructor(private readonly actions: Actions) { } + + getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) : string[] { + return privilegeDefinition.ui.map(ui => this.actions.ui.get(feature.id, ui)); + } +} + +class FeatureDerivedNavlinkPrivileges implements FeatureDerivedPrivileges { + constructor(private readonly actions: Actions) {} + + getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) { + return (feature.navLinkId ? [this.actions.ui.get('navLinks', feature.navLinkId)] : []); + } +} + +class FeatureDerivedCataloguePrivileges implements FeatureDerivedPrivileges { + constructor(private readonly actions: Actions) {} + + getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) { + const catalogueEntries = privilegeDefinition.catalogue || feature.catalogue; + + if (!catalogueEntries) { + return []; + } + + return catalogueEntries.map(catalogueEntryId => + this.actions.ui.get('catalogue', catalogueEntryId) + ); + } +} + +class FeatureDerivedManagementPrivileges implements FeatureDerivedPrivileges { + constructor(private readonly actions: Actions) {} + + getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) { + const managementSections = privilegeDefinition.management || feature.management; + + if (!managementSections) { + return []; + } + + return Object.entries(managementSections).reduce( + (acc, [sectionId, items]) => { + return [...acc, ...items.map(item => this.actions.ui.get('management', sectionId, item))]; + }, + [] as string[] + ); + } +} export class FeaturesPrivilegesBuilder { private actions: Actions; + private api: FeatureDerivedPrivileges; + private app: FeatureDerivedPrivileges; + private savedObject: FeatureDerivedPrivileges; + private ui: FeatureDerivedPrivileges; + private navLink: FeatureDerivedPrivileges; + private catalogue: FeatureDerivedPrivileges; + private management: FeatureDerivedPrivileges; constructor(actions: Actions) { this.actions = actions; + this.api = new FeatureDerivedAPIPrivileges(this.actions); + this.app = new FeatureDerivedAppPrivileges(this.actions); + this.savedObject = new FeatureDerivedSavedObjectPrivileges(this.actions); + this.ui = new FeatureDerivedUIPrivileges(this.actions); + this.navLink = new FeatureDerivedNavlinkPrivileges(this.actions); + this.catalogue = new FeatureDerivedCataloguePrivileges(this.actions); + this.management = new FeatureDerivedManagementPrivileges(this.actions); } public buildFeaturesPrivileges(features: Feature[]): RawKibanaFeaturePrivileges { @@ -83,7 +198,7 @@ export class FeaturesPrivilegesBuilder { const catalogueReadActions = Object.entries(privileges).reduce( (acc, [privilegeId, privilege]) => { if (this.includeInBaseRead(privilegeId, privilege)) { - return [...acc, ...this.buildCatalogueFeaturePrivileges(privilege, feature)]; + return [...acc, ...this.catalogue.getActions(privilege, feature)]; } return acc; }, @@ -105,7 +220,7 @@ export class FeaturesPrivilegesBuilder { const managementReadActions = Object.entries(privileges).reduce( (acc, [privilegeId, privilege]) => { if (this.includeInBaseRead(privilegeId, privilege)) { - return [...acc, ...this.buildManagementFeaturePrivileges(privilege, feature)]; + return [...acc, ...this.management.getActions(privilege, feature)]; } return acc; }, @@ -124,74 +239,22 @@ export class FeaturesPrivilegesBuilder { } private buildFeaturePrivileges(feature: Feature): Record { + const featureDerivedPrivilegesCollection = [ + this.api, + this.app, + this.savedObject, + this.ui, + this.navLink, + this.catalogue, + this.management, + ]; + return mapValues(feature.privileges, privilegeDefinition => [ this.actions.login, this.actions.version, - ...(privilegeDefinition.api - ? privilegeDefinition.api.map(api => this.actions.api.get(api)) - : []), - ...this.buildAppFeaturePrivileges(privilegeDefinition, feature), - ...flatten( - privilegeDefinition.savedObject.all.map(types => - this.actions.savedObject.allOperations(types) - ) - ), - ...flatten( - privilegeDefinition.savedObject.read.map(types => - this.actions.savedObject.readOperations(types) - ) - ), - ...privilegeDefinition.ui.map(ui => this.actions.ui.get(feature.id, ui)), - ...(feature.navLinkId ? [this.actions.ui.get('navLinks', feature.navLinkId)] : []), - // Entries on the privilege definition take priority over entries on the feature. - ...this.buildCatalogueFeaturePrivileges(privilegeDefinition, feature), - ...this.buildManagementFeaturePrivileges(privilegeDefinition, feature), + ...flatten(featureDerivedPrivilegesCollection.map( + featureDerivedPrivileges => featureDerivedPrivileges.getActions(privilegeDefinition, feature)) + ) ]); } - - private buildAppFeaturePrivileges( - privilegeDefinition: FeatureKibanaPrivileges, - feature: Feature - ): string[] { - const appEntries = privilegeDefinition.app || feature.app; - - if (!appEntries) { - return []; - } - - return appEntries.map(appId => this.actions.app.get(appId)); - } - - private buildCatalogueFeaturePrivileges( - privilegeDefinition: FeatureKibanaPrivileges, - feature: Feature - ): string[] { - const catalogueEntries = privilegeDefinition.catalogue || feature.catalogue; - - if (!catalogueEntries) { - return []; - } - - return catalogueEntries.map(catalogueEntryId => - this.actions.ui.get('catalogue', catalogueEntryId) - ); - } - - private buildManagementFeaturePrivileges( - privilegeDefinition: FeatureKibanaPrivileges, - feature: Feature - ): string[] { - const managementSections = privilegeDefinition.management || feature.management; - - if (!managementSections) { - return []; - } - - return Object.entries(managementSections).reduce( - (acc, [sectionId, items]) => { - return [...acc, ...items.map(item => this.actions.ui.get('management', sectionId, item))]; - }, - [] as string[] - ); - } } From 34aee5ac63024fcc78ccda339910a9acf25e6751 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 5 Feb 2019 13:55:15 -0800 Subject: [PATCH 04/62] No more wildcards, starting to move some stuff around --- .../features_privileges_builders.test.ts | 1027 ----------------- .../lib/authorization/privileges.test.ts | 303 ----- .../server/lib/authorization/privileges.ts | 84 -- .../features_privileges_builder.ts | 186 ++- .../lib/authorization/privileges/index.ts | 7 + .../privileges/privileges.test.ts | 631 ++++++++++ .../authorization/privileges/privileges.ts | 56 + 7 files changed, 761 insertions(+), 1533 deletions(-) delete mode 100644 x-pack/plugins/security/server/lib/authorization/features_privileges_builders.test.ts delete mode 100644 x-pack/plugins/security/server/lib/authorization/privileges.test.ts delete mode 100644 x-pack/plugins/security/server/lib/authorization/privileges.ts rename x-pack/plugins/security/server/lib/authorization/{ => privileges}/features_privileges_builder.ts (53%) create mode 100644 x-pack/plugins/security/server/lib/authorization/privileges/index.ts create mode 100644 x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts create mode 100644 x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts diff --git a/x-pack/plugins/security/server/lib/authorization/features_privileges_builders.test.ts b/x-pack/plugins/security/server/lib/authorization/features_privileges_builders.test.ts deleted file mode 100644 index 6fdfa888a21dd..0000000000000 --- a/x-pack/plugins/security/server/lib/authorization/features_privileges_builders.test.ts +++ /dev/null @@ -1,1027 +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 '../../../../xpack_main/types'; -import { Actions } from './actions'; -import { FeaturesPrivilegesBuilder } from './features_privileges_builder'; - -const versionNumber = '1.0.0-zeta1'; - -describe('#buildFeaturesPrivileges', () => { - test('specifies key for each feature', () => { - const builder = new FeaturesPrivilegesBuilder(new Actions(versionNumber)); - const features = [ - { - id: 'foo', - name: '', - app: [], - privileges: {}, - }, - { - id: 'bar', - name: '', - app: [], - privileges: {}, - }, - ]; - const result = builder.buildFeaturesPrivileges(features); - expect(result).toEqual({ - foo: expect.anything(), - bar: expect.anything(), - }); - }); - - test('always includes login and version action', () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - bar: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.buildFeaturesPrivileges(features); - expect(result).toEqual({ - foo: { - bar: [actions.login, actions.version], - }, - }); - }); - - test('includes api actions when specified', () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - bar: { - api: ['foo/operation', 'bar/operation'], - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.buildFeaturesPrivileges(features); - expect(result).toEqual({ - foo: { - bar: [ - actions.login, - actions.version, - actions.api.get('foo/operation'), - actions.api.get('bar/operation'), - ], - }, - }); - }); - - test('includes app actions when specified at the feature level', () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features = [ - { - id: 'foo', - name: '', - app: ['foo-app', 'bar-app'], - privileges: { - bar: { - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.buildFeaturesPrivileges(features); - expect(result).toEqual({ - foo: { - bar: [ - actions.login, - actions.version, - actions.app.get('foo-app'), - actions.app.get('bar-app'), - ], - }, - }); - }); - - test('includes only the app actions specified at the privilege level, when specified', () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features = [ - { - id: 'foo', - name: '', - app: ['foo-app', 'bar-app'], - privileges: { - bar: { - app: ['foo-app'], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.buildFeaturesPrivileges(features); - expect(result).toEqual({ - foo: { - bar: [actions.login, actions.version, actions.app.get('foo-app')], - }, - }); - }); - - test('excludes apps when an empty array is provided at the privilege level', () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features = [ - { - id: 'foo', - name: '', - app: ['foo-app', 'bar-app'], - privileges: { - bar: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.buildFeaturesPrivileges(features); - expect(result).toEqual({ - foo: { - bar: [actions.login, actions.version], - }, - }); - }); - - test('includes catalogue actions when specified', () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - bar: { - catalogue: ['fooEntry', 'barEntry'], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.buildFeaturesPrivileges(features); - expect(result).toEqual({ - foo: { - bar: [ - actions.login, - actions.version, - actions.ui.get('catalogue', 'fooEntry'), - actions.ui.get('catalogue', 'barEntry'), - ], - }, - }); - }); - - test('includes savedObject all actions when specified', () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - bar: { - app: [], - savedObject: { - all: ['foo-type', 'bar-type'], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.buildFeaturesPrivileges(features); - expect(result).toEqual({ - foo: { - bar: [ - actions.login, - actions.version, - ...actions.savedObject.allOperations(['foo-type', 'bar-type']), - ], - }, - }); - }); - - test('includes savedObject read actions when specified', () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - bar: { - app: [], - savedObject: { - all: [], - read: ['foo-type', 'bar-type'], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.buildFeaturesPrivileges(features); - expect(result).toEqual({ - foo: { - bar: [ - actions.login, - actions.version, - ...actions.savedObject.readOperations(['foo-type', 'bar-type']), - ], - }, - }); - }); - - test('includes ui capabilities actions when specified', () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - bar: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: ['foo-ui-capability', 'bar-ui-capability'], - }, - }, - }, - ]; - const result = builder.buildFeaturesPrivileges(features); - expect(result).toEqual({ - foo: { - bar: [ - actions.login, - actions.version, - actions.ui.get('foo', 'foo-ui-capability'), - actions.ui.get('foo', 'bar-ui-capability'), - ], - }, - }); - }); - - test('includes navlink ui capability action when specified', () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features = [ - { - id: 'foo', - name: '', - navLinkId: 'foo-navlink', - app: [], - privileges: { - bar: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.buildFeaturesPrivileges(features); - expect(result).toEqual({ - foo: { - bar: [actions.login, actions.version, actions.ui.get('navLinks', 'foo-navlink')], - }, - }); - }); -}); - -describe('#getApiReadActions', () => { - test(`includes api actions from the read privileges`, () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features: Feature[] = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - // wrong privilege name - bar: { - app: [], - api: ['foo/api'], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - // no api read privileges - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - { - id: 'bar', - name: '', - app: [], - privileges: { - // this one should show up in the results - read: { - app: [], - api: ['foo/api'], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.getApiReadActions(features); - expect(result).toEqual([actions.api.get('foo/api')]); - }); - - test(`includes api actions from other privileges when "grantWithBaseRead" is true`, () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features: Feature[] = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - bar: { - app: [], - // This should show up in the results - grantWithBaseRead: true, - api: ['bar/api'], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - // no api read privileges - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - { - id: 'bar', - name: '', - app: [], - privileges: { - // this one should show up in the results - read: { - app: [], - api: ['foo/api'], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.getApiReadActions(features); - expect(result).toEqual([actions.api.get('bar/api'), actions.api.get('foo/api')]); - }); -}); - -describe('#getUIFeaturesReadActions', () => { - test(`includes ui actions from the read privileges`, () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features: Feature[] = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - // wrong privilege name - bar: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: ['foo'], - }, - // no ui read privileges - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - { - id: 'bar', - name: '', - app: [], - privileges: { - // this ui capability should show up in the results - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: ['bar-ui-capability'], - }, - }, - }, - ]; - const result = builder.getUIFeaturesReadActions(features); - expect(result).toEqual([actions.ui.get('bar', 'bar-ui-capability')]); - }); - - test(`includes ui actions from other privileges when "grantWithBaseRead" is true`, () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features: Feature[] = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - // this should show up in the results - bar: { - grantWithBaseRead: true, - app: [], - savedObject: { - all: [], - read: [], - }, - ui: ['foo-ui-capability'], - }, - // no ui read privileges - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - { - id: 'bar', - name: '', - app: [], - privileges: { - // this ui capability should show up in the results - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: ['bar-ui-capability'], - }, - }, - }, - ]; - const result = builder.getUIFeaturesReadActions(features); - expect(result).toEqual([ - actions.ui.get('foo', 'foo-ui-capability'), - actions.ui.get('bar', 'bar-ui-capability'), - ]); - }); -}); - -describe('#getUIManagementReadActions', () => { - test(`includes management actions from the read privileges`, () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features: Feature[] = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - // wrong privilege name - bar: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - // no management read privileges - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - { - id: 'bar', - name: '', - app: [], - management: { - kibana: ['fooManagementLink', 'otherManagementLink'], - es: ['barManagementLink', 'bazManagementLink'], - }, - privileges: { - // this management capability should show up in the results - read: { - app: [], - management: { - kibana: ['fooManagementLink'], - es: ['barManagementLink', 'bazManagementLink'], - }, - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.getUIManagementReadActions(features); - expect(result).toEqual([ - actions.ui.get('management', 'kibana', 'fooManagementLink'), - actions.ui.get('management', 'es', 'barManagementLink'), - actions.ui.get('management', 'es', 'bazManagementLink'), - ]); - }); - - test(`includes management actions from the feature when not specified on the privilege`, () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features: Feature[] = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - // wrong privilege name - bar: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - // no management read privileges - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - { - id: 'bar', - name: '', - app: [], - management: { - kibana: ['fooManagementLink', 'otherManagementLink'], - es: ['barManagementLink', 'bazManagementLink'], - }, - privileges: { - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.getUIManagementReadActions(features); - expect(result).toEqual([ - actions.ui.get('management', 'kibana', 'fooManagementLink'), - actions.ui.get('management', 'kibana', 'otherManagementLink'), - actions.ui.get('management', 'es', 'barManagementLink'), - actions.ui.get('management', 'es', 'bazManagementLink'), - ]); - }); - - test(`excludes management actions when an empty object is specified on the privilege`, () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features: Feature[] = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - // wrong privilege name - bar: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - // no management read privileges - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - { - id: 'bar', - name: '', - app: [], - management: { - kibana: ['fooManagementLink', 'otherManagementLink'], - es: ['barManagementLink', 'bazManagementLink'], - }, - privileges: { - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - management: {}, - }, - }, - }, - ]; - const result = builder.getUIManagementReadActions(features); - expect(result).toEqual([]); - }); - - test(`includes management actions from other privileges when "grantWithBaseRead" is true`, () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features: Feature[] = [ - { - id: 'foo', - name: '', - app: [], - management: { - foo: ['bar'], - }, - privileges: { - // this management capability should show up in the results - bar: { - grantWithBaseRead: true, - app: [], - management: { - foo: ['bar'], - }, - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - // no management read privileges - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - { - id: 'bar', - name: '', - app: [], - management: { - kibana: ['fooManagementLink', 'otherManagementLink'], - es: ['barManagementLink', 'bazManagementLink'], - }, - privileges: { - // this management capability should show up in the results - read: { - app: [], - management: { - kibana: ['fooManagementLink'], - es: ['barManagementLink', 'bazManagementLink'], - }, - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.getUIManagementReadActions(features); - expect(result).toEqual([ - actions.ui.get('management', 'foo', 'bar'), - actions.ui.get('management', 'kibana', 'fooManagementLink'), - actions.ui.get('management', 'es', 'barManagementLink'), - actions.ui.get('management', 'es', 'bazManagementLink'), - ]); - }); -}); - -describe('#getUICatalogueReadActions', () => { - test(`includes catalogue actions from the read privileges`, () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features: Feature[] = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - // wrong privilege name - bar: { - catalogue: [], - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - // no catalogue read privileges - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - { - id: 'bar', - name: '', - app: [], - catalogue: ['barCatalogueLink', 'bazCatalogueLink', 'anotherCatalogueLink'], - privileges: { - // this catalogue capability should show up in the results - read: { - app: [], - catalogue: ['barCatalogueLink', 'bazCatalogueLink'], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.getUICatalogueReadActions(features); - expect(result).toEqual([ - actions.ui.get('catalogue', 'barCatalogueLink'), - actions.ui.get('catalogue', 'bazCatalogueLink'), - ]); - }); - - test(`includes catalogue actions from the feature when not specified on the privilege`, () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features: Feature[] = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - // wrong privilege name - bar: { - catalogue: [], - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - // no catalogue read privileges - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - { - id: 'bar', - name: '', - app: [], - catalogue: ['barCatalogueLink', 'bazCatalogueLink', 'anotherCatalogueLink'], - privileges: { - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.getUICatalogueReadActions(features); - expect(result).toEqual([ - actions.ui.get('catalogue', 'barCatalogueLink'), - actions.ui.get('catalogue', 'bazCatalogueLink'), - actions.ui.get('catalogue', 'anotherCatalogueLink'), - ]); - }); - - test(`excludes catalogue actions from the feature when an empty array is specified on the privilege`, () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features: Feature[] = [ - { - id: 'foo', - name: '', - app: [], - privileges: { - // wrong privilege name - bar: { - catalogue: [], - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - // no catalogue read privileges - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - { - id: 'bar', - name: '', - app: [], - catalogue: [], - privileges: { - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.getUICatalogueReadActions(features); - expect(result).toEqual([]); - }); - - test(`includes catalogue actions from other privileges when "grantWithBaseRead" is true`, () => { - const actions = new Actions(versionNumber); - const builder = new FeaturesPrivilegesBuilder(actions); - const features: Feature[] = [ - { - id: 'foo', - name: '', - app: [], - catalogue: ['fooCatalogueLink'], - privileges: { - // this catalogue capability should show up in the results - bar: { - catalogue: ['fooCatalogueLink'], - grantWithBaseRead: true, - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - // no catalogue read privileges - read: { - app: [], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - { - id: 'bar', - name: '', - app: [], - catalogue: ['barCatalogueLink', 'bazCatalogueLink', 'anotherCatalogueLink'], - privileges: { - // this catalogue capability should show up in the results - read: { - app: [], - catalogue: ['barCatalogueLink', 'bazCatalogueLink'], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, - }, - ]; - const result = builder.getUICatalogueReadActions(features); - expect(result).toEqual([ - actions.ui.get('catalogue', 'fooCatalogueLink'), - actions.ui.get('catalogue', 'barCatalogueLink'), - actions.ui.get('catalogue', 'bazCatalogueLink'), - ]); - }); -}); diff --git a/x-pack/plugins/security/server/lib/authorization/privileges.test.ts b/x-pack/plugins/security/server/lib/authorization/privileges.test.ts deleted file mode 100644 index 58bd77dd6bd94..0000000000000 --- a/x-pack/plugins/security/server/lib/authorization/privileges.test.ts +++ /dev/null @@ -1,303 +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. - */ - -// @ts-ignore -import { Feature } from '../../../../xpack_main/types'; -import { IGNORED_TYPES } from '../../../common/constants'; -import { Actions } from './actions'; -import { privilegesFactory } from './privileges'; - -test(`builds privileges correctly`, () => { - const actions = new Actions('1.0.0-zeta1'); - - const savedObjectTypes = ['foo-saved-object-type', 'bar-saved-object-type', ...IGNORED_TYPES]; - - const features: Feature[] = [ - { - id: 'foo-feature', - name: 'Foo Feature', - icon: 'arrowDown', - navLinkId: 'kibana:foo-feature', - app: ['foo-app'], - catalogue: ['fooAppEntry1', 'fooAppEntry2', 'fooReadEntry'], - management: { - foo: ['fooManagementLink', 'anotherFooManagementLink'], - }, - privileges: { - all: { - catalogue: ['fooAppEntry1', 'fooAppEntry2'], - management: { - foo: ['fooManagementLink', 'anotherFooManagementLink'], - }, - savedObject: { - all: ['foo-saved-object-type'], - read: ['bad-saved-object-type'], - }, - ui: ['show', 'showSaveButton', 'showCreateButton'], - }, - read: { - app: [], - api: ['foo/read/api'], - catalogue: ['fooReadEntry'], - management: { - foo: ['anotherFooManagementLink'], - }, - savedObject: { - all: [], - read: ['foo-saved-object-type', 'bar-saved-object-type'], - }, - ui: ['show'], - }, - }, - }, - { - id: 'bar-feature', - name: 'Bar Feature', - icon: 'arrowUp', - app: ['bar-app', 'another-bar-app'], - catalogue: ['barCatalogue'], - management: { - kibana: ['yeppers'], - }, - privileges: { - all: { - savedObject: { - all: ['bar-saved-object-type'], - read: ['foo-saved-object-type'], - }, - ui: ['show', 'showSaveButton', 'showCreateButton'], - }, - read: { - app: ['another-bar-app'], - management: { - kibana: ['yeppers'], - }, - api: ['bar/read/api'], - savedObject: { - all: [], - read: ['foo-saved-object-type', 'bar-saved-object-type'], - }, - ui: ['show'], - }, - }, - }, - { - id: 'baz-feature', - name: 'Baz Feature', - icon: 'arrowUp', - app: ['bar-app'], - catalogue: ['bazCatalogue'], - management: { - kibana: ['bazKibana'], - }, - privileges: { - all: { - grantWithBaseRead: true, - catalogue: ['bazCatalogue'], - management: { - kibana: ['bazKibana'], - }, - api: ['bar/read/api'], - savedObject: { - all: [], - read: ['foo-saved-object-type', 'bar-saved-object-type'], - }, - ui: ['show'], - }, - }, - }, - ]; - - const mockXPackMainPlugin = { - getFeatures: jest.fn().mockReturnValue(features), - }; - - const privileges = privilegesFactory(savedObjectTypes, actions, mockXPackMainPlugin); - - // we want to make sure we don't call `xpackMainPlugin.getFeatures` until `get` is called - // to ensure that plugins have the time to register their features before we build the privilegeMap - expect(mockXPackMainPlugin.getFeatures).not.toHaveBeenCalled(); - expect(privileges.get()).toEqual({ - features: { - 'bar-feature': { - all: [ - 'login:', - 'version:1.0.0-zeta1', - 'app:bar-app', - 'app:another-bar-app', - 'saved_object:bar-saved-object-type/bulk_get', - 'saved_object:bar-saved-object-type/get', - 'saved_object:bar-saved-object-type/find', - 'saved_object:bar-saved-object-type/create', - 'saved_object:bar-saved-object-type/bulk_create', - 'saved_object:bar-saved-object-type/update', - 'saved_object:bar-saved-object-type/delete', - 'saved_object:foo-saved-object-type/bulk_get', - 'saved_object:foo-saved-object-type/get', - 'saved_object:foo-saved-object-type/find', - 'ui:bar-feature/show', - 'ui:bar-feature/showSaveButton', - 'ui:bar-feature/showCreateButton', - 'ui:catalogue/barCatalogue', - 'ui:management/kibana/yeppers', - ], - read: [ - 'login:', - 'version:1.0.0-zeta1', - 'api:bar/read/api', - 'app:another-bar-app', - 'saved_object:foo-saved-object-type/bulk_get', - 'saved_object:foo-saved-object-type/get', - 'saved_object:foo-saved-object-type/find', - 'saved_object:bar-saved-object-type/bulk_get', - 'saved_object:bar-saved-object-type/get', - 'saved_object:bar-saved-object-type/find', - 'ui:bar-feature/show', - 'ui:catalogue/barCatalogue', - 'ui:management/kibana/yeppers', - ], - }, - 'baz-feature': { - all: [ - 'login:', - 'version:1.0.0-zeta1', - 'api:bar/read/api', - 'app:bar-app', - 'saved_object:foo-saved-object-type/bulk_get', - 'saved_object:foo-saved-object-type/get', - 'saved_object:foo-saved-object-type/find', - 'saved_object:bar-saved-object-type/bulk_get', - 'saved_object:bar-saved-object-type/get', - 'saved_object:bar-saved-object-type/find', - 'ui:baz-feature/show', - 'ui:catalogue/bazCatalogue', - 'ui:management/kibana/bazKibana', - ], - }, - 'foo-feature': { - all: [ - 'login:', - 'version:1.0.0-zeta1', - 'app:foo-app', - 'saved_object:foo-saved-object-type/bulk_get', - 'saved_object:foo-saved-object-type/get', - 'saved_object:foo-saved-object-type/find', - 'saved_object:foo-saved-object-type/create', - 'saved_object:foo-saved-object-type/bulk_create', - 'saved_object:foo-saved-object-type/update', - 'saved_object:foo-saved-object-type/delete', - 'saved_object:bad-saved-object-type/bulk_get', - 'saved_object:bad-saved-object-type/get', - 'saved_object:bad-saved-object-type/find', - 'ui:foo-feature/show', - 'ui:foo-feature/showSaveButton', - 'ui:foo-feature/showCreateButton', - 'ui:navLinks/kibana:foo-feature', - 'ui:catalogue/fooAppEntry1', - 'ui:catalogue/fooAppEntry2', - 'ui:management/foo/fooManagementLink', - 'ui:management/foo/anotherFooManagementLink', - ], - read: [ - 'login:', - 'version:1.0.0-zeta1', - 'api:foo/read/api', - 'saved_object:foo-saved-object-type/bulk_get', - 'saved_object:foo-saved-object-type/get', - 'saved_object:foo-saved-object-type/find', - 'saved_object:bar-saved-object-type/bulk_get', - 'saved_object:bar-saved-object-type/get', - 'saved_object:bar-saved-object-type/find', - 'ui:foo-feature/show', - 'ui:navLinks/kibana:foo-feature', - 'ui:catalogue/fooReadEntry', - 'ui:management/foo/anotherFooManagementLink', - ], - }, - }, - global: { - all: [ - 'login:', - 'version:1.0.0-zeta1', - 'api:*', - 'app:*', - 'saved_object:*', - 'space:manage', - 'ui:*', - ], - read: [ - 'login:', - 'version:1.0.0-zeta1', - 'api:foo/read/api', - 'api:bar/read/api', - 'app:*', - 'saved_object:foo-saved-object-type/bulk_get', - 'saved_object:foo-saved-object-type/get', - 'saved_object:foo-saved-object-type/find', - 'saved_object:bar-saved-object-type/bulk_get', - 'saved_object:bar-saved-object-type/get', - 'saved_object:bar-saved-object-type/find', - 'ui:foo-feature/show', - 'ui:bar-feature/show', - 'ui:baz-feature/show', - 'ui:management/foo/anotherFooManagementLink', - 'ui:management/kibana/yeppers', - 'ui:management/kibana/bazKibana', - 'ui:catalogue/fooReadEntry', - 'ui:catalogue/barCatalogue', - 'ui:catalogue/bazCatalogue', - 'ui:navLinks/*', - ], - }, - space: { - all: [ - 'login:', - 'version:1.0.0-zeta1', - 'api:*', - 'app:*', - 'saved_object:foo-saved-object-type/bulk_get', - 'saved_object:foo-saved-object-type/get', - 'saved_object:foo-saved-object-type/find', - 'saved_object:foo-saved-object-type/create', - 'saved_object:foo-saved-object-type/bulk_create', - 'saved_object:foo-saved-object-type/update', - 'saved_object:foo-saved-object-type/delete', - 'saved_object:bar-saved-object-type/bulk_get', - 'saved_object:bar-saved-object-type/get', - 'saved_object:bar-saved-object-type/find', - 'saved_object:bar-saved-object-type/create', - 'saved_object:bar-saved-object-type/bulk_create', - 'saved_object:bar-saved-object-type/update', - 'saved_object:bar-saved-object-type/delete', - 'ui:*', - ], - read: [ - 'login:', - 'version:1.0.0-zeta1', - 'api:foo/read/api', - 'api:bar/read/api', - 'app:*', - 'saved_object:foo-saved-object-type/bulk_get', - 'saved_object:foo-saved-object-type/get', - 'saved_object:foo-saved-object-type/find', - 'saved_object:bar-saved-object-type/bulk_get', - 'saved_object:bar-saved-object-type/get', - 'saved_object:bar-saved-object-type/find', - 'ui:foo-feature/show', - 'ui:bar-feature/show', - 'ui:baz-feature/show', - 'ui:management/foo/anotherFooManagementLink', - 'ui:management/kibana/yeppers', - 'ui:management/kibana/bazKibana', - 'ui:catalogue/fooReadEntry', - 'ui:catalogue/barCatalogue', - 'ui:catalogue/bazCatalogue', - 'ui:navLinks/*', - ], - }, - }); - expect(mockXPackMainPlugin.getFeatures).toHaveBeenCalled(); -}); diff --git a/x-pack/plugins/security/server/lib/authorization/privileges.ts b/x-pack/plugins/security/server/lib/authorization/privileges.ts deleted file mode 100644 index b3a0b55280b7f..0000000000000 --- a/x-pack/plugins/security/server/lib/authorization/privileges.ts +++ /dev/null @@ -1,84 +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 { RawKibanaPrivileges } from 'x-pack/plugins/security/common/model'; -import { Feature } from '../../../../xpack_main/types'; -import { IGNORED_TYPES } from '../../../common/constants'; -import { Actions } from './actions'; -import { FeaturesPrivilegesBuilder } from './features_privileges_builder'; - -export interface PrivilegesService { - get(): RawKibanaPrivileges; -} - -interface XPackMainPlugin { - getFeatures(): Feature[]; -} - -export function privilegesFactory( - allSavedObjectTypes: string[], - actions: Actions, - xpackMainPlugin: XPackMainPlugin -) { - return { - get() { - // TODO: I'd like to ensure an explicit Error is thrown here if all - // plugins haven't had a chance to register their features yet - const features = xpackMainPlugin.getFeatures(); - const validSavedObjectTypes = allSavedObjectTypes.filter( - type => !IGNORED_TYPES.includes(type) - ); - const featuresPrivilegesBuilder = new FeaturesPrivilegesBuilder(actions); - - return { - features: featuresPrivilegesBuilder.buildFeaturesPrivileges(features), - global: { - all: [ - actions.login, - actions.version, - actions.api.all, - actions.app.all, - actions.savedObject.all, - actions.space.manage, - actions.ui.all, - ], - read: [ - actions.login, - actions.version, - ...featuresPrivilegesBuilder.getApiReadActions(features), - actions.app.all, - ...actions.savedObject.readOperations(validSavedObjectTypes), - ...featuresPrivilegesBuilder.getUIFeaturesReadActions(features), - ...featuresPrivilegesBuilder.getUIManagementReadActions(features), - ...featuresPrivilegesBuilder.getUICatalogueReadActions(features), - actions.ui.allNavLinks, - ], - }, - space: { - all: [ - actions.login, - actions.version, - actions.api.all, - actions.app.all, - ...actions.savedObject.allOperations(validSavedObjectTypes), - actions.ui.all, - ], - read: [ - actions.login, - actions.version, - ...featuresPrivilegesBuilder.getApiReadActions(features), - actions.app.all, - ...actions.savedObject.readOperations(validSavedObjectTypes), - ...featuresPrivilegesBuilder.getUIFeaturesReadActions(features), - ...featuresPrivilegesBuilder.getUIManagementReadActions(features), - ...featuresPrivilegesBuilder.getUICatalogueReadActions(features), - actions.ui.allNavLinks, - ], - }, - }; - }, - }; -} diff --git a/x-pack/plugins/security/server/lib/authorization/features_privileges_builder.ts b/x-pack/plugins/security/server/lib/authorization/privileges/features_privileges_builder.ts similarity index 53% rename from x-pack/plugins/security/server/lib/authorization/features_privileges_builder.ts rename to x-pack/plugins/security/server/lib/authorization/privileges/features_privileges_builder.ts index 2cfadee3496ee..6a0a447729f60 100644 --- a/x-pack/plugins/security/server/lib/authorization/features_privileges_builder.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/features_privileges_builder.ts @@ -4,20 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ import { flatten, mapValues, uniq } from 'lodash'; -import { FeatureKibanaPrivileges } from 'x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry'; -import { Feature } from '../../../../xpack_main/types'; -import { Actions } from './actions'; import { RawKibanaFeaturePrivileges } from 'x-pack/plugins/security/common/model'; +import { FeatureKibanaPrivileges } from 'x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry'; +import { Feature } from '../../../../../xpack_main/types'; +import { Actions } from '../actions'; -interface FeatureDerivedPrivileges -{ - getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] +interface FeatureDerivedPrivileges { + getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[]; } class FeatureDerivedAPIPrivileges implements FeatureDerivedPrivileges { - constructor(private readonly actions: Actions) { } + constructor(private readonly actions: Actions) {} - getActions(privilegeDefinition: FeatureKibanaPrivileges) : string[] { + public getActions(privilegeDefinition: FeatureKibanaPrivileges): string[] { if (privilegeDefinition.api) { return privilegeDefinition.api.map(operation => this.actions.api.get(operation)); } @@ -27,9 +26,9 @@ class FeatureDerivedAPIPrivileges implements FeatureDerivedPrivileges { } class FeatureDerivedAppPrivileges implements FeatureDerivedPrivileges { - constructor(private readonly actions: Actions) { } + constructor(private readonly actions: Actions) {} - getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) : string[] { + public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { const appIds = privilegeDefinition.app || feature.app; if (!appIds) { @@ -41,9 +40,9 @@ class FeatureDerivedAppPrivileges implements FeatureDerivedPrivileges { } class FeatureDerivedSavedObjectPrivileges implements FeatureDerivedPrivileges { - constructor(private readonly actions: Actions) { } + constructor(private readonly actions: Actions) {} - getActions(privilegeDefinition: FeatureKibanaPrivileges) : string[] { + public getActions(privilegeDefinition: FeatureKibanaPrivileges): string[] { return [ ...flatten( privilegeDefinition.savedObject.all.map(types => @@ -60,9 +59,9 @@ class FeatureDerivedSavedObjectPrivileges implements FeatureDerivedPrivileges { } class FeatureDerivedUIPrivileges implements FeatureDerivedPrivileges { - constructor(private readonly actions: Actions) { } + constructor(private readonly actions: Actions) {} - getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) : string[] { + public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { return privilegeDefinition.ui.map(ui => this.actions.ui.get(feature.id, ui)); } } @@ -70,15 +69,15 @@ class FeatureDerivedUIPrivileges implements FeatureDerivedPrivileges { class FeatureDerivedNavlinkPrivileges implements FeatureDerivedPrivileges { constructor(private readonly actions: Actions) {} - getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) { - return (feature.navLinkId ? [this.actions.ui.get('navLinks', feature.navLinkId)] : []); + public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) { + return feature.navLinkId ? [this.actions.ui.get('navLinks', feature.navLinkId)] : []; } } class FeatureDerivedCataloguePrivileges implements FeatureDerivedPrivileges { constructor(private readonly actions: Actions) {} - getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) { + public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) { const catalogueEntries = privilegeDefinition.catalogue || feature.catalogue; if (!catalogueEntries) { @@ -94,7 +93,7 @@ class FeatureDerivedCataloguePrivileges implements FeatureDerivedPrivileges { class FeatureDerivedManagementPrivileges implements FeatureDerivedPrivileges { constructor(private readonly actions: Actions) {} - getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) { + public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) { const managementSections = privilegeDefinition.management || feature.management; if (!managementSections) { @@ -119,6 +118,7 @@ export class FeaturesPrivilegesBuilder { private navLink: FeatureDerivedPrivileges; private catalogue: FeatureDerivedPrivileges; private management: FeatureDerivedPrivileges; + private featureDerivedPrivilegesCollection: FeatureDerivedPrivileges[]; constructor(actions: Actions) { this.actions = actions; @@ -129,6 +129,16 @@ export class FeaturesPrivilegesBuilder { this.navLink = new FeatureDerivedNavlinkPrivileges(this.actions); this.catalogue = new FeatureDerivedCataloguePrivileges(this.actions); this.management = new FeatureDerivedManagementPrivileges(this.actions); + + this.featureDerivedPrivilegesCollection = [ + this.api, + this.app, + this.savedObject, + this.catalogue, + this.management, + this.navLink, + this.ui, + ]; } public buildFeaturesPrivileges(features: Feature[]): RawKibanaFeaturePrivileges { @@ -138,100 +148,46 @@ export class FeaturesPrivilegesBuilder { }, {}); } - public getApiReadActions(features: Feature[]): string[] { - const allApiReadActions = flatten( - features.map(feature => { - const { privileges } = feature; - if (!privileges) { - return []; - } - - const apiOperations = Object.entries(privileges).reduce( - (acc, [privilegeId, privilege]) => { - if (this.includeInBaseRead(privilegeId, privilege)) { - return [...acc, ...(privilege.api || [])]; - } - return acc; - }, - [] - ); - - return apiOperations.map(api => this.actions.api.get(api)); - }) - ); - - return uniq(allApiReadActions); - } - - public getUIFeaturesReadActions(features: Feature[]): string[] { - const allUIReadActions = flatten( - features.map(feature => { - const { privileges } = feature; - if (!privileges) { - return []; - } - - const readUICapabilities = Object.entries(privileges).reduce( - (acc, [privilegeId, privilege]) => { - if (this.includeInBaseRead(privilegeId, privilege)) { - return [...acc, ...(privilege.ui || [])]; - } - return acc; - }, - [] - ); - - return readUICapabilities.map(uiCapability => - this.actions.ui.get(feature.id, uiCapability) - ); - }) - ); - - return uniq(allUIReadActions); - } - - public getUICatalogueReadActions(features: Feature[]): string[] { - const allCatalogueReadActions = flatten( - features.map(feature => { - const { privileges = {} } = feature; - - const catalogueReadActions = Object.entries(privileges).reduce( - (acc, [privilegeId, privilege]) => { - if (this.includeInBaseRead(privilegeId, privilege)) { - return [...acc, ...this.catalogue.getActions(privilege, feature)]; - } - return acc; - }, - [] - ); - - return catalogueReadActions; - }) + public getAllActions(features: Feature[]): string[] { + return uniq( + flatten( + features.map(feature => + Object.values(feature.privileges).reduce((acc, privilege) => { + return [ + ...acc, + ...flatten( + this.featureDerivedPrivilegesCollection.map(featureDerivedPrivileges => + featureDerivedPrivileges.getActions(privilege, feature) + ) + ), + ]; + }, []) + ) + ) ); - - return uniq(allCatalogueReadActions); } - public getUIManagementReadActions(features: Feature[]): string[] { - const allManagementReadActions = flatten( - features.map(feature => { - const { privileges = {} } = feature; - - const managementReadActions = Object.entries(privileges).reduce( - (acc, [privilegeId, privilege]) => { - if (this.includeInBaseRead(privilegeId, privilege)) { - return [...acc, ...this.management.getActions(privilege, feature)]; + public getReadActions(features: Feature[]): string[] { + return uniq( + flatten( + features.map(feature => + Object.entries(feature.privileges).reduce((acc, [privilegeId, privilege]) => { + if (!this.includeInBaseRead(privilegeId, privilege)) { + return acc; } - return acc; - }, - [] - ); - return managementReadActions; - }) + return [ + ...acc, + ...flatten( + this.featureDerivedPrivilegesCollection.map(featureDerivedPrivileges => + featureDerivedPrivileges.getActions(privilege, feature) + ) + ), + ]; + }, []) + ) + ) ); - - return uniq(allManagementReadActions); } private includeInBaseRead(privilegeId: string, privilege: FeatureKibanaPrivileges): boolean { @@ -239,22 +195,14 @@ export class FeaturesPrivilegesBuilder { } private buildFeaturePrivileges(feature: Feature): Record { - const featureDerivedPrivilegesCollection = [ - this.api, - this.app, - this.savedObject, - this.ui, - this.navLink, - this.catalogue, - this.management, - ]; - return mapValues(feature.privileges, privilegeDefinition => [ this.actions.login, this.actions.version, - ...flatten(featureDerivedPrivilegesCollection.map( - featureDerivedPrivileges => featureDerivedPrivileges.getActions(privilegeDefinition, feature)) - ) + ...flatten( + this.featureDerivedPrivilegesCollection.map(featureDerivedPrivileges => + featureDerivedPrivileges.getActions(privilegeDefinition, feature) + ) + ), ]); } } diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/index.ts b/x-pack/plugins/security/server/lib/authorization/privileges/index.ts new file mode 100644 index 0000000000000..e1b0e073599df --- /dev/null +++ b/x-pack/plugins/security/server/lib/authorization/privileges/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { privilegesFactory } from './privileges'; diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts new file mode 100644 index 0000000000000..82636474f64b0 --- /dev/null +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts @@ -0,0 +1,631 @@ +/* + * 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. + */ + +// @ts-ignore +import { Feature } from '../../../../xpack_main/types'; +import { Actions } from '../actions'; +import { privilegesFactory } from './privileges'; + +const actions = new Actions('1.0.0-zeta1'); + +describe('features', () => { + test('actions defined at the feature cascade to the privileges', () => { + const features: Feature[] = [ + { + id: 'foo-feature', + name: 'Foo Feature', + icon: 'arrowDown', + navLinkId: 'kibana:foo', + app: ['app-1', 'app-2'], + catalogue: ['catalogue-1', 'catalogue-2'], + management: { + foo: ['management-1', 'management-2'], + }, + privileges: { + all: { + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + read: { + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + }, + }, + ]; + + const mockXPackMainPlugin = { + getFeatures: jest.fn().mockReturnValue(features), + }; + + const privileges = privilegesFactory(actions, mockXPackMainPlugin); + + const actual = privileges.get(); + expect(actual).toHaveProperty('features.foo-feature', { + all: [ + actions.login, + actions.version, + 'app:app-1', + 'app:app-2', + 'ui:catalogue/catalogue-1', + 'ui:catalogue/catalogue-2', + 'ui:management/foo/management-1', + 'ui:management/foo/management-2', + 'ui:navLinks/kibana:foo', + ], + read: [ + actions.login, + actions.version, + 'app:app-1', + 'app:app-2', + 'ui:catalogue/catalogue-1', + 'ui:catalogue/catalogue-2', + 'ui:management/foo/management-1', + 'ui:management/foo/management-2', + 'ui:navLinks/kibana:foo', + ], + }); + }); + + test('actions defined at the privilege take precedence', () => { + const features: Feature[] = [ + { + id: 'foo', + name: 'Foo Feature', + icon: 'arrowDown', + app: ['ignore-me-1', 'ignore-me-2'], + catalogue: ['ignore-me-1', 'ignore-me-2'], + management: { + foo: ['ignore-me-1', 'ignore-me-2'], + }, + privileges: { + all: { + app: ['all-app-1', 'all-app-2'], + catalogue: ['catalogue-all-1', 'catalogue-all-2'], + management: { + all: ['all-management-1', 'all-management-2'], + }, + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + read: { + app: ['read-app-1', 'read-app-2'], + catalogue: ['catalogue-read-1', 'catalogue-read-2'], + management: { + read: ['read-management-1', 'read-management-2'], + }, + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + }, + }, + ]; + + const mockXPackMainPlugin = { + getFeatures: jest.fn().mockReturnValue(features), + }; + + const privileges = privilegesFactory(actions, mockXPackMainPlugin); + + const actual = privileges.get(); + expect(actual).toHaveProperty('features.foo', { + all: [ + actions.login, + actions.version, + 'app:all-app-1', + 'app:all-app-2', + 'ui:catalogue/catalogue-all-1', + 'ui:catalogue/catalogue-all-2', + 'ui:management/all/all-management-1', + 'ui:management/all/all-management-2', + ], + read: [ + actions.login, + actions.version, + 'app:read-app-1', + 'app:read-app-2', + 'ui:catalogue/catalogue-read-1', + 'ui:catalogue/catalogue-read-2', + 'ui:management/read/read-management-1', + 'ui:management/read/read-management-2', + ], + }); + }); + + test(`actions only specified at the privilege are alright too`, () => { + const features: Feature[] = [ + { + id: 'foo', + name: 'Foo Feature', + icon: 'arrowDown', + app: [], + privileges: { + all: { + savedObject: { + all: ['all-savedObject-all-1', 'all-savedObject-all-2'], + read: ['all-savedObject-read-1', 'all-savedObject-read-2'], + }, + ui: ['all-ui-1', 'all-ui-2'], + }, + read: { + savedObject: { + all: ['read-savedObject-all-1', 'read-savedObject-all-2'], + read: ['read-savedObject-read-1', 'read-savedObject-read-2'], + }, + ui: ['read-ui-1', 'read-ui-2'], + }, + }, + }, + ]; + + const mockXPackMainPlugin = { + getFeatures: jest.fn().mockReturnValue(features), + }; + + const privileges = privilegesFactory(actions, mockXPackMainPlugin); + + const actual = privileges.get(); + expect(actual).toHaveProperty('features.foo', { + all: [ + actions.login, + actions.version, + 'saved_object:all-savedObject-all-1/bulk_get', + 'saved_object:all-savedObject-all-1/get', + 'saved_object:all-savedObject-all-1/find', + 'saved_object:all-savedObject-all-1/create', + 'saved_object:all-savedObject-all-1/bulk_create', + 'saved_object:all-savedObject-all-1/update', + 'saved_object:all-savedObject-all-1/delete', + 'saved_object:all-savedObject-all-2/bulk_get', + 'saved_object:all-savedObject-all-2/get', + 'saved_object:all-savedObject-all-2/find', + 'saved_object:all-savedObject-all-2/create', + 'saved_object:all-savedObject-all-2/bulk_create', + 'saved_object:all-savedObject-all-2/update', + 'saved_object:all-savedObject-all-2/delete', + 'saved_object:all-savedObject-read-1/bulk_get', + 'saved_object:all-savedObject-read-1/get', + 'saved_object:all-savedObject-read-1/find', + 'saved_object:all-savedObject-read-2/bulk_get', + 'saved_object:all-savedObject-read-2/get', + 'saved_object:all-savedObject-read-2/find', + 'ui:foo/all-ui-1', + 'ui:foo/all-ui-2', + ], + read: [ + actions.login, + actions.version, + 'saved_object:read-savedObject-all-1/bulk_get', + 'saved_object:read-savedObject-all-1/get', + 'saved_object:read-savedObject-all-1/find', + 'saved_object:read-savedObject-all-1/create', + 'saved_object:read-savedObject-all-1/bulk_create', + 'saved_object:read-savedObject-all-1/update', + 'saved_object:read-savedObject-all-1/delete', + 'saved_object:read-savedObject-all-2/bulk_get', + 'saved_object:read-savedObject-all-2/get', + 'saved_object:read-savedObject-all-2/find', + 'saved_object:read-savedObject-all-2/create', + 'saved_object:read-savedObject-all-2/bulk_create', + 'saved_object:read-savedObject-all-2/update', + 'saved_object:read-savedObject-all-2/delete', + 'saved_object:read-savedObject-read-1/bulk_get', + 'saved_object:read-savedObject-read-1/get', + 'saved_object:read-savedObject-read-1/find', + 'saved_object:read-savedObject-read-2/bulk_get', + 'saved_object:read-savedObject-read-2/get', + 'saved_object:read-savedObject-read-2/find', + 'ui:foo/read-ui-1', + 'ui:foo/read-ui-2', + ], + }); + }); +}); + +// the `global` and `space` privileges behave very similarly, with the one exception being that +// "global all" includes the ability to manage spaces. The following tests both groups at once... +[ + { + group: 'global', + expectManageSpaces: true, + }, + { + group: 'space', + expectManageSpaces: false, + }, +].forEach(({ group, expectManageSpaces }) => { + describe(group, () => { + test('actions defined only at the feature are included in `all` and `read`', () => { + const features: Feature[] = [ + { + id: 'foo', + name: 'Foo Feature', + icon: 'arrowDown', + navLinkId: 'kibana:foo', + app: ['app-1', 'app-2'], + catalogue: ['catalogue-1', 'catalogue-2'], + management: { + foo: ['management-1', 'management-2'], + }, + privileges: { + all: { + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + read: { + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + }, + }, + ]; + + const mockXPackMainPlugin = { + getFeatures: jest.fn().mockReturnValue(features), + }; + + const privileges = privilegesFactory(actions, mockXPackMainPlugin); + + const actual = privileges.get(); + expect(actual).toHaveProperty(group, { + all: [ + actions.login, + actions.version, + ...(expectManageSpaces ? ['space:manage'] : []), + 'app:app-1', + 'app:app-2', + 'ui:catalogue/catalogue-1', + 'ui:catalogue/catalogue-2', + 'ui:management/foo/management-1', + 'ui:management/foo/management-2', + 'ui:navLinks/kibana:foo', + ], + read: [ + actions.login, + actions.version, + 'app:app-1', + 'app:app-2', + 'ui:catalogue/catalogue-1', + 'ui:catalogue/catalogue-2', + 'ui:management/foo/management-1', + 'ui:management/foo/management-2', + 'ui:navLinks/kibana:foo', + ], + }); + }); + + test('actions defined in any feature privilege are included in `all`', () => { + const features: Feature[] = [ + { + id: 'foo', + name: 'Foo Feature', + icon: 'arrowDown', + navLinkId: 'kibana:foo', + app: [], + catalogue: ['ignore-me-1', 'ignore-me-2'], + management: { + foo: ['ignore-me-1', 'ignore-me-2'], + }, + privileges: { + bar: { + management: { + 'bar-management': ['bar-management-1', 'bar-management-2'], + }, + catalogue: ['bar-catalogue-1', 'bar-catalogue-2'], + savedObject: { + all: ['bar-savedObject-all-1', 'bar-savedObject-all-2'], + read: ['bar-savedObject-read-1', 'bar-savedObject-read-2'], + }, + ui: ['bar-ui-1', 'bar-ui-2'], + }, + all: { + management: { + 'all-management': ['all-management-1', 'all-management-2'], + }, + catalogue: ['all-catalogue-1', 'all-catalogue-2'], + savedObject: { + all: ['all-savedObject-all-1', 'all-savedObject-all-2'], + read: ['all-savedObject-read-1', 'all-savedObject-read-2'], + }, + ui: ['all-ui-1', 'all-ui-2'], + }, + read: { + management: { + 'read-management': ['read-management-1', 'read-management-2'], + }, + catalogue: ['read-catalogue-1', 'read-catalogue-2'], + savedObject: { + all: ['read-savedObject-all-1', 'read-savedObject-all-2'], + read: ['read-savedObject-read-1', 'read-savedObject-read-2'], + }, + ui: ['read-ui-1', 'read-ui-2'], + }, + }, + }, + ]; + + const mockXPackMainPlugin = { + getFeatures: jest.fn().mockReturnValue(features), + }; + + const privileges = privilegesFactory(actions, mockXPackMainPlugin); + + const actual = privileges.get(); + expect(actual).toHaveProperty(`${group}.all`, [ + actions.login, + actions.version, + ...(expectManageSpaces ? ['space:manage'] : []), + 'saved_object:bar-savedObject-all-1/bulk_get', + 'saved_object:bar-savedObject-all-1/get', + 'saved_object:bar-savedObject-all-1/find', + 'saved_object:bar-savedObject-all-1/create', + 'saved_object:bar-savedObject-all-1/bulk_create', + 'saved_object:bar-savedObject-all-1/update', + 'saved_object:bar-savedObject-all-1/delete', + 'saved_object:bar-savedObject-all-2/bulk_get', + 'saved_object:bar-savedObject-all-2/get', + 'saved_object:bar-savedObject-all-2/find', + 'saved_object:bar-savedObject-all-2/create', + 'saved_object:bar-savedObject-all-2/bulk_create', + 'saved_object:bar-savedObject-all-2/update', + 'saved_object:bar-savedObject-all-2/delete', + 'saved_object:bar-savedObject-read-1/bulk_get', + 'saved_object:bar-savedObject-read-1/get', + 'saved_object:bar-savedObject-read-1/find', + 'saved_object:bar-savedObject-read-2/bulk_get', + 'saved_object:bar-savedObject-read-2/get', + 'saved_object:bar-savedObject-read-2/find', + 'ui:catalogue/bar-catalogue-1', + 'ui:catalogue/bar-catalogue-2', + 'ui:management/bar-management/bar-management-1', + 'ui:management/bar-management/bar-management-2', + 'ui:navLinks/kibana:foo', + 'ui:foo/bar-ui-1', + 'ui:foo/bar-ui-2', + 'saved_object:all-savedObject-all-1/bulk_get', + 'saved_object:all-savedObject-all-1/get', + 'saved_object:all-savedObject-all-1/find', + 'saved_object:all-savedObject-all-1/create', + 'saved_object:all-savedObject-all-1/bulk_create', + 'saved_object:all-savedObject-all-1/update', + 'saved_object:all-savedObject-all-1/delete', + 'saved_object:all-savedObject-all-2/bulk_get', + 'saved_object:all-savedObject-all-2/get', + 'saved_object:all-savedObject-all-2/find', + 'saved_object:all-savedObject-all-2/create', + 'saved_object:all-savedObject-all-2/bulk_create', + 'saved_object:all-savedObject-all-2/update', + 'saved_object:all-savedObject-all-2/delete', + 'saved_object:all-savedObject-read-1/bulk_get', + 'saved_object:all-savedObject-read-1/get', + 'saved_object:all-savedObject-read-1/find', + 'saved_object:all-savedObject-read-2/bulk_get', + 'saved_object:all-savedObject-read-2/get', + 'saved_object:all-savedObject-read-2/find', + 'ui:catalogue/all-catalogue-1', + 'ui:catalogue/all-catalogue-2', + 'ui:management/all-management/all-management-1', + 'ui:management/all-management/all-management-2', + 'ui:foo/all-ui-1', + 'ui:foo/all-ui-2', + 'saved_object:read-savedObject-all-1/bulk_get', + 'saved_object:read-savedObject-all-1/get', + 'saved_object:read-savedObject-all-1/find', + 'saved_object:read-savedObject-all-1/create', + 'saved_object:read-savedObject-all-1/bulk_create', + 'saved_object:read-savedObject-all-1/update', + 'saved_object:read-savedObject-all-1/delete', + 'saved_object:read-savedObject-all-2/bulk_get', + 'saved_object:read-savedObject-all-2/get', + 'saved_object:read-savedObject-all-2/find', + 'saved_object:read-savedObject-all-2/create', + 'saved_object:read-savedObject-all-2/bulk_create', + 'saved_object:read-savedObject-all-2/update', + 'saved_object:read-savedObject-all-2/delete', + 'saved_object:read-savedObject-read-1/bulk_get', + 'saved_object:read-savedObject-read-1/get', + 'saved_object:read-savedObject-read-1/find', + 'saved_object:read-savedObject-read-2/bulk_get', + 'saved_object:read-savedObject-read-2/get', + 'saved_object:read-savedObject-read-2/find', + 'ui:catalogue/read-catalogue-1', + 'ui:catalogue/read-catalogue-2', + 'ui:management/read-management/read-management-1', + 'ui:management/read-management/read-management-2', + 'ui:foo/read-ui-1', + 'ui:foo/read-ui-2', + ]); + }); + + test('actions defined in a feature privilege with name `read` are included in `read`', () => { + const features: Feature[] = [ + { + id: 'foo', + name: 'Foo Feature', + icon: 'arrowDown', + navLinkId: 'kibana:foo', + app: [], + catalogue: ['ignore-me-1', 'ignore-me-2'], + management: { + foo: ['ignore-me-1', 'ignore-me-2'], + }, + privileges: { + bar: { + management: { + 'ignore-me': ['ignore-me-1', 'ignore-me-2'], + }, + catalogue: ['ignore-me-1', 'ignore-me-2'], + savedObject: { + all: ['ignore-me-1', 'ignore-me-2'], + read: ['ignore-me-1', 'ignore-me-2'], + }, + ui: ['ignore-me-1', 'ignore-me-2'], + }, + all: { + management: { + 'ignore-me': ['ignore-me-1', 'ignore-me-2'], + }, + catalogue: ['ignore-me-1', 'ignore-me-2'], + savedObject: { + all: ['ignore-me-1', 'ignore-me-2'], + read: ['ignore-me-1', 'ignore-me-2'], + }, + ui: ['ignore-me-1', 'ignore-me-2'], + }, + read: { + management: { + 'read-management': ['read-management-1', 'read-management-2'], + }, + catalogue: ['read-catalogue-1', 'read-catalogue-2'], + savedObject: { + all: ['read-savedObject-all-1', 'read-savedObject-all-2'], + read: ['read-savedObject-read-1', 'read-savedObject-read-2'], + }, + ui: ['read-ui-1', 'read-ui-2'], + }, + }, + }, + ]; + + const mockXPackMainPlugin = { + getFeatures: jest.fn().mockReturnValue(features), + }; + + const privileges = privilegesFactory(actions, mockXPackMainPlugin); + + const actual = privileges.get(); + expect(actual).toHaveProperty(`${group}.read`, [ + actions.login, + actions.version, + 'saved_object:read-savedObject-all-1/bulk_get', + 'saved_object:read-savedObject-all-1/get', + 'saved_object:read-savedObject-all-1/find', + 'saved_object:read-savedObject-all-1/create', + 'saved_object:read-savedObject-all-1/bulk_create', + 'saved_object:read-savedObject-all-1/update', + 'saved_object:read-savedObject-all-1/delete', + 'saved_object:read-savedObject-all-2/bulk_get', + 'saved_object:read-savedObject-all-2/get', + 'saved_object:read-savedObject-all-2/find', + 'saved_object:read-savedObject-all-2/create', + 'saved_object:read-savedObject-all-2/bulk_create', + 'saved_object:read-savedObject-all-2/update', + 'saved_object:read-savedObject-all-2/delete', + 'saved_object:read-savedObject-read-1/bulk_get', + 'saved_object:read-savedObject-read-1/get', + 'saved_object:read-savedObject-read-1/find', + 'saved_object:read-savedObject-read-2/bulk_get', + 'saved_object:read-savedObject-read-2/get', + 'saved_object:read-savedObject-read-2/find', + 'ui:catalogue/read-catalogue-1', + 'ui:catalogue/read-catalogue-2', + 'ui:management/read-management/read-management-1', + 'ui:management/read-management/read-management-2', + 'ui:navLinks/kibana:foo', + 'ui:foo/read-ui-1', + 'ui:foo/read-ui-2', + ]); + }); + + test('actions defined in a feature privilege with `includeInBaseRead` are included in `read`', () => { + const features: Feature[] = [ + { + id: 'foo', + name: 'Foo Feature', + icon: 'arrowDown', + navLinkId: 'kibana:foo', + app: [], + catalogue: ['ignore-me-1', 'ignore-me-2'], + management: { + foo: ['ignore-me-1', 'ignore-me-2'], + }, + privileges: { + all: { + management: { + 'ignore-me': ['ignore-me-1', 'ignore-me-2'], + }, + catalogue: ['ignore-me-1', 'ignore-me-2'], + savedObject: { + all: ['ignore-me-1', 'ignore-me-2'], + read: ['ignore-me-1', 'ignore-me-2'], + }, + ui: ['ignore-me-1', 'ignore-me-2'], + }, + bar: { + grantWithBaseRead: true, + management: { + 'read-management': ['read-management-1', 'read-management-2'], + }, + catalogue: ['read-catalogue-1', 'read-catalogue-2'], + savedObject: { + all: ['read-savedObject-all-1', 'read-savedObject-all-2'], + read: ['read-savedObject-read-1', 'read-savedObject-read-2'], + }, + ui: ['read-ui-1', 'read-ui-2'], + }, + }, + }, + ]; + + const mockXPackMainPlugin = { + getFeatures: jest.fn().mockReturnValue(features), + }; + + const privileges = privilegesFactory(actions, mockXPackMainPlugin); + + const actual = privileges.get(); + expect(actual).toHaveProperty(`${group}.read`, [ + actions.login, + actions.version, + 'saved_object:read-savedObject-all-1/bulk_get', + 'saved_object:read-savedObject-all-1/get', + 'saved_object:read-savedObject-all-1/find', + 'saved_object:read-savedObject-all-1/create', + 'saved_object:read-savedObject-all-1/bulk_create', + 'saved_object:read-savedObject-all-1/update', + 'saved_object:read-savedObject-all-1/delete', + 'saved_object:read-savedObject-all-2/bulk_get', + 'saved_object:read-savedObject-all-2/get', + 'saved_object:read-savedObject-all-2/find', + 'saved_object:read-savedObject-all-2/create', + 'saved_object:read-savedObject-all-2/bulk_create', + 'saved_object:read-savedObject-all-2/update', + 'saved_object:read-savedObject-all-2/delete', + 'saved_object:read-savedObject-read-1/bulk_get', + 'saved_object:read-savedObject-read-1/get', + 'saved_object:read-savedObject-read-1/find', + 'saved_object:read-savedObject-read-2/bulk_get', + 'saved_object:read-savedObject-read-2/get', + 'saved_object:read-savedObject-read-2/find', + 'ui:catalogue/read-catalogue-1', + 'ui:catalogue/read-catalogue-2', + 'ui:management/read-management/read-management-1', + 'ui:management/read-management/read-management-2', + 'ui:navLinks/kibana:foo', + 'ui:foo/read-ui-1', + 'ui:foo/read-ui-2', + ]); + }); + }); +}); diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts new file mode 100644 index 0000000000000..d5f6005f8384a --- /dev/null +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts @@ -0,0 +1,56 @@ +/* + * 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 { RawKibanaPrivileges } from 'x-pack/plugins/security/common/model'; +import { Feature } from '../../../../../xpack_main/types'; +import { Actions } from '../actions'; +import { FeaturesPrivilegesBuilder } from './features_privileges_builder'; + +export interface PrivilegesService { + get(): RawKibanaPrivileges; +} + +interface XPackMainPlugin { + getFeatures(): Feature[]; +} + +export function privilegesFactory(actions: Actions, xpackMainPlugin: XPackMainPlugin) { + return { + get() { + const features = xpackMainPlugin.getFeatures(); + const featuresPrivilegesBuilder = new FeaturesPrivilegesBuilder(actions); + + return { + features: featuresPrivilegesBuilder.buildFeaturesPrivileges(features), + global: { + all: [ + actions.login, + actions.version, + actions.space.manage, + ...featuresPrivilegesBuilder.getAllActions(features), + ], + read: [ + actions.login, + actions.version, + ...featuresPrivilegesBuilder.getReadActions(features), + ], + }, + space: { + all: [ + actions.login, + actions.version, + ...featuresPrivilegesBuilder.getAllActions(features), + ], + read: [ + actions.login, + actions.version, + ...featuresPrivilegesBuilder.getReadActions(features), + ], + }, + }; + }, + }; +} From b21024916d6381738c882ce62e6a24ee61ef6fd2 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 5 Feb 2019 16:24:49 -0800 Subject: [PATCH 05/62] Splitting out the feature privilege builders --- .../components/edit_role_page.test.tsx | 2 +- .../feature_privilege_builder/api.ts | 21 ++ .../feature_privilege_builder/app.ts | 23 ++ .../feature_privilege_builder/catalogue.ts | 25 +++ .../feature_privilege_builder.ts | 20 ++ .../feature_privilege_builder/index.ts | 27 +++ .../feature_privilege_builder/management.ts | 28 +++ .../feature_privilege_builder/navlink.ts | 17 ++ .../feature_privilege_builder/saved_object.ts | 29 +++ .../feature_privilege_builder/ui.ts | 17 ++ .../privileges/features_privileges_builder.ts | 208 ------------------ .../privileges/privileges.test.ts | 46 ++-- .../authorization/privileges/privileges.ts | 85 +++++-- 13 files changed, 292 insertions(+), 256 deletions(-) create mode 100644 x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/api.ts create mode 100644 x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/app.ts create mode 100644 x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/catalogue.ts create mode 100644 x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/feature_privilege_builder.ts create mode 100644 x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/index.ts create mode 100644 x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/management.ts create mode 100644 x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/navlink.ts create mode 100644 x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/saved_object.ts create mode 100644 x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/ui.ts delete mode 100644 x-pack/plugins/security/server/lib/authorization/privileges/features_privileges_builder.ts diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx index 950e76e5bb505..4bd518a904d80 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/edit_role_page.test.tsx @@ -62,7 +62,7 @@ const buildRawKibanaPrivileges = () => { const actions = actionsFactory({ get: jest.fn(() => 'unit_test_version') }); - return privilegesFactory(['config', 'feature2'], actions, xpackMainPlugin).get(); + return privilegesFactory(actions, xpackMainPlugin).get(); }; const buildUICapabilities = (canManageSpaces = true) => { diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/api.ts b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/api.ts new file mode 100644 index 0000000000000..df9c1c9c9d0c2 --- /dev/null +++ b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/api.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 { + Feature, + FeatureKibanaPrivileges, +} from 'x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry'; +import { FeaturePrivilegeBuilder } from './feature_privilege_builder'; + +export class FeaturePrivilegeApiBuilder extends FeaturePrivilegeBuilder { + public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { + if (privilegeDefinition.api) { + return privilegeDefinition.api.map(operation => this.actions.api.get(operation)); + } + + return []; + } +} diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/app.ts b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/app.ts new file mode 100644 index 0000000000000..b81954cfd9cbd --- /dev/null +++ b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/app.ts @@ -0,0 +1,23 @@ +/* + * 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, + FeatureKibanaPrivileges, +} from 'x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry'; +import { FeaturePrivilegeBuilder } from './feature_privilege_builder'; + +export class FeaturePrivilegeAppBuilder extends FeaturePrivilegeBuilder { + public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { + const appIds = privilegeDefinition.app || feature.app; + + if (!appIds) { + return []; + } + + return appIds.map(appId => this.actions.app.get(appId)); + } +} diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/catalogue.ts b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/catalogue.ts new file mode 100644 index 0000000000000..3fa13a291c16b --- /dev/null +++ b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/catalogue.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Feature, + FeatureKibanaPrivileges, +} from 'x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry'; +import { FeaturePrivilegeBuilder } from './feature_privilege_builder'; + +export class FeaturePrivilegeCatalogueBuilder extends FeaturePrivilegeBuilder { + public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { + const catalogueEntries = privilegeDefinition.catalogue || feature.catalogue; + + if (!catalogueEntries) { + return []; + } + + return catalogueEntries.map(catalogueEntryId => + this.actions.ui.get('catalogue', catalogueEntryId) + ); + } +} diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/feature_privilege_builder.ts b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/feature_privilege_builder.ts new file mode 100644 index 0000000000000..d1e0b8bf85b62 --- /dev/null +++ b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/feature_privilege_builder.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Feature, + FeatureKibanaPrivileges, +} from 'x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry'; +import { Actions } from '../../actions'; + +export abstract class FeaturePrivilegeBuilder { + constructor(protected readonly actions: Actions) {} + + public abstract getActions( + privilegeDefinition: FeatureKibanaPrivileges, + feature: Feature + ): string[]; +} diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/index.ts b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/index.ts new file mode 100644 index 0000000000000..e008f4e105d61 --- /dev/null +++ b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/index.ts @@ -0,0 +1,27 @@ +/* + * 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 { Actions } from '../../actions'; +import { FeaturePrivilegeApiBuilder } from './api'; +import { FeaturePrivilegeAppBuilder } from './app'; +import { FeaturePrivilegeCatalogueBuilder } from './catalogue'; +import { FeaturePrivilegeManagementBuilder } from './management'; +import { FeaturePrivilegeNavlinkBuilder } from './navlink'; +import { FeaturePrivilegeSavedObjectBuilder } from './saved_object'; +import { FeaturePrivilegeUIBuilder } from './ui'; +export { FeaturePrivilegeBuilder } from './feature_privilege_builder'; + +export const featurePrivilegeBuildersFactory = (actions: Actions): FeaturePrivilegeApiBuilder[] => { + return [ + new FeaturePrivilegeApiBuilder(actions), + new FeaturePrivilegeAppBuilder(actions), + new FeaturePrivilegeCatalogueBuilder(actions), + new FeaturePrivilegeManagementBuilder(actions), + new FeaturePrivilegeNavlinkBuilder(actions), + new FeaturePrivilegeSavedObjectBuilder(actions), + new FeaturePrivilegeUIBuilder(actions), + ]; +}; diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/management.ts b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/management.ts new file mode 100644 index 0000000000000..4853da6270dfd --- /dev/null +++ b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/management.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Feature, + FeatureKibanaPrivileges, +} from 'x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry'; +import { FeaturePrivilegeBuilder } from './feature_privilege_builder'; + +export class FeaturePrivilegeManagementBuilder extends FeaturePrivilegeBuilder { + public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { + const managementSections = privilegeDefinition.management || feature.management; + + if (!managementSections) { + return []; + } + + return Object.entries(managementSections).reduce( + (acc, [sectionId, items]) => { + return [...acc, ...items.map(item => this.actions.ui.get('management', sectionId, item))]; + }, + [] as string[] + ); + } +} diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/navlink.ts b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/navlink.ts new file mode 100644 index 0000000000000..3e28300babc76 --- /dev/null +++ b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/navlink.ts @@ -0,0 +1,17 @@ +/* + * 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, + FeatureKibanaPrivileges, +} from 'x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry'; +import { FeaturePrivilegeBuilder } from './feature_privilege_builder'; + +export class FeaturePrivilegeNavlinkBuilder extends FeaturePrivilegeBuilder { + public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { + return feature.navLinkId ? [this.actions.ui.get('navLinks', feature.navLinkId)] : []; + } +} diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/saved_object.ts b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/saved_object.ts new file mode 100644 index 0000000000000..6a663cd1d01b8 --- /dev/null +++ b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/saved_object.ts @@ -0,0 +1,29 @@ +/* + * 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 { flatten, uniq } from 'lodash'; +import { + Feature, + FeatureKibanaPrivileges, +} from 'x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry'; +import { FeaturePrivilegeBuilder } from './feature_privilege_builder'; + +export class FeaturePrivilegeSavedObjectBuilder extends FeaturePrivilegeBuilder { + public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { + return uniq([ + ...flatten( + privilegeDefinition.savedObject.all.map(types => + this.actions.savedObject.allOperations(types) + ) + ), + ...flatten( + privilegeDefinition.savedObject.read.map(types => + this.actions.savedObject.readOperations(types) + ) + ), + ]); + } +} diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/ui.ts b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/ui.ts new file mode 100644 index 0000000000000..a815a3ad70b57 --- /dev/null +++ b/x-pack/plugins/security/server/lib/authorization/privileges/feature_privilege_builder/ui.ts @@ -0,0 +1,17 @@ +/* + * 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, + FeatureKibanaPrivileges, +} from 'x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry'; +import { FeaturePrivilegeBuilder } from './feature_privilege_builder'; + +export class FeaturePrivilegeUIBuilder extends FeaturePrivilegeBuilder { + public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { + return privilegeDefinition.ui.map(ui => this.actions.ui.get(feature.id, ui)); + } +} diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/features_privileges_builder.ts b/x-pack/plugins/security/server/lib/authorization/privileges/features_privileges_builder.ts deleted file mode 100644 index 6a0a447729f60..0000000000000 --- a/x-pack/plugins/security/server/lib/authorization/privileges/features_privileges_builder.ts +++ /dev/null @@ -1,208 +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 { flatten, mapValues, uniq } from 'lodash'; -import { RawKibanaFeaturePrivileges } from 'x-pack/plugins/security/common/model'; -import { FeatureKibanaPrivileges } from 'x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry'; -import { Feature } from '../../../../../xpack_main/types'; -import { Actions } from '../actions'; - -interface FeatureDerivedPrivileges { - getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[]; -} - -class FeatureDerivedAPIPrivileges implements FeatureDerivedPrivileges { - constructor(private readonly actions: Actions) {} - - public getActions(privilegeDefinition: FeatureKibanaPrivileges): string[] { - if (privilegeDefinition.api) { - return privilegeDefinition.api.map(operation => this.actions.api.get(operation)); - } - - return []; - } -} - -class FeatureDerivedAppPrivileges implements FeatureDerivedPrivileges { - constructor(private readonly actions: Actions) {} - - public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { - const appIds = privilegeDefinition.app || feature.app; - - if (!appIds) { - return []; - } - - return appIds.map(appId => this.actions.app.get(appId)); - } -} - -class FeatureDerivedSavedObjectPrivileges implements FeatureDerivedPrivileges { - constructor(private readonly actions: Actions) {} - - public getActions(privilegeDefinition: FeatureKibanaPrivileges): string[] { - return [ - ...flatten( - privilegeDefinition.savedObject.all.map(types => - this.actions.savedObject.allOperations(types) - ) - ), - ...flatten( - privilegeDefinition.savedObject.read.map(types => - this.actions.savedObject.readOperations(types) - ) - ), - ]; - } -} - -class FeatureDerivedUIPrivileges implements FeatureDerivedPrivileges { - constructor(private readonly actions: Actions) {} - - public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] { - return privilegeDefinition.ui.map(ui => this.actions.ui.get(feature.id, ui)); - } -} - -class FeatureDerivedNavlinkPrivileges implements FeatureDerivedPrivileges { - constructor(private readonly actions: Actions) {} - - public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) { - return feature.navLinkId ? [this.actions.ui.get('navLinks', feature.navLinkId)] : []; - } -} - -class FeatureDerivedCataloguePrivileges implements FeatureDerivedPrivileges { - constructor(private readonly actions: Actions) {} - - public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) { - const catalogueEntries = privilegeDefinition.catalogue || feature.catalogue; - - if (!catalogueEntries) { - return []; - } - - return catalogueEntries.map(catalogueEntryId => - this.actions.ui.get('catalogue', catalogueEntryId) - ); - } -} - -class FeatureDerivedManagementPrivileges implements FeatureDerivedPrivileges { - constructor(private readonly actions: Actions) {} - - public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature) { - const managementSections = privilegeDefinition.management || feature.management; - - if (!managementSections) { - return []; - } - - return Object.entries(managementSections).reduce( - (acc, [sectionId, items]) => { - return [...acc, ...items.map(item => this.actions.ui.get('management', sectionId, item))]; - }, - [] as string[] - ); - } -} - -export class FeaturesPrivilegesBuilder { - private actions: Actions; - private api: FeatureDerivedPrivileges; - private app: FeatureDerivedPrivileges; - private savedObject: FeatureDerivedPrivileges; - private ui: FeatureDerivedPrivileges; - private navLink: FeatureDerivedPrivileges; - private catalogue: FeatureDerivedPrivileges; - private management: FeatureDerivedPrivileges; - private featureDerivedPrivilegesCollection: FeatureDerivedPrivileges[]; - - constructor(actions: Actions) { - this.actions = actions; - this.api = new FeatureDerivedAPIPrivileges(this.actions); - this.app = new FeatureDerivedAppPrivileges(this.actions); - this.savedObject = new FeatureDerivedSavedObjectPrivileges(this.actions); - this.ui = new FeatureDerivedUIPrivileges(this.actions); - this.navLink = new FeatureDerivedNavlinkPrivileges(this.actions); - this.catalogue = new FeatureDerivedCataloguePrivileges(this.actions); - this.management = new FeatureDerivedManagementPrivileges(this.actions); - - this.featureDerivedPrivilegesCollection = [ - this.api, - this.app, - this.savedObject, - this.catalogue, - this.management, - this.navLink, - this.ui, - ]; - } - - public buildFeaturesPrivileges(features: Feature[]): RawKibanaFeaturePrivileges { - return features.reduce((acc: RawKibanaFeaturePrivileges, feature: Feature) => { - acc[feature.id] = this.buildFeaturePrivileges(feature); - return acc; - }, {}); - } - - public getAllActions(features: Feature[]): string[] { - return uniq( - flatten( - features.map(feature => - Object.values(feature.privileges).reduce((acc, privilege) => { - return [ - ...acc, - ...flatten( - this.featureDerivedPrivilegesCollection.map(featureDerivedPrivileges => - featureDerivedPrivileges.getActions(privilege, feature) - ) - ), - ]; - }, []) - ) - ) - ); - } - - public getReadActions(features: Feature[]): string[] { - return uniq( - flatten( - features.map(feature => - Object.entries(feature.privileges).reduce((acc, [privilegeId, privilege]) => { - if (!this.includeInBaseRead(privilegeId, privilege)) { - return acc; - } - - return [ - ...acc, - ...flatten( - this.featureDerivedPrivilegesCollection.map(featureDerivedPrivileges => - featureDerivedPrivileges.getActions(privilege, feature) - ) - ), - ]; - }, []) - ) - ) - ); - } - - private includeInBaseRead(privilegeId: string, privilege: FeatureKibanaPrivileges): boolean { - return privilegeId === 'read' || Boolean(privilege.grantWithBaseRead); - } - - private buildFeaturePrivileges(feature: Feature): Record { - return mapValues(feature.privileges, privilegeDefinition => [ - this.actions.login, - this.actions.version, - ...flatten( - this.featureDerivedPrivilegesCollection.map(featureDerivedPrivileges => - featureDerivedPrivileges.getActions(privilegeDefinition, feature) - ) - ), - ]); - } -} diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts index 82636474f64b0..7e833c2b687ba 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts @@ -376,6 +376,11 @@ describe('features', () => { actions.login, actions.version, ...(expectManageSpaces ? ['space:manage'] : []), + 'ui:catalogue/bar-catalogue-1', + 'ui:catalogue/bar-catalogue-2', + 'ui:management/bar-management/bar-management-1', + 'ui:management/bar-management/bar-management-2', + 'ui:navLinks/kibana:foo', 'saved_object:bar-savedObject-all-1/bulk_get', 'saved_object:bar-savedObject-all-1/get', 'saved_object:bar-savedObject-all-1/find', @@ -396,13 +401,12 @@ describe('features', () => { 'saved_object:bar-savedObject-read-2/bulk_get', 'saved_object:bar-savedObject-read-2/get', 'saved_object:bar-savedObject-read-2/find', - 'ui:catalogue/bar-catalogue-1', - 'ui:catalogue/bar-catalogue-2', - 'ui:management/bar-management/bar-management-1', - 'ui:management/bar-management/bar-management-2', - 'ui:navLinks/kibana:foo', 'ui:foo/bar-ui-1', 'ui:foo/bar-ui-2', + 'ui:catalogue/all-catalogue-1', + 'ui:catalogue/all-catalogue-2', + 'ui:management/all-management/all-management-1', + 'ui:management/all-management/all-management-2', 'saved_object:all-savedObject-all-1/bulk_get', 'saved_object:all-savedObject-all-1/get', 'saved_object:all-savedObject-all-1/find', @@ -423,12 +427,12 @@ describe('features', () => { 'saved_object:all-savedObject-read-2/bulk_get', 'saved_object:all-savedObject-read-2/get', 'saved_object:all-savedObject-read-2/find', - 'ui:catalogue/all-catalogue-1', - 'ui:catalogue/all-catalogue-2', - 'ui:management/all-management/all-management-1', - 'ui:management/all-management/all-management-2', 'ui:foo/all-ui-1', 'ui:foo/all-ui-2', + 'ui:catalogue/read-catalogue-1', + 'ui:catalogue/read-catalogue-2', + 'ui:management/read-management/read-management-1', + 'ui:management/read-management/read-management-2', 'saved_object:read-savedObject-all-1/bulk_get', 'saved_object:read-savedObject-all-1/get', 'saved_object:read-savedObject-all-1/find', @@ -449,10 +453,6 @@ describe('features', () => { 'saved_object:read-savedObject-read-2/bulk_get', 'saved_object:read-savedObject-read-2/get', 'saved_object:read-savedObject-read-2/find', - 'ui:catalogue/read-catalogue-1', - 'ui:catalogue/read-catalogue-2', - 'ui:management/read-management/read-management-1', - 'ui:management/read-management/read-management-2', 'ui:foo/read-ui-1', 'ui:foo/read-ui-2', ]); @@ -518,6 +518,11 @@ describe('features', () => { expect(actual).toHaveProperty(`${group}.read`, [ actions.login, actions.version, + 'ui:catalogue/read-catalogue-1', + 'ui:catalogue/read-catalogue-2', + 'ui:management/read-management/read-management-1', + 'ui:management/read-management/read-management-2', + 'ui:navLinks/kibana:foo', 'saved_object:read-savedObject-all-1/bulk_get', 'saved_object:read-savedObject-all-1/get', 'saved_object:read-savedObject-all-1/find', @@ -538,11 +543,6 @@ describe('features', () => { 'saved_object:read-savedObject-read-2/bulk_get', 'saved_object:read-savedObject-read-2/get', 'saved_object:read-savedObject-read-2/find', - 'ui:catalogue/read-catalogue-1', - 'ui:catalogue/read-catalogue-2', - 'ui:management/read-management/read-management-1', - 'ui:management/read-management/read-management-2', - 'ui:navLinks/kibana:foo', 'ui:foo/read-ui-1', 'ui:foo/read-ui-2', ]); @@ -598,6 +598,11 @@ describe('features', () => { expect(actual).toHaveProperty(`${group}.read`, [ actions.login, actions.version, + 'ui:catalogue/read-catalogue-1', + 'ui:catalogue/read-catalogue-2', + 'ui:management/read-management/read-management-1', + 'ui:management/read-management/read-management-2', + 'ui:navLinks/kibana:foo', 'saved_object:read-savedObject-all-1/bulk_get', 'saved_object:read-savedObject-all-1/get', 'saved_object:read-savedObject-all-1/find', @@ -618,11 +623,6 @@ describe('features', () => { 'saved_object:read-savedObject-read-2/bulk_get', 'saved_object:read-savedObject-read-2/get', 'saved_object:read-savedObject-read-2/find', - 'ui:catalogue/read-catalogue-1', - 'ui:catalogue/read-catalogue-2', - 'ui:management/read-management/read-management-1', - 'ui:management/read-management/read-management-2', - 'ui:navLinks/kibana:foo', 'ui:foo/read-ui-1', 'ui:foo/read-ui-2', ]); diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts index d5f6005f8384a..b76779f630939 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts @@ -4,10 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RawKibanaPrivileges } from 'x-pack/plugins/security/common/model'; +import { flatten, mapValues, uniq } from 'lodash'; +import { + RawKibanaFeaturePrivileges, + RawKibanaPrivileges, +} from 'x-pack/plugins/security/common/model'; import { Feature } from '../../../../../xpack_main/types'; import { Actions } from '../actions'; -import { FeaturesPrivilegesBuilder } from './features_privileges_builder'; +import { featurePrivilegeBuildersFactory } from './feature_privilege_builder'; export interface PrivilegesService { get(): RawKibanaPrivileges; @@ -18,37 +22,70 @@ interface XPackMainPlugin { } export function privilegesFactory(actions: Actions, xpackMainPlugin: XPackMainPlugin) { + const featurePrivilegeBuilders = featurePrivilegeBuildersFactory(actions); + return { get() { const features = xpackMainPlugin.getFeatures(); - const featuresPrivilegesBuilder = new FeaturesPrivilegesBuilder(actions); + + const allActions = uniq( + flatten( + features.map(feature => + Object.values(feature.privileges).reduce((acc, privilege) => { + return [ + ...acc, + ...flatten( + featurePrivilegeBuilders.map(featurePrivilegeBuilder => + featurePrivilegeBuilder.getActions(privilege, feature) + ) + ), + ]; + }, []) + ) + ) + ); + + const readActions = uniq( + flatten( + features.map(feature => + Object.entries(feature.privileges).reduce((acc, [privilegeId, privilege]) => { + if (privilegeId !== 'read' && !Boolean(privilege.grantWithBaseRead)) { + return acc; + } + + return [ + ...acc, + ...flatten( + featurePrivilegeBuilders.map(featurePrivilegeBuilder => + featurePrivilegeBuilder.getActions(privilege, feature) + ) + ), + ]; + }, []) + ) + ) + ); return { - features: featuresPrivilegesBuilder.buildFeaturesPrivileges(features), - global: { - all: [ - actions.login, - actions.version, - actions.space.manage, - ...featuresPrivilegesBuilder.getAllActions(features), - ], - read: [ + features: features.reduce((acc: RawKibanaFeaturePrivileges, feature: Feature) => { + acc[feature.id] = mapValues(feature.privileges, privilege => [ actions.login, actions.version, - ...featuresPrivilegesBuilder.getReadActions(features), - ], + ...flatten( + featurePrivilegeBuilders.map(featurePrivilegeBuilder => + featurePrivilegeBuilder.getActions(privilege, feature) + ) + ), + ]); + return acc; + }, {}), + global: { + all: [actions.login, actions.version, actions.space.manage, ...allActions], + read: [actions.login, actions.version, ...readActions], }, space: { - all: [ - actions.login, - actions.version, - ...featuresPrivilegesBuilder.getAllActions(features), - ], - read: [ - actions.login, - actions.version, - ...featuresPrivilegesBuilder.getReadActions(features), - ], + all: [actions.login, actions.version, ...allActions], + read: [actions.login, actions.version, ...readActions], }, }; }, From 0d67f4de0ef40b26572c5c9ae4e4ea405989f0f0 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 5 Feb 2019 16:48:10 -0800 Subject: [PATCH 06/62] Using actions instead of relying on their implementation --- .../privileges/privileges.test.ts | 434 +++++++++--------- 1 file changed, 217 insertions(+), 217 deletions(-) diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts index 7e833c2b687ba..d3ea985230575 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts @@ -54,24 +54,24 @@ describe('features', () => { all: [ actions.login, actions.version, - 'app:app-1', - 'app:app-2', - 'ui:catalogue/catalogue-1', - 'ui:catalogue/catalogue-2', - 'ui:management/foo/management-1', - 'ui:management/foo/management-2', - 'ui:navLinks/kibana:foo', + actions.app.get('app-1'), + actions.app.get('app-2'), + actions.ui.get('catalogue', 'catalogue-1'), + actions.ui.get('catalogue', 'catalogue-2'), + actions.ui.get('management', 'foo', 'management-1'), + actions.ui.get('management', 'foo', 'management-2'), + actions.ui.get('navLinks', 'kibana:foo'), ], read: [ actions.login, actions.version, - 'app:app-1', - 'app:app-2', - 'ui:catalogue/catalogue-1', - 'ui:catalogue/catalogue-2', - 'ui:management/foo/management-1', - 'ui:management/foo/management-2', - 'ui:navLinks/kibana:foo', + actions.app.get('app-1'), + actions.app.get('app-2'), + actions.ui.get('catalogue', 'catalogue-1'), + actions.ui.get('catalogue', 'catalogue-2'), + actions.ui.get('management', 'foo', 'management-1'), + actions.ui.get('management', 'foo', 'management-2'), + actions.ui.get('navLinks', 'kibana:foo'), ], }); }); @@ -127,22 +127,22 @@ describe('features', () => { all: [ actions.login, actions.version, - 'app:all-app-1', - 'app:all-app-2', - 'ui:catalogue/catalogue-all-1', - 'ui:catalogue/catalogue-all-2', - 'ui:management/all/all-management-1', - 'ui:management/all/all-management-2', + actions.app.get('all-app-1'), + actions.app.get('all-app-2'), + actions.ui.get('catalogue', 'catalogue-all-1'), + actions.ui.get('catalogue', 'catalogue-all-2'), + actions.ui.get('management', 'all', 'all-management-1'), + actions.ui.get('management', 'all', 'all-management-2'), ], read: [ actions.login, actions.version, - 'app:read-app-1', - 'app:read-app-2', - 'ui:catalogue/catalogue-read-1', - 'ui:catalogue/catalogue-read-2', - 'ui:management/read/read-management-1', - 'ui:management/read/read-management-2', + actions.app.get('read-app-1'), + actions.app.get('read-app-2'), + actions.ui.get('catalogue', 'catalogue-read-1'), + actions.ui.get('catalogue', 'catalogue-read-2'), + actions.ui.get('management', 'read', 'read-management-1'), + actions.ui.get('management', 'read', 'read-management-2'), ], }); }); @@ -184,54 +184,54 @@ describe('features', () => { all: [ actions.login, actions.version, - 'saved_object:all-savedObject-all-1/bulk_get', - 'saved_object:all-savedObject-all-1/get', - 'saved_object:all-savedObject-all-1/find', - 'saved_object:all-savedObject-all-1/create', - 'saved_object:all-savedObject-all-1/bulk_create', - 'saved_object:all-savedObject-all-1/update', - 'saved_object:all-savedObject-all-1/delete', - 'saved_object:all-savedObject-all-2/bulk_get', - 'saved_object:all-savedObject-all-2/get', - 'saved_object:all-savedObject-all-2/find', - 'saved_object:all-savedObject-all-2/create', - 'saved_object:all-savedObject-all-2/bulk_create', - 'saved_object:all-savedObject-all-2/update', - 'saved_object:all-savedObject-all-2/delete', - 'saved_object:all-savedObject-read-1/bulk_get', - 'saved_object:all-savedObject-read-1/get', - 'saved_object:all-savedObject-read-1/find', - 'saved_object:all-savedObject-read-2/bulk_get', - 'saved_object:all-savedObject-read-2/get', - 'saved_object:all-savedObject-read-2/find', - 'ui:foo/all-ui-1', - 'ui:foo/all-ui-2', + actions.savedObject.get('all-savedObject-all-1', 'bulk_get'), + actions.savedObject.get('all-savedObject-all-1', 'get'), + actions.savedObject.get('all-savedObject-all-1', 'find'), + actions.savedObject.get('all-savedObject-all-1', 'create'), + actions.savedObject.get('all-savedObject-all-1', 'bulk_create'), + actions.savedObject.get('all-savedObject-all-1', 'update'), + actions.savedObject.get('all-savedObject-all-1', 'delete'), + actions.savedObject.get('all-savedObject-all-2', 'bulk_get'), + actions.savedObject.get('all-savedObject-all-2', 'get'), + actions.savedObject.get('all-savedObject-all-2', 'find'), + actions.savedObject.get('all-savedObject-all-2', 'create'), + actions.savedObject.get('all-savedObject-all-2', 'bulk_create'), + actions.savedObject.get('all-savedObject-all-2', 'update'), + actions.savedObject.get('all-savedObject-all-2', 'delete'), + actions.savedObject.get('all-savedObject-read-1', 'bulk_get'), + actions.savedObject.get('all-savedObject-read-1', 'get'), + actions.savedObject.get('all-savedObject-read-1', 'find'), + actions.savedObject.get('all-savedObject-read-2', 'bulk_get'), + actions.savedObject.get('all-savedObject-read-2', 'get'), + actions.savedObject.get('all-savedObject-read-2', 'find'), + actions.ui.get('foo', 'all-ui-1'), + actions.ui.get('foo', 'all-ui-2'), ], read: [ actions.login, actions.version, - 'saved_object:read-savedObject-all-1/bulk_get', - 'saved_object:read-savedObject-all-1/get', - 'saved_object:read-savedObject-all-1/find', - 'saved_object:read-savedObject-all-1/create', - 'saved_object:read-savedObject-all-1/bulk_create', - 'saved_object:read-savedObject-all-1/update', - 'saved_object:read-savedObject-all-1/delete', - 'saved_object:read-savedObject-all-2/bulk_get', - 'saved_object:read-savedObject-all-2/get', - 'saved_object:read-savedObject-all-2/find', - 'saved_object:read-savedObject-all-2/create', - 'saved_object:read-savedObject-all-2/bulk_create', - 'saved_object:read-savedObject-all-2/update', - 'saved_object:read-savedObject-all-2/delete', - 'saved_object:read-savedObject-read-1/bulk_get', - 'saved_object:read-savedObject-read-1/get', - 'saved_object:read-savedObject-read-1/find', - 'saved_object:read-savedObject-read-2/bulk_get', - 'saved_object:read-savedObject-read-2/get', - 'saved_object:read-savedObject-read-2/find', - 'ui:foo/read-ui-1', - 'ui:foo/read-ui-2', + actions.savedObject.get('read-savedObject-all-1', 'bulk_get'), + actions.savedObject.get('read-savedObject-all-1', 'get'), + actions.savedObject.get('read-savedObject-all-1', 'find'), + actions.savedObject.get('read-savedObject-all-1', 'create'), + actions.savedObject.get('read-savedObject-all-1', 'bulk_create'), + actions.savedObject.get('read-savedObject-all-1', 'update'), + actions.savedObject.get('read-savedObject-all-1', 'delete'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_get'), + actions.savedObject.get('read-savedObject-all-2', 'get'), + actions.savedObject.get('read-savedObject-all-2', 'find'), + actions.savedObject.get('read-savedObject-all-2', 'create'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_create'), + actions.savedObject.get('read-savedObject-all-2', 'update'), + actions.savedObject.get('read-savedObject-all-2', 'delete'), + actions.savedObject.get('read-savedObject-read-1', 'bulk_get'), + actions.savedObject.get('read-savedObject-read-1', 'get'), + actions.savedObject.get('read-savedObject-read-1', 'find'), + actions.savedObject.get('read-savedObject-read-2', 'bulk_get'), + actions.savedObject.get('read-savedObject-read-2', 'get'), + actions.savedObject.get('read-savedObject-read-2', 'find'), + actions.ui.get('foo', 'read-ui-1'), + actions.ui.get('foo', 'read-ui-2'), ], }); }); @@ -293,24 +293,24 @@ describe('features', () => { actions.login, actions.version, ...(expectManageSpaces ? ['space:manage'] : []), - 'app:app-1', - 'app:app-2', - 'ui:catalogue/catalogue-1', - 'ui:catalogue/catalogue-2', - 'ui:management/foo/management-1', - 'ui:management/foo/management-2', - 'ui:navLinks/kibana:foo', + actions.app.get('app-1'), + actions.app.get('app-2'), + actions.ui.get('catalogue', 'catalogue-1'), + actions.ui.get('catalogue', 'catalogue-2'), + actions.ui.get('management', 'foo', 'management-1'), + actions.ui.get('management', 'foo', 'management-2'), + actions.ui.get('navLinks', 'kibana:foo'), ], read: [ actions.login, actions.version, - 'app:app-1', - 'app:app-2', - 'ui:catalogue/catalogue-1', - 'ui:catalogue/catalogue-2', - 'ui:management/foo/management-1', - 'ui:management/foo/management-2', - 'ui:navLinks/kibana:foo', + actions.app.get('app-1'), + actions.app.get('app-2'), + actions.ui.get('catalogue', 'catalogue-1'), + actions.ui.get('catalogue', 'catalogue-2'), + actions.ui.get('management', 'foo', 'management-1'), + actions.ui.get('management', 'foo', 'management-2'), + actions.ui.get('navLinks', 'kibana:foo'), ], }); }); @@ -376,85 +376,85 @@ describe('features', () => { actions.login, actions.version, ...(expectManageSpaces ? ['space:manage'] : []), - 'ui:catalogue/bar-catalogue-1', - 'ui:catalogue/bar-catalogue-2', - 'ui:management/bar-management/bar-management-1', - 'ui:management/bar-management/bar-management-2', - 'ui:navLinks/kibana:foo', - 'saved_object:bar-savedObject-all-1/bulk_get', - 'saved_object:bar-savedObject-all-1/get', - 'saved_object:bar-savedObject-all-1/find', - 'saved_object:bar-savedObject-all-1/create', - 'saved_object:bar-savedObject-all-1/bulk_create', - 'saved_object:bar-savedObject-all-1/update', - 'saved_object:bar-savedObject-all-1/delete', - 'saved_object:bar-savedObject-all-2/bulk_get', - 'saved_object:bar-savedObject-all-2/get', - 'saved_object:bar-savedObject-all-2/find', - 'saved_object:bar-savedObject-all-2/create', - 'saved_object:bar-savedObject-all-2/bulk_create', - 'saved_object:bar-savedObject-all-2/update', - 'saved_object:bar-savedObject-all-2/delete', - 'saved_object:bar-savedObject-read-1/bulk_get', - 'saved_object:bar-savedObject-read-1/get', - 'saved_object:bar-savedObject-read-1/find', - 'saved_object:bar-savedObject-read-2/bulk_get', - 'saved_object:bar-savedObject-read-2/get', - 'saved_object:bar-savedObject-read-2/find', - 'ui:foo/bar-ui-1', - 'ui:foo/bar-ui-2', - 'ui:catalogue/all-catalogue-1', - 'ui:catalogue/all-catalogue-2', - 'ui:management/all-management/all-management-1', - 'ui:management/all-management/all-management-2', - 'saved_object:all-savedObject-all-1/bulk_get', - 'saved_object:all-savedObject-all-1/get', - 'saved_object:all-savedObject-all-1/find', - 'saved_object:all-savedObject-all-1/create', - 'saved_object:all-savedObject-all-1/bulk_create', - 'saved_object:all-savedObject-all-1/update', - 'saved_object:all-savedObject-all-1/delete', - 'saved_object:all-savedObject-all-2/bulk_get', - 'saved_object:all-savedObject-all-2/get', - 'saved_object:all-savedObject-all-2/find', - 'saved_object:all-savedObject-all-2/create', - 'saved_object:all-savedObject-all-2/bulk_create', - 'saved_object:all-savedObject-all-2/update', - 'saved_object:all-savedObject-all-2/delete', - 'saved_object:all-savedObject-read-1/bulk_get', - 'saved_object:all-savedObject-read-1/get', - 'saved_object:all-savedObject-read-1/find', - 'saved_object:all-savedObject-read-2/bulk_get', - 'saved_object:all-savedObject-read-2/get', - 'saved_object:all-savedObject-read-2/find', - 'ui:foo/all-ui-1', - 'ui:foo/all-ui-2', - 'ui:catalogue/read-catalogue-1', - 'ui:catalogue/read-catalogue-2', - 'ui:management/read-management/read-management-1', - 'ui:management/read-management/read-management-2', - 'saved_object:read-savedObject-all-1/bulk_get', - 'saved_object:read-savedObject-all-1/get', - 'saved_object:read-savedObject-all-1/find', - 'saved_object:read-savedObject-all-1/create', - 'saved_object:read-savedObject-all-1/bulk_create', - 'saved_object:read-savedObject-all-1/update', - 'saved_object:read-savedObject-all-1/delete', - 'saved_object:read-savedObject-all-2/bulk_get', - 'saved_object:read-savedObject-all-2/get', - 'saved_object:read-savedObject-all-2/find', - 'saved_object:read-savedObject-all-2/create', - 'saved_object:read-savedObject-all-2/bulk_create', - 'saved_object:read-savedObject-all-2/update', - 'saved_object:read-savedObject-all-2/delete', - 'saved_object:read-savedObject-read-1/bulk_get', - 'saved_object:read-savedObject-read-1/get', - 'saved_object:read-savedObject-read-1/find', - 'saved_object:read-savedObject-read-2/bulk_get', - 'saved_object:read-savedObject-read-2/get', - 'saved_object:read-savedObject-read-2/find', - 'ui:foo/read-ui-1', - 'ui:foo/read-ui-2', + actions.ui.get('catalogue', 'bar-catalogue-1'), + actions.ui.get('catalogue', 'bar-catalogue-2'), + actions.ui.get('management', 'bar-management', 'bar-management-1'), + actions.ui.get('management', 'bar-management', 'bar-management-2'), + actions.ui.get('navLinks', 'kibana:foo'), + actions.savedObject.get('bar-savedObject-all-1', 'bulk_get'), + actions.savedObject.get('bar-savedObject-all-1', 'get'), + actions.savedObject.get('bar-savedObject-all-1', 'find'), + actions.savedObject.get('bar-savedObject-all-1', 'create'), + actions.savedObject.get('bar-savedObject-all-1', 'bulk_create'), + actions.savedObject.get('bar-savedObject-all-1', 'update'), + actions.savedObject.get('bar-savedObject-all-1', 'delete'), + actions.savedObject.get('bar-savedObject-all-2', 'bulk_get'), + actions.savedObject.get('bar-savedObject-all-2', 'get'), + actions.savedObject.get('bar-savedObject-all-2', 'find'), + actions.savedObject.get('bar-savedObject-all-2', 'create'), + actions.savedObject.get('bar-savedObject-all-2', 'bulk_create'), + actions.savedObject.get('bar-savedObject-all-2', 'update'), + actions.savedObject.get('bar-savedObject-all-2', 'delete'), + actions.savedObject.get('bar-savedObject-read-1', 'bulk_get'), + actions.savedObject.get('bar-savedObject-read-1', 'get'), + actions.savedObject.get('bar-savedObject-read-1', 'find'), + actions.savedObject.get('bar-savedObject-read-2', 'bulk_get'), + actions.savedObject.get('bar-savedObject-read-2', 'get'), + actions.savedObject.get('bar-savedObject-read-2', 'find'), + actions.ui.get('foo', 'bar-ui-1'), + actions.ui.get('foo', 'bar-ui-2'), + actions.ui.get('catalogue', 'all-catalogue-1'), + actions.ui.get('catalogue', 'all-catalogue-2'), + actions.ui.get('management', 'all-management', 'all-management-1'), + actions.ui.get('management', 'all-management', 'all-management-2'), + actions.savedObject.get('all-savedObject-all-1', 'bulk_get'), + actions.savedObject.get('all-savedObject-all-1', 'get'), + actions.savedObject.get('all-savedObject-all-1', 'find'), + actions.savedObject.get('all-savedObject-all-1', 'create'), + actions.savedObject.get('all-savedObject-all-1', 'bulk_create'), + actions.savedObject.get('all-savedObject-all-1', 'update'), + actions.savedObject.get('all-savedObject-all-1', 'delete'), + actions.savedObject.get('all-savedObject-all-2', 'bulk_get'), + actions.savedObject.get('all-savedObject-all-2', 'get'), + actions.savedObject.get('all-savedObject-all-2', 'find'), + actions.savedObject.get('all-savedObject-all-2', 'create'), + actions.savedObject.get('all-savedObject-all-2', 'bulk_create'), + actions.savedObject.get('all-savedObject-all-2', 'update'), + actions.savedObject.get('all-savedObject-all-2', 'delete'), + actions.savedObject.get('all-savedObject-read-1', 'bulk_get'), + actions.savedObject.get('all-savedObject-read-1', 'get'), + actions.savedObject.get('all-savedObject-read-1', 'find'), + actions.savedObject.get('all-savedObject-read-2', 'bulk_get'), + actions.savedObject.get('all-savedObject-read-2', 'get'), + actions.savedObject.get('all-savedObject-read-2', 'find'), + actions.ui.get('foo', 'all-ui-1'), + actions.ui.get('foo', 'all-ui-2'), + actions.ui.get('catalogue', 'read-catalogue-1'), + actions.ui.get('catalogue', 'read-catalogue-2'), + actions.ui.get('management', 'read-management', 'read-management-1'), + actions.ui.get('management', 'read-management', 'read-management-2'), + actions.savedObject.get('read-savedObject-all-1', 'bulk_get'), + actions.savedObject.get('read-savedObject-all-1', 'get'), + actions.savedObject.get('read-savedObject-all-1', 'find'), + actions.savedObject.get('read-savedObject-all-1', 'create'), + actions.savedObject.get('read-savedObject-all-1', 'bulk_create'), + actions.savedObject.get('read-savedObject-all-1', 'update'), + actions.savedObject.get('read-savedObject-all-1', 'delete'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_get'), + actions.savedObject.get('read-savedObject-all-2', 'get'), + actions.savedObject.get('read-savedObject-all-2', 'find'), + actions.savedObject.get('read-savedObject-all-2', 'create'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_create'), + actions.savedObject.get('read-savedObject-all-2', 'update'), + actions.savedObject.get('read-savedObject-all-2', 'delete'), + actions.savedObject.get('read-savedObject-read-1', 'bulk_get'), + actions.savedObject.get('read-savedObject-read-1', 'get'), + actions.savedObject.get('read-savedObject-read-1', 'find'), + actions.savedObject.get('read-savedObject-read-2', 'bulk_get'), + actions.savedObject.get('read-savedObject-read-2', 'get'), + actions.savedObject.get('read-savedObject-read-2', 'find'), + actions.ui.get('foo', 'read-ui-1'), + actions.ui.get('foo', 'read-ui-2'), ]); }); @@ -518,33 +518,33 @@ describe('features', () => { expect(actual).toHaveProperty(`${group}.read`, [ actions.login, actions.version, - 'ui:catalogue/read-catalogue-1', - 'ui:catalogue/read-catalogue-2', - 'ui:management/read-management/read-management-1', - 'ui:management/read-management/read-management-2', - 'ui:navLinks/kibana:foo', - 'saved_object:read-savedObject-all-1/bulk_get', - 'saved_object:read-savedObject-all-1/get', - 'saved_object:read-savedObject-all-1/find', - 'saved_object:read-savedObject-all-1/create', - 'saved_object:read-savedObject-all-1/bulk_create', - 'saved_object:read-savedObject-all-1/update', - 'saved_object:read-savedObject-all-1/delete', - 'saved_object:read-savedObject-all-2/bulk_get', - 'saved_object:read-savedObject-all-2/get', - 'saved_object:read-savedObject-all-2/find', - 'saved_object:read-savedObject-all-2/create', - 'saved_object:read-savedObject-all-2/bulk_create', - 'saved_object:read-savedObject-all-2/update', - 'saved_object:read-savedObject-all-2/delete', - 'saved_object:read-savedObject-read-1/bulk_get', - 'saved_object:read-savedObject-read-1/get', - 'saved_object:read-savedObject-read-1/find', - 'saved_object:read-savedObject-read-2/bulk_get', - 'saved_object:read-savedObject-read-2/get', - 'saved_object:read-savedObject-read-2/find', - 'ui:foo/read-ui-1', - 'ui:foo/read-ui-2', + actions.ui.get('catalogue', 'read-catalogue-1'), + actions.ui.get('catalogue', 'read-catalogue-2'), + actions.ui.get('management', 'read-management', 'read-management-1'), + actions.ui.get('management', 'read-management', 'read-management-2'), + actions.ui.get('navLinks', 'kibana:foo'), + actions.savedObject.get('read-savedObject-all-1', 'bulk_get'), + actions.savedObject.get('read-savedObject-all-1', 'get'), + actions.savedObject.get('read-savedObject-all-1', 'find'), + actions.savedObject.get('read-savedObject-all-1', 'create'), + actions.savedObject.get('read-savedObject-all-1', 'bulk_create'), + actions.savedObject.get('read-savedObject-all-1', 'update'), + actions.savedObject.get('read-savedObject-all-1', 'delete'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_get'), + actions.savedObject.get('read-savedObject-all-2', 'get'), + actions.savedObject.get('read-savedObject-all-2', 'find'), + actions.savedObject.get('read-savedObject-all-2', 'create'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_create'), + actions.savedObject.get('read-savedObject-all-2', 'update'), + actions.savedObject.get('read-savedObject-all-2', 'delete'), + actions.savedObject.get('read-savedObject-read-1', 'bulk_get'), + actions.savedObject.get('read-savedObject-read-1', 'get'), + actions.savedObject.get('read-savedObject-read-1', 'find'), + actions.savedObject.get('read-savedObject-read-2', 'bulk_get'), + actions.savedObject.get('read-savedObject-read-2', 'get'), + actions.savedObject.get('read-savedObject-read-2', 'find'), + actions.ui.get('foo', 'read-ui-1'), + actions.ui.get('foo', 'read-ui-2'), ]); }); @@ -598,33 +598,33 @@ describe('features', () => { expect(actual).toHaveProperty(`${group}.read`, [ actions.login, actions.version, - 'ui:catalogue/read-catalogue-1', - 'ui:catalogue/read-catalogue-2', - 'ui:management/read-management/read-management-1', - 'ui:management/read-management/read-management-2', - 'ui:navLinks/kibana:foo', - 'saved_object:read-savedObject-all-1/bulk_get', - 'saved_object:read-savedObject-all-1/get', - 'saved_object:read-savedObject-all-1/find', - 'saved_object:read-savedObject-all-1/create', - 'saved_object:read-savedObject-all-1/bulk_create', - 'saved_object:read-savedObject-all-1/update', - 'saved_object:read-savedObject-all-1/delete', - 'saved_object:read-savedObject-all-2/bulk_get', - 'saved_object:read-savedObject-all-2/get', - 'saved_object:read-savedObject-all-2/find', - 'saved_object:read-savedObject-all-2/create', - 'saved_object:read-savedObject-all-2/bulk_create', - 'saved_object:read-savedObject-all-2/update', - 'saved_object:read-savedObject-all-2/delete', - 'saved_object:read-savedObject-read-1/bulk_get', - 'saved_object:read-savedObject-read-1/get', - 'saved_object:read-savedObject-read-1/find', - 'saved_object:read-savedObject-read-2/bulk_get', - 'saved_object:read-savedObject-read-2/get', - 'saved_object:read-savedObject-read-2/find', - 'ui:foo/read-ui-1', - 'ui:foo/read-ui-2', + actions.ui.get('catalogue', 'read-catalogue-1'), + actions.ui.get('catalogue', 'read-catalogue-2'), + actions.ui.get('management', 'read-management', 'read-management-1'), + actions.ui.get('management', 'read-management', 'read-management-2'), + actions.ui.get('navLinks', 'kibana:foo'), + actions.savedObject.get('read-savedObject-all-1', 'bulk_get'), + actions.savedObject.get('read-savedObject-all-1', 'get'), + actions.savedObject.get('read-savedObject-all-1', 'find'), + actions.savedObject.get('read-savedObject-all-1', 'create'), + actions.savedObject.get('read-savedObject-all-1', 'bulk_create'), + actions.savedObject.get('read-savedObject-all-1', 'update'), + actions.savedObject.get('read-savedObject-all-1', 'delete'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_get'), + actions.savedObject.get('read-savedObject-all-2', 'get'), + actions.savedObject.get('read-savedObject-all-2', 'find'), + actions.savedObject.get('read-savedObject-all-2', 'create'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_create'), + actions.savedObject.get('read-savedObject-all-2', 'update'), + actions.savedObject.get('read-savedObject-all-2', 'delete'), + actions.savedObject.get('read-savedObject-read-1', 'bulk_get'), + actions.savedObject.get('read-savedObject-read-1', 'get'), + actions.savedObject.get('read-savedObject-read-1', 'find'), + actions.savedObject.get('read-savedObject-read-2', 'bulk_get'), + actions.savedObject.get('read-savedObject-read-2', 'get'), + actions.savedObject.get('read-savedObject-read-2', 'find'), + actions.ui.get('foo', 'read-ui-1'), + actions.ui.get('foo', 'read-ui-2'), ]); }); }); From 670821451b400247366a48175036668c819d8297 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 5 Feb 2019 18:58:20 -0800 Subject: [PATCH 07/62] We don't need the saved object types any longer --- x-pack/plugins/security/index.js | 2 +- x-pack/plugins/security/server/lib/authorization/service.js | 4 ++-- .../security/server/lib/authorization/service.test.js | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security/index.js b/x-pack/plugins/security/index.js index 63583a41ebf73..7cf383b42a2ed 100644 --- a/x-pack/plugins/security/index.js +++ b/x-pack/plugins/security/index.js @@ -150,7 +150,7 @@ export const security = (kibana) => new kibana.Plugin({ const spaces = createOptionalPlugin(config, 'xpack.spaces', server.plugins, 'spaces'); // exposes server.plugins.security.authorization - const authorization = createAuthorizationService(server, xpackInfoFeature, savedObjects.types, xpackMainPlugin, spaces); + const authorization = createAuthorizationService(server, xpackInfoFeature, xpackMainPlugin, spaces); server.expose('authorization', deepFreeze(authorization)); watchStatusAndLicenseToInitialize(xpackMainPlugin, plugin, async (license) => { diff --git a/x-pack/plugins/security/server/lib/authorization/service.js b/x-pack/plugins/security/server/lib/authorization/service.js index d4685afdc9cb8..c426577d94fc0 100644 --- a/x-pack/plugins/security/server/lib/authorization/service.js +++ b/x-pack/plugins/security/server/lib/authorization/service.js @@ -11,7 +11,7 @@ import { checkPrivilegesWithRequestFactory } from './check_privileges'; import { checkPrivilegesDynamicallyWithRequestFactory } from './check_privileges_dynamically'; import { getClient } from '../../../../../server/lib/get_client_shield'; -export function createAuthorizationService(server, xpackInfoFeature, savedObjectTypes, xpackMainPlugin, spaces) { +export function createAuthorizationService(server, xpackInfoFeature, xpackMainPlugin, spaces) { const shieldClient = getClient(server); const config = server.config(); @@ -22,7 +22,7 @@ export function createAuthorizationService(server, xpackInfoFeature, savedObject const mode = authorizationModeFactory( xpackInfoFeature, ); - const privileges = privilegesFactory(savedObjectTypes, actions, xpackMainPlugin); + const privileges = privilegesFactory(actions, xpackMainPlugin); return { actions, diff --git a/x-pack/plugins/security/server/lib/authorization/service.test.js b/x-pack/plugins/security/server/lib/authorization/service.test.js index 38533fabae729..b03c1d9bee053 100644 --- a/x-pack/plugins/security/server/lib/authorization/service.test.js +++ b/x-pack/plugins/security/server/lib/authorization/service.test.js @@ -68,7 +68,6 @@ test(`returns exposed services`, () => { actionsFactory.mockReturnValue(mockActions); mockConfig.get.mock; const mockXpackInfoFeature = Symbol(); - const mockSavedObjectTypes = Symbol(); const mockFeatures = Symbol(); const mockXpackMainPlugin = { getFeatures: () => mockFeatures @@ -79,14 +78,14 @@ test(`returns exposed services`, () => { authorizationModeFactory.mockReturnValue(mockAuthorizationMode); const mockSpaces = Symbol(); - const authorization = createAuthorizationService(mockServer, mockXpackInfoFeature, mockSavedObjectTypes, mockXpackMainPlugin, mockSpaces); + const authorization = createAuthorizationService(mockServer, mockXpackInfoFeature, mockXpackMainPlugin, mockSpaces); const application = `kibana-${kibanaIndex}`; expect(getClient).toHaveBeenCalledWith(mockServer); expect(actionsFactory).toHaveBeenCalledWith(mockConfig); expect(checkPrivilegesWithRequestFactory).toHaveBeenCalledWith(mockActions, application, mockShieldClient); expect(checkPrivilegesDynamicallyWithRequestFactory).toHaveBeenCalledWith(mockCheckPrivilegesWithRequest, mockSpaces); - expect(privilegesFactory).toHaveBeenCalledWith(mockSavedObjectTypes, mockActions, mockXpackMainPlugin); + expect(privilegesFactory).toHaveBeenCalledWith(mockActions, mockXpackMainPlugin); expect(authorizationModeFactory).toHaveBeenCalledWith( mockXpackInfoFeature, ); From df127440938c7f6870cf88db325350f198d4ba6a Mon Sep 17 00:00:00 2001 From: kobelb Date: Wed, 6 Feb 2019 06:37:46 -0800 Subject: [PATCH 08/62] Explicitly specifying some actions that used to rely on wildcards --- .../lib/authorization/privileges/privileges.test.ts | 4 ++-- .../server/lib/authorization/privileges/privileges.ts | 8 +++++++- .../xpack_main/server/lib/register_oss_features.ts | 4 ++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts index d3ea985230575..9ae5d4c80ab59 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts @@ -292,7 +292,7 @@ describe('features', () => { all: [ actions.login, actions.version, - ...(expectManageSpaces ? ['space:manage'] : []), + ...(expectManageSpaces ? [actions.space.manage, actions.ui.get('spaces', 'manage')] : []), actions.app.get('app-1'), actions.app.get('app-2'), actions.ui.get('catalogue', 'catalogue-1'), @@ -375,7 +375,7 @@ describe('features', () => { expect(actual).toHaveProperty(`${group}.all`, [ actions.login, actions.version, - ...(expectManageSpaces ? ['space:manage'] : []), + ...(expectManageSpaces ? [actions.space.manage, actions.ui.get('spaces', 'manage')] : []), actions.ui.get('catalogue', 'bar-catalogue-1'), actions.ui.get('catalogue', 'bar-catalogue-2'), actions.ui.get('management', 'bar-management', 'bar-management-1'), diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts index b76779f630939..2f61a0949e8a3 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts @@ -80,7 +80,13 @@ export function privilegesFactory(actions: Actions, xpackMainPlugin: XPackMainPl return acc; }, {}), global: { - all: [actions.login, actions.version, actions.space.manage, ...allActions], + all: [ + actions.login, + actions.version, + actions.space.manage, + actions.ui.get('spaces', 'manage'), + ...allActions, + ], read: [actions.login, actions.version, ...readActions], }, space: { diff --git a/x-pack/plugins/xpack_main/server/lib/register_oss_features.ts b/x-pack/plugins/xpack_main/server/lib/register_oss_features.ts index 6cde11cf9a3f5..694c28ad28f7f 100644 --- a/x-pack/plugins/xpack_main/server/lib/register_oss_features.ts +++ b/x-pack/plugins/xpack_main/server/lib/register_oss_features.ts @@ -48,7 +48,7 @@ const kibanaFeatures: Feature[] = [ all: ['visualization'], read: ['config', 'index-pattern', 'search'], }, - ui: [], + ui: ['showWriteControls'], }, read: { savedObject: { @@ -196,7 +196,7 @@ const timelionFeatures: Feature[] = [ all: ['timelion-sheet'], read: ['config', 'index-pattern'], }, - ui: [], + ui: ['showWriteControls'], }, read: { savedObject: { From fb369cecbe147823263906193b6fca02abc96a86 Mon Sep 17 00:00:00 2001 From: kobelb Date: Wed, 6 Feb 2019 06:52:00 -0800 Subject: [PATCH 09/62] Fixing api integration test for privileges --- .../apis/security/privileges.ts | 646 ++++++++++-------- 1 file changed, 346 insertions(+), 300 deletions(-) diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index cab26dfb571cd..c3a2a481dcc14 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -33,6 +33,8 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'login:', `version:${version}`, 'app:kibana', + 'ui:catalogue/discover', + 'ui:navLinks/kibana:discover', 'saved_object:search/bulk_get', 'saved_object:search/get', 'saved_object:search/find', @@ -48,13 +50,13 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:index-pattern/find', 'ui:discover/show', 'ui:discover/save', - 'ui:navLinks/kibana:discover', - 'ui:catalogue/discover', ], read: [ 'login:', `version:${version}`, 'app:kibana', + 'ui:catalogue/discover', + 'ui:navLinks/kibana:discover', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', @@ -65,8 +67,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:search/get', 'saved_object:search/find', 'ui:discover/show', - 'ui:navLinks/kibana:discover', - 'ui:catalogue/discover', ], }, visualize: { @@ -74,6 +74,8 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'login:', `version:${version}`, 'app:kibana', + 'ui:catalogue/visualize', + 'ui:navLinks/kibana:visualize', 'saved_object:visualization/bulk_get', 'saved_object:visualization/get', 'saved_object:visualization/find', @@ -90,13 +92,14 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:search/bulk_get', 'saved_object:search/get', 'saved_object:search/find', - 'ui:navLinks/kibana:visualize', - 'ui:catalogue/visualize', + 'ui:visualize/showWriteControls', ], read: [ 'login:', `version:${version}`, 'app:kibana', + 'ui:catalogue/visualize', + 'ui:navLinks/kibana:visualize', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', @@ -109,8 +112,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:visualization/bulk_get', 'saved_object:visualization/get', 'saved_object:visualization/find', - 'ui:navLinks/kibana:visualize', - 'ui:catalogue/visualize', ], }, dashboard: { @@ -118,6 +119,8 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'login:', `version:${version}`, 'app:kibana', + 'ui:catalogue/dashboard', + 'ui:navLinks/kibana:dashboard', 'saved_object:dashboard/bulk_get', 'saved_object:dashboard/get', 'saved_object:dashboard/find', @@ -146,13 +149,13 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'ui:dashboard/createNew', 'ui:dashboard/show', 'ui:dashboard/showWriteControls', - 'ui:navLinks/kibana:dashboard', - 'ui:catalogue/dashboard', ], read: [ 'login:', `version:${version}`, 'app:kibana', + 'ui:catalogue/dashboard', + 'ui:navLinks/kibana:dashboard', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', @@ -175,8 +178,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:dashboard/get', 'saved_object:dashboard/find', 'ui:dashboard/show', - 'ui:navLinks/kibana:dashboard', - 'ui:catalogue/dashboard', ], }, dev_tools: { @@ -185,13 +186,13 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { `version:${version}`, 'api:console/execute', 'app:kibana', - 'saved_object:config/bulk_get', - 'saved_object:config/get', - 'saved_object:config/find', - 'ui:navLinks/kibana:dev_tools', 'ui:catalogue/console', 'ui:catalogue/searchprofiler', 'ui:catalogue/grokdebugger', + 'ui:navLinks/kibana:dev_tools', + 'saved_object:config/bulk_get', + 'saved_object:config/get', + 'saved_object:config/find', ], }, advancedSettings: { @@ -199,6 +200,8 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'login:', `version:${version}`, 'app:kibana', + 'ui:catalogue/advanced_settings', + 'ui:management/kibana/settings', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', @@ -206,18 +209,16 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:config/bulk_create', 'saved_object:config/update', 'saved_object:config/delete', - 'ui:catalogue/advanced_settings', - 'ui:management/kibana/settings', ], read: [ 'login:', `version:${version}`, 'app:kibana', + 'ui:catalogue/advanced_settings', + 'ui:management/kibana/settings', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'ui:catalogue/advanced_settings', - 'ui:management/kibana/settings', ], }, indexPatterns: { @@ -225,6 +226,8 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'login:', `version:${version}`, 'app:kibana', + 'ui:catalogue/index_patterns', + 'ui:management/kibana/indices', 'saved_object:index-pattern/bulk_get', 'saved_object:index-pattern/get', 'saved_object:index-pattern/find', @@ -235,21 +238,19 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'ui:catalogue/index_patterns', - 'ui:management/kibana/indices', ], read: [ 'login:', `version:${version}`, 'app:kibana', + 'ui:catalogue/index_patterns', + 'ui:management/kibana/indices', 'saved_object:index-pattern/bulk_get', 'saved_object:index-pattern/get', 'saved_object:index-pattern/find', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'ui:catalogue/index_patterns', - 'ui:management/kibana/indices', ], }, timelion: { @@ -258,6 +259,8 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { `version:${version}`, 'app:timelion', 'app:kibana', + 'ui:catalogue/timelion', + 'ui:navLinks/timelion', 'saved_object:timelion-sheet/bulk_get', 'saved_object:timelion-sheet/get', 'saved_object:timelion-sheet/find', @@ -271,14 +274,15 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:index-pattern/bulk_get', 'saved_object:index-pattern/get', 'saved_object:index-pattern/find', - 'ui:navLinks/timelion', - 'ui:catalogue/timelion', + 'ui:timelion/showWriteControls', ], read: [ 'login:', `version:${version}`, 'app:timelion', 'app:kibana', + 'ui:catalogue/timelion', + 'ui:navLinks/timelion', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', @@ -288,8 +292,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:timelion-sheet/bulk_get', 'saved_object:timelion-sheet/get', 'saved_object:timelion-sheet/find', - 'ui:navLinks/timelion', - 'ui:catalogue/timelion', ], }, graph: { @@ -298,6 +300,8 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { `version:${version}`, 'app:graph', 'app:kibana', + 'ui:catalogue/graph', + 'ui:navLinks/graph', 'saved_object:graph-workspace/bulk_get', 'saved_object:graph-workspace/get', 'saved_object:graph-workspace/find', @@ -311,14 +315,14 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:index-pattern/bulk_get', 'saved_object:index-pattern/get', 'saved_object:index-pattern/find', - 'ui:navLinks/graph', - 'ui:catalogue/graph', ], read: [ 'login:', `version:${version}`, 'app:graph', 'app:kibana', + 'ui:catalogue/graph', + 'ui:navLinks/graph', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', @@ -328,8 +332,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:graph-workspace/bulk_get', 'saved_object:graph-workspace/get', 'saved_object:graph-workspace/find', - 'ui:navLinks/graph', - 'ui:catalogue/graph', ], }, monitoring: { @@ -338,11 +340,11 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { `version:${version}`, 'app:monitoring', 'app:kibana', + 'ui:catalogue/monitoring', + 'ui:navLinks/monitoring', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'ui:navLinks/monitoring', - 'ui:catalogue/monitoring', ], }, ml: { @@ -351,11 +353,11 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { `version:${version}`, 'app:ml', 'app:kibana', + 'ui:catalogue/ml', + 'ui:navLinks/ml', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'ui:navLinks/ml', - 'ui:catalogue/ml', ], }, apm: { @@ -364,11 +366,11 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { `version:${version}`, 'app:apm', 'app:kibana', + 'ui:catalogue/apm', + 'ui:navLinks/apm', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'ui:navLinks/apm', - 'ui:catalogue/apm', ], }, maps: { @@ -377,6 +379,8 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { `version:${version}`, 'app:maps', 'app:kibana', + 'ui:catalogue/maps', + 'ui:navLinks/maps', 'saved_object:map/bulk_get', 'saved_object:map/get', 'saved_object:map/find', @@ -387,22 +391,20 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'ui:navLinks/maps', - 'ui:catalogue/maps', ], read: [ 'login:', `version:${version}`, 'app:maps', 'app:kibana', + 'ui:catalogue/maps', + 'ui:navLinks/maps', 'saved_object:map/bulk_get', 'saved_object:map/get', 'saved_object:map/find', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'ui:navLinks/maps', - 'ui:catalogue/maps', ], }, canvas: { @@ -411,6 +413,8 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { `version:${version}`, 'app:canvas', 'app:kibana', + 'ui:catalogue/canvas', + 'ui:navLinks/canvas', 'saved_object:canvas-workpad/bulk_get', 'saved_object:canvas-workpad/get', 'saved_object:canvas-workpad/find', @@ -424,14 +428,14 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:index-pattern/bulk_get', 'saved_object:index-pattern/get', 'saved_object:index-pattern/find', - 'ui:navLinks/canvas', - 'ui:catalogue/canvas', ], read: [ 'login:', `version:${version}`, 'app:canvas', 'app:kibana', + 'ui:catalogue/canvas', + 'ui:navLinks/canvas', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', @@ -441,8 +445,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:canvas-workpad/bulk_get', 'saved_object:canvas-workpad/get', 'saved_object:canvas-workpad/find', - 'ui:navLinks/canvas', - 'ui:catalogue/canvas', ], }, infrastructure: { @@ -451,11 +453,11 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { `version:${version}`, 'app:infra', 'app:kibana', + 'ui:catalogue/infraops', + 'ui:navLinks/infra:home', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'ui:navLinks/infra:home', - 'ui:catalogue/infraops', ], }, logs: { @@ -464,11 +466,11 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { `version:${version}`, 'app:infra', 'app:kibana', + 'ui:catalogue/infralogging', + 'ui:navLinks/infra:logs', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'ui:navLinks/infra:logs', - 'ui:catalogue/infralogging', ], }, uptime: { @@ -477,11 +479,11 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { `version:${version}`, 'app:uptime', 'app:kibana', + 'ui:catalogue/uptime', + 'ui:navLinks/uptime', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'ui:navLinks/uptime', - 'ui:catalogue/uptime', ], }, }, @@ -489,133 +491,82 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { all: [ 'login:', `version:${version}`, - 'api:*', - 'app:*', - 'saved_object:*', 'space:manage', - 'ui:*', - ], - read: [ - 'login:', - `version:${version}`, - 'api:console/execute', - 'app:*', + 'ui:spaces/manage', + 'app:kibana', + 'ui:catalogue/discover', + 'ui:navLinks/kibana:discover', + 'saved_object:search/bulk_get', + 'saved_object:search/get', + 'saved_object:search/find', + 'saved_object:search/create', + 'saved_object:search/bulk_create', + 'saved_object:search/update', + 'saved_object:search/delete', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'saved_object:migrationVersion/bulk_get', - 'saved_object:migrationVersion/get', - 'saved_object:migrationVersion/find', - 'saved_object:references/bulk_get', - 'saved_object:references/get', - 'saved_object:references/find', - 'saved_object:telemetry/bulk_get', - 'saved_object:telemetry/get', - 'saved_object:telemetry/find', - 'saved_object:graph-workspace/bulk_get', - 'saved_object:graph-workspace/get', - 'saved_object:graph-workspace/find', - 'saved_object:ml-telemetry/bulk_get', - 'saved_object:ml-telemetry/get', - 'saved_object:ml-telemetry/find', - 'saved_object:apm-telemetry/bulk_get', - 'saved_object:apm-telemetry/get', - 'saved_object:apm-telemetry/find', - 'saved_object:map/bulk_get', - 'saved_object:map/get', - 'saved_object:map/find', - 'saved_object:canvas-workpad/bulk_get', - 'saved_object:canvas-workpad/get', - 'saved_object:canvas-workpad/find', - 'saved_object:infrastructure-ui-source/bulk_get', - 'saved_object:infrastructure-ui-source/get', - 'saved_object:infrastructure-ui-source/find', - 'saved_object:upgrade-assistant-reindex-operation/bulk_get', - 'saved_object:upgrade-assistant-reindex-operation/get', - 'saved_object:upgrade-assistant-reindex-operation/find', 'saved_object:index-pattern/bulk_get', 'saved_object:index-pattern/get', 'saved_object:index-pattern/find', + 'ui:discover/show', + 'ui:discover/save', + 'ui:catalogue/visualize', + 'ui:navLinks/kibana:visualize', 'saved_object:visualization/bulk_get', 'saved_object:visualization/get', 'saved_object:visualization/find', - 'saved_object:search/bulk_get', - 'saved_object:search/get', - 'saved_object:search/find', + 'saved_object:visualization/create', + 'saved_object:visualization/bulk_create', + 'saved_object:visualization/update', + 'saved_object:visualization/delete', + 'ui:visualize/showWriteControls', + 'ui:catalogue/dashboard', + 'ui:navLinks/kibana:dashboard', 'saved_object:dashboard/bulk_get', 'saved_object:dashboard/get', 'saved_object:dashboard/find', - 'saved_object:url/bulk_get', - 'saved_object:url/get', - 'saved_object:url/find', - 'saved_object:server/bulk_get', - 'saved_object:server/get', - 'saved_object:server/find', - 'saved_object:kql-telemetry/bulk_get', - 'saved_object:kql-telemetry/get', - 'saved_object:kql-telemetry/find', + 'saved_object:dashboard/create', + 'saved_object:dashboard/bulk_create', + 'saved_object:dashboard/update', + 'saved_object:dashboard/delete', 'saved_object:timelion-sheet/bulk_get', 'saved_object:timelion-sheet/get', 'saved_object:timelion-sheet/find', - 'ui:discover/show', + 'saved_object:canvas-workpad/bulk_get', + 'saved_object:canvas-workpad/get', + 'saved_object:canvas-workpad/find', + 'ui:dashboard/createNew', 'ui:dashboard/show', - 'ui:management/kibana/settings', - 'ui:management/kibana/indices', - 'ui:catalogue/discover', - 'ui:catalogue/visualize', - 'ui:catalogue/dashboard', + 'ui:dashboard/showWriteControls', + 'api:console/execute', 'ui:catalogue/console', 'ui:catalogue/searchprofiler', 'ui:catalogue/grokdebugger', + 'ui:navLinks/kibana:dev_tools', 'ui:catalogue/advanced_settings', - 'ui:catalogue/index_patterns', - 'ui:catalogue/timelion', - 'ui:catalogue/graph', - 'ui:catalogue/monitoring', - 'ui:catalogue/ml', - 'ui:catalogue/apm', - 'ui:catalogue/maps', - 'ui:catalogue/canvas', - 'ui:catalogue/infraops', - 'ui:catalogue/infralogging', - 'ui:catalogue/uptime', - 'ui:navLinks/*', - ], - }, - space: { - all: [ - 'login:', - `version:${version}`, - 'api:*', - 'app:*', - 'saved_object:config/bulk_get', - 'saved_object:config/get', - 'saved_object:config/find', + 'ui:management/kibana/settings', 'saved_object:config/create', 'saved_object:config/bulk_create', 'saved_object:config/update', 'saved_object:config/delete', - 'saved_object:migrationVersion/bulk_get', - 'saved_object:migrationVersion/get', - 'saved_object:migrationVersion/find', - 'saved_object:migrationVersion/create', - 'saved_object:migrationVersion/bulk_create', - 'saved_object:migrationVersion/update', - 'saved_object:migrationVersion/delete', - 'saved_object:references/bulk_get', - 'saved_object:references/get', - 'saved_object:references/find', - 'saved_object:references/create', - 'saved_object:references/bulk_create', - 'saved_object:references/update', - 'saved_object:references/delete', - 'saved_object:telemetry/bulk_get', - 'saved_object:telemetry/get', - 'saved_object:telemetry/find', - 'saved_object:telemetry/create', - 'saved_object:telemetry/bulk_create', - 'saved_object:telemetry/update', - 'saved_object:telemetry/delete', + 'ui:catalogue/index_patterns', + 'ui:management/kibana/indices', + 'saved_object:index-pattern/create', + 'saved_object:index-pattern/bulk_create', + 'saved_object:index-pattern/update', + 'saved_object:index-pattern/delete', + 'app:timelion', + 'ui:catalogue/timelion', + 'ui:navLinks/timelion', + 'saved_object:timelion-sheet/create', + 'saved_object:timelion-sheet/bulk_create', + 'saved_object:timelion-sheet/update', + 'saved_object:timelion-sheet/delete', + 'ui:timelion/showWriteControls', + 'app:graph', + 'ui:catalogue/graph', + 'ui:navLinks/graph', 'saved_object:graph-workspace/bulk_get', 'saved_object:graph-workspace/get', 'saved_object:graph-workspace/find', @@ -623,20 +574,18 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:graph-workspace/bulk_create', 'saved_object:graph-workspace/update', 'saved_object:graph-workspace/delete', - 'saved_object:ml-telemetry/bulk_get', - 'saved_object:ml-telemetry/get', - 'saved_object:ml-telemetry/find', - 'saved_object:ml-telemetry/create', - 'saved_object:ml-telemetry/bulk_create', - 'saved_object:ml-telemetry/update', - 'saved_object:ml-telemetry/delete', - 'saved_object:apm-telemetry/bulk_get', - 'saved_object:apm-telemetry/get', - 'saved_object:apm-telemetry/find', - 'saved_object:apm-telemetry/create', - 'saved_object:apm-telemetry/bulk_create', - 'saved_object:apm-telemetry/update', - 'saved_object:apm-telemetry/delete', + 'app:monitoring', + 'ui:catalogue/monitoring', + 'ui:navLinks/monitoring', + 'app:ml', + 'ui:catalogue/ml', + 'ui:navLinks/ml', + 'app:apm', + 'ui:catalogue/apm', + 'ui:navLinks/apm', + 'app:maps', + 'ui:catalogue/maps', + 'ui:navLinks/maps', 'saved_object:map/bulk_get', 'saved_object:map/get', 'saved_object:map/find', @@ -644,41 +593,108 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:map/bulk_create', 'saved_object:map/update', 'saved_object:map/delete', - 'saved_object:canvas-workpad/bulk_get', - 'saved_object:canvas-workpad/get', - 'saved_object:canvas-workpad/find', + 'app:canvas', + 'ui:catalogue/canvas', + 'ui:navLinks/canvas', 'saved_object:canvas-workpad/create', 'saved_object:canvas-workpad/bulk_create', 'saved_object:canvas-workpad/update', 'saved_object:canvas-workpad/delete', - 'saved_object:infrastructure-ui-source/bulk_get', - 'saved_object:infrastructure-ui-source/get', - 'saved_object:infrastructure-ui-source/find', - 'saved_object:infrastructure-ui-source/create', - 'saved_object:infrastructure-ui-source/bulk_create', - 'saved_object:infrastructure-ui-source/update', - 'saved_object:infrastructure-ui-source/delete', - 'saved_object:upgrade-assistant-reindex-operation/bulk_get', - 'saved_object:upgrade-assistant-reindex-operation/get', - 'saved_object:upgrade-assistant-reindex-operation/find', - 'saved_object:upgrade-assistant-reindex-operation/create', - 'saved_object:upgrade-assistant-reindex-operation/bulk_create', - 'saved_object:upgrade-assistant-reindex-operation/update', - 'saved_object:upgrade-assistant-reindex-operation/delete', + 'app:infra', + 'ui:catalogue/infraops', + 'ui:navLinks/infra:home', + 'ui:catalogue/infralogging', + 'ui:navLinks/infra:logs', + 'app:uptime', + 'ui:catalogue/uptime', + 'ui:navLinks/uptime', + ], + read: [ + 'login:', + `version:${version}`, + 'app:kibana', + 'ui:catalogue/discover', + 'ui:navLinks/kibana:discover', + 'saved_object:config/bulk_get', + 'saved_object:config/get', + 'saved_object:config/find', 'saved_object:index-pattern/bulk_get', 'saved_object:index-pattern/get', 'saved_object:index-pattern/find', - 'saved_object:index-pattern/create', - 'saved_object:index-pattern/bulk_create', - 'saved_object:index-pattern/update', - 'saved_object:index-pattern/delete', + 'saved_object:search/bulk_get', + 'saved_object:search/get', + 'saved_object:search/find', + 'ui:discover/show', + 'ui:catalogue/visualize', + 'ui:navLinks/kibana:visualize', 'saved_object:visualization/bulk_get', 'saved_object:visualization/get', 'saved_object:visualization/find', - 'saved_object:visualization/create', - 'saved_object:visualization/bulk_create', - 'saved_object:visualization/update', - 'saved_object:visualization/delete', + 'ui:catalogue/dashboard', + 'ui:navLinks/kibana:dashboard', + 'saved_object:timelion-sheet/bulk_get', + 'saved_object:timelion-sheet/get', + 'saved_object:timelion-sheet/find', + 'saved_object:canvas-workpad/bulk_get', + 'saved_object:canvas-workpad/get', + 'saved_object:canvas-workpad/find', + 'saved_object:dashboard/bulk_get', + 'saved_object:dashboard/get', + 'saved_object:dashboard/find', + 'ui:dashboard/show', + 'api:console/execute', + 'ui:catalogue/console', + 'ui:catalogue/searchprofiler', + 'ui:catalogue/grokdebugger', + 'ui:navLinks/kibana:dev_tools', + 'ui:catalogue/advanced_settings', + 'ui:management/kibana/settings', + 'ui:catalogue/index_patterns', + 'ui:management/kibana/indices', + 'app:timelion', + 'ui:catalogue/timelion', + 'ui:navLinks/timelion', + 'app:graph', + 'ui:catalogue/graph', + 'ui:navLinks/graph', + 'saved_object:graph-workspace/bulk_get', + 'saved_object:graph-workspace/get', + 'saved_object:graph-workspace/find', + 'app:monitoring', + 'ui:catalogue/monitoring', + 'ui:navLinks/monitoring', + 'app:ml', + 'ui:catalogue/ml', + 'ui:navLinks/ml', + 'app:apm', + 'ui:catalogue/apm', + 'ui:navLinks/apm', + 'app:maps', + 'ui:catalogue/maps', + 'ui:navLinks/maps', + 'saved_object:map/bulk_get', + 'saved_object:map/get', + 'saved_object:map/find', + 'app:canvas', + 'ui:catalogue/canvas', + 'ui:navLinks/canvas', + 'app:infra', + 'ui:catalogue/infraops', + 'ui:navLinks/infra:home', + 'ui:catalogue/infralogging', + 'ui:navLinks/infra:logs', + 'app:uptime', + 'ui:catalogue/uptime', + 'ui:navLinks/uptime', + ], + }, + space: { + all: [ + 'login:', + `version:${version}`, + 'app:kibana', + 'ui:catalogue/discover', + 'ui:navLinks/kibana:discover', 'saved_object:search/bulk_get', 'saved_object:search/get', 'saved_object:search/find', @@ -686,6 +702,26 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:search/bulk_create', 'saved_object:search/update', 'saved_object:search/delete', + 'saved_object:config/bulk_get', + 'saved_object:config/get', + 'saved_object:config/find', + 'saved_object:index-pattern/bulk_get', + 'saved_object:index-pattern/get', + 'saved_object:index-pattern/find', + 'ui:discover/show', + 'ui:discover/save', + 'ui:catalogue/visualize', + 'ui:navLinks/kibana:visualize', + 'saved_object:visualization/bulk_get', + 'saved_object:visualization/get', + 'saved_object:visualization/find', + 'saved_object:visualization/create', + 'saved_object:visualization/bulk_create', + 'saved_object:visualization/update', + 'saved_object:visualization/delete', + 'ui:visualize/showWriteControls', + 'ui:catalogue/dashboard', + 'ui:navLinks/kibana:dashboard', 'saved_object:dashboard/bulk_get', 'saved_object:dashboard/get', 'saved_object:dashboard/find', @@ -693,156 +729,166 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:dashboard/bulk_create', 'saved_object:dashboard/update', 'saved_object:dashboard/delete', - 'saved_object:url/bulk_get', - 'saved_object:url/get', - 'saved_object:url/find', - 'saved_object:url/create', - 'saved_object:url/bulk_create', - 'saved_object:url/update', - 'saved_object:url/delete', - 'saved_object:server/bulk_get', - 'saved_object:server/get', - 'saved_object:server/find', - 'saved_object:server/create', - 'saved_object:server/bulk_create', - 'saved_object:server/update', - 'saved_object:server/delete', - 'saved_object:kql-telemetry/bulk_get', - 'saved_object:kql-telemetry/get', - 'saved_object:kql-telemetry/find', - 'saved_object:kql-telemetry/create', - 'saved_object:kql-telemetry/bulk_create', - 'saved_object:kql-telemetry/update', - 'saved_object:kql-telemetry/delete', 'saved_object:timelion-sheet/bulk_get', 'saved_object:timelion-sheet/get', 'saved_object:timelion-sheet/find', + 'saved_object:canvas-workpad/bulk_get', + 'saved_object:canvas-workpad/get', + 'saved_object:canvas-workpad/find', + 'ui:dashboard/createNew', + 'ui:dashboard/show', + 'ui:dashboard/showWriteControls', + 'api:console/execute', + 'ui:catalogue/console', + 'ui:catalogue/searchprofiler', + 'ui:catalogue/grokdebugger', + 'ui:navLinks/kibana:dev_tools', + 'ui:catalogue/advanced_settings', + 'ui:management/kibana/settings', + 'saved_object:config/create', + 'saved_object:config/bulk_create', + 'saved_object:config/update', + 'saved_object:config/delete', + 'ui:catalogue/index_patterns', + 'ui:management/kibana/indices', + 'saved_object:index-pattern/create', + 'saved_object:index-pattern/bulk_create', + 'saved_object:index-pattern/update', + 'saved_object:index-pattern/delete', + 'app:timelion', + 'ui:catalogue/timelion', + 'ui:navLinks/timelion', 'saved_object:timelion-sheet/create', 'saved_object:timelion-sheet/bulk_create', 'saved_object:timelion-sheet/update', 'saved_object:timelion-sheet/delete', - 'ui:*', + 'ui:timelion/showWriteControls', + 'app:graph', + 'ui:catalogue/graph', + 'ui:navLinks/graph', + 'saved_object:graph-workspace/bulk_get', + 'saved_object:graph-workspace/get', + 'saved_object:graph-workspace/find', + 'saved_object:graph-workspace/create', + 'saved_object:graph-workspace/bulk_create', + 'saved_object:graph-workspace/update', + 'saved_object:graph-workspace/delete', + 'app:monitoring', + 'ui:catalogue/monitoring', + 'ui:navLinks/monitoring', + 'app:ml', + 'ui:catalogue/ml', + 'ui:navLinks/ml', + 'app:apm', + 'ui:catalogue/apm', + 'ui:navLinks/apm', + 'app:maps', + 'ui:catalogue/maps', + 'ui:navLinks/maps', + 'saved_object:map/bulk_get', + 'saved_object:map/get', + 'saved_object:map/find', + 'saved_object:map/create', + 'saved_object:map/bulk_create', + 'saved_object:map/update', + 'saved_object:map/delete', + 'app:canvas', + 'ui:catalogue/canvas', + 'ui:navLinks/canvas', + 'saved_object:canvas-workpad/create', + 'saved_object:canvas-workpad/bulk_create', + 'saved_object:canvas-workpad/update', + 'saved_object:canvas-workpad/delete', + 'app:infra', + 'ui:catalogue/infraops', + 'ui:navLinks/infra:home', + 'ui:catalogue/infralogging', + 'ui:navLinks/infra:logs', + 'app:uptime', + 'ui:catalogue/uptime', + 'ui:navLinks/uptime', ], read: [ 'login:', `version:${version}`, - 'api:console/execute', - 'app:*', + 'app:kibana', + 'ui:catalogue/discover', + 'ui:navLinks/kibana:discover', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'saved_object:migrationVersion/bulk_get', - 'saved_object:migrationVersion/get', - 'saved_object:migrationVersion/find', - 'saved_object:references/bulk_get', - 'saved_object:references/get', - 'saved_object:references/find', - 'saved_object:telemetry/bulk_get', - 'saved_object:telemetry/get', - 'saved_object:telemetry/find', - 'saved_object:graph-workspace/bulk_get', - 'saved_object:graph-workspace/get', - 'saved_object:graph-workspace/find', - 'saved_object:ml-telemetry/bulk_get', - 'saved_object:ml-telemetry/get', - 'saved_object:ml-telemetry/find', - 'saved_object:apm-telemetry/bulk_get', - 'saved_object:apm-telemetry/get', - 'saved_object:apm-telemetry/find', - 'saved_object:map/bulk_get', - 'saved_object:map/get', - 'saved_object:map/find', - 'saved_object:canvas-workpad/bulk_get', - 'saved_object:canvas-workpad/get', - 'saved_object:canvas-workpad/find', - 'saved_object:infrastructure-ui-source/bulk_get', - 'saved_object:infrastructure-ui-source/get', - 'saved_object:infrastructure-ui-source/find', - 'saved_object:upgrade-assistant-reindex-operation/bulk_get', - 'saved_object:upgrade-assistant-reindex-operation/get', - 'saved_object:upgrade-assistant-reindex-operation/find', 'saved_object:index-pattern/bulk_get', 'saved_object:index-pattern/get', 'saved_object:index-pattern/find', - 'saved_object:visualization/bulk_get', - 'saved_object:visualization/get', - 'saved_object:visualization/find', 'saved_object:search/bulk_get', 'saved_object:search/get', 'saved_object:search/find', - 'saved_object:dashboard/bulk_get', - 'saved_object:dashboard/get', - 'saved_object:dashboard/find', - 'saved_object:url/bulk_get', - 'saved_object:url/get', - 'saved_object:url/find', - 'saved_object:server/bulk_get', - 'saved_object:server/get', - 'saved_object:server/find', - 'saved_object:kql-telemetry/bulk_get', - 'saved_object:kql-telemetry/get', - 'saved_object:kql-telemetry/find', + 'ui:discover/show', + 'ui:catalogue/visualize', + 'ui:navLinks/kibana:visualize', + 'saved_object:visualization/bulk_get', + 'saved_object:visualization/get', + 'saved_object:visualization/find', + 'ui:catalogue/dashboard', + 'ui:navLinks/kibana:dashboard', 'saved_object:timelion-sheet/bulk_get', 'saved_object:timelion-sheet/get', 'saved_object:timelion-sheet/find', - 'ui:discover/show', + 'saved_object:canvas-workpad/bulk_get', + 'saved_object:canvas-workpad/get', + 'saved_object:canvas-workpad/find', + 'saved_object:dashboard/bulk_get', + 'saved_object:dashboard/get', + 'saved_object:dashboard/find', 'ui:dashboard/show', - 'ui:management/kibana/settings', - 'ui:management/kibana/indices', - 'ui:catalogue/discover', - 'ui:catalogue/visualize', - 'ui:catalogue/dashboard', + 'api:console/execute', 'ui:catalogue/console', 'ui:catalogue/searchprofiler', 'ui:catalogue/grokdebugger', + 'ui:navLinks/kibana:dev_tools', 'ui:catalogue/advanced_settings', + 'ui:management/kibana/settings', 'ui:catalogue/index_patterns', + 'ui:management/kibana/indices', + 'app:timelion', 'ui:catalogue/timelion', + 'ui:navLinks/timelion', + 'app:graph', 'ui:catalogue/graph', + 'ui:navLinks/graph', + 'saved_object:graph-workspace/bulk_get', + 'saved_object:graph-workspace/get', + 'saved_object:graph-workspace/find', + 'app:monitoring', 'ui:catalogue/monitoring', + 'ui:navLinks/monitoring', + 'app:ml', 'ui:catalogue/ml', + 'ui:navLinks/ml', + 'app:apm', 'ui:catalogue/apm', + 'ui:navLinks/apm', + 'app:maps', 'ui:catalogue/maps', + 'ui:navLinks/maps', + 'saved_object:map/bulk_get', + 'saved_object:map/get', + 'saved_object:map/find', + 'app:canvas', 'ui:catalogue/canvas', + 'ui:navLinks/canvas', + 'app:infra', 'ui:catalogue/infraops', + 'ui:navLinks/infra:home', 'ui:catalogue/infralogging', + 'ui:navLinks/infra:logs', + 'app:uptime', 'ui:catalogue/uptime', - 'ui:navLinks/*', + 'ui:navLinks/uptime', ], }, }); }); }); - - describe('GET /api/security/privileges', () => { - it('should return a privilege map with all known privileges, without actions', async () => { - await supertest - .get('/api/security/privileges') - .set('kbn-xsrf', 'xxx') - .send() - .expect(200, { - features: { - discover: ['all', 'read'], - visualize: ['all', 'read'], - dashboard: ['all', 'read'], - dev_tools: ['read'], - advancedSettings: ['all', 'read'], - indexPatterns: ['all', 'read'], - timelion: ['all', 'read'], - graph: ['all', 'read'], - monitoring: ['all'], - ml: ['all'], - apm: ['all'], - maps: ['all', 'read'], - canvas: ['all', 'read'], - infrastructure: ['read'], - logs: ['read'], - uptime: ['read'], - }, - global: ['all', 'read'], - space: ['all', 'read'], - }); - }); - }); }); } From 951ceb35315b596e3c6b0b08aad6c6055b1822fa Mon Sep 17 00:00:00 2001 From: kobelb Date: Wed, 6 Feb 2019 09:47:13 -0800 Subject: [PATCH 10/62] Test fixture plugin which adds the globaltype now specifies a feature --- .../namespace_agnostic_type_plugin/index.js | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/namespace_agnostic_type_plugin/index.js b/x-pack/test/saved_object_api_integration/common/fixtures/namespace_agnostic_type_plugin/index.js index 3fdbb6b9a2509..8980bc565a2d3 100644 --- a/x-pack/test/saved_object_api_integration/common/fixtures/namespace_agnostic_type_plugin/index.js +++ b/x-pack/test/saved_object_api_integration/common/fixtures/namespace_agnostic_type_plugin/index.js @@ -8,7 +8,7 @@ import mappings from './mappings.json'; export default function (kibana) { return new kibana.Plugin({ - require: [], + require: ['kibana', 'elasticsearch', 'xpack_main'], name: 'namespace_agnostic_type_plugin', uiExports: { savedObjectSchemas: { @@ -20,5 +20,31 @@ export default function (kibana) { }, config() {}, + + init(server) { + server.plugins.xpack_main.registerFeature({ + id: 'namespace_agnostic_type_plugin', + name: 'namespace_agnostic_type_plugin', + icon: 'upArrow', + navLinkId: 'namespace_agnostic_type_plugin', + app: [], + privileges: { + all: { + savedObject: { + all: ['globaltype'], + read: [], + }, + ui: [], + }, + read: { + savedObject: { + all: [], + read: ['globaltype'], + }, + ui: [], + } + } + }); + } }); } From 92e7e0bbf4cacc76c8b4218c46808ec742d3a626 Mon Sep 17 00:00:00 2001 From: kobelb Date: Wed, 6 Feb 2019 10:15:12 -0800 Subject: [PATCH 11/62] Unauthorized to find unknown types now --- .../security_and_spaces/apis/find.ts | 26 ++++++------ .../security_only/apis/find.ts | 40 +++++++++---------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts index 06c538271e25e..1344d58312f2e 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts @@ -108,7 +108,7 @@ export default function({ getService }: TestInvoker) { response: expectNotSpaceAwareResults, }, unknownType: { - description: 'empty result', + description: 'emptry result', statusCode: 200, response: createExpectEmpty(1, 20, 0), }, @@ -182,9 +182,9 @@ export default function({ getService }: TestInvoker) { response: expectNotSpaceAwareResults, }, unknownType: { - description: 'empty result', - statusCode: 200, - response: createExpectEmpty(1, 20, 0), + description: 'forbidden find wigwags message', + statusCode: 403, + response: createExpectRbacForbidden('wigwags'), }, pageBeyondTotal: { description: 'empty result', @@ -192,9 +192,9 @@ export default function({ getService }: TestInvoker) { response: createExpectEmpty(100, 100, 1), }, unknownSearchField: { - description: 'empty result', - statusCode: 200, - response: createExpectEmpty(1, 20, 0), + description: 'forbiddden find wigwags message', + statusCode: 403, + response: createExpectRbacForbidden('wigwags'), }, noType: { description: 'bad request, type is required', @@ -256,9 +256,9 @@ export default function({ getService }: TestInvoker) { response: expectNotSpaceAwareResults, }, unknownType: { - description: 'empty result', - statusCode: 200, - response: createExpectEmpty(1, 20, 0), + description: 'forbiddden find wigwags message', + statusCode: 403, + response: createExpectRbacForbidden('wigwags'), }, pageBeyondTotal: { description: 'empty result', @@ -266,9 +266,9 @@ export default function({ getService }: TestInvoker) { response: createExpectEmpty(100, 100, 1), }, unknownSearchField: { - description: 'empty result', - statusCode: 200, - response: createExpectEmpty(1, 20, 0), + description: 'forbiddden find wigwags message', + statusCode: 403, + response: createExpectRbacForbidden('wigwags'), }, noType: { description: 'bad request, type is required', diff --git a/x-pack/test/saved_object_api_integration/security_only/apis/find.ts b/x-pack/test/saved_object_api_integration/security_only/apis/find.ts index 0e7e414fd97d7..cd969ba9199f2 100644 --- a/x-pack/test/saved_object_api_integration/security_only/apis/find.ts +++ b/x-pack/test/saved_object_api_integration/security_only/apis/find.ts @@ -145,9 +145,9 @@ export default function({ getService }: TestInvoker) { response: expectNotSpaceAwareResults, }, unknownType: { - description: 'empty result', - statusCode: 200, - response: createExpectEmpty(1, 20, 0), + description: 'forbidden find wigwags message', + statusCode: 403, + response: createExpectRbacForbidden('wigwags'), }, pageBeyondTotal: { description: 'empty result', @@ -155,9 +155,9 @@ export default function({ getService }: TestInvoker) { response: createExpectEmpty(100, 100, 1), }, unknownSearchField: { - description: 'empty result', - statusCode: 200, - response: createExpectEmpty(1, 20, 0), + description: 'forbidden find wigwags message', + statusCode: 403, + response: createExpectRbacForbidden('wigwags'), }, noType: { description: 'bad request, type is required', @@ -217,9 +217,9 @@ export default function({ getService }: TestInvoker) { response: expectNotSpaceAwareResults, }, unknownType: { - description: 'empty result', - statusCode: 200, - response: createExpectEmpty(1, 20, 0), + description: 'forbidden find wigwags message', + statusCode: 403, + response: createExpectRbacForbidden('wigwags'), }, pageBeyondTotal: { description: 'empty result', @@ -227,9 +227,9 @@ export default function({ getService }: TestInvoker) { response: createExpectEmpty(100, 100, 1), }, unknownSearchField: { - description: 'empty result', - statusCode: 200, - response: createExpectEmpty(1, 20, 0), + description: 'forbidden find wigwags message', + statusCode: 403, + response: createExpectRbacForbidden('wigwags'), }, noType: { description: 'bad request, type is required', @@ -289,7 +289,7 @@ export default function({ getService }: TestInvoker) { response: createExpectRbacForbidden('globaltype'), }, unknownType: { - description: 'empty result', + description: 'forbidden find wigwags message', statusCode: 403, response: createExpectRbacForbidden('wigwags'), }, @@ -299,7 +299,7 @@ export default function({ getService }: TestInvoker) { response: createExpectRbacForbidden('visualization'), }, unknownSearchField: { - description: 'empty result', + description: 'forbidden find wigwags message', statusCode: 403, response: createExpectRbacForbidden('wigwags'), }, @@ -325,7 +325,7 @@ export default function({ getService }: TestInvoker) { response: createExpectRbacForbidden('globaltype'), }, unknownType: { - description: 'empty result', + description: 'forbidden find wigwags message', statusCode: 403, response: createExpectRbacForbidden('wigwags'), }, @@ -335,7 +335,7 @@ export default function({ getService }: TestInvoker) { response: createExpectRbacForbidden('visualization'), }, unknownSearchField: { - description: 'empty result', + description: 'forbidden find wigwags message', statusCode: 403, response: createExpectRbacForbidden('wigwags'), }, @@ -361,7 +361,7 @@ export default function({ getService }: TestInvoker) { response: createExpectRbacForbidden('globaltype'), }, unknownType: { - description: 'empty result', + description: 'forbidden find wigwags message', statusCode: 403, response: createExpectRbacForbidden('wigwags'), }, @@ -371,7 +371,7 @@ export default function({ getService }: TestInvoker) { response: createExpectRbacForbidden('visualization'), }, unknownSearchField: { - description: 'empty result', + description: 'forbidden find wigwags message', statusCode: 403, response: createExpectRbacForbidden('wigwags'), }, @@ -397,7 +397,7 @@ export default function({ getService }: TestInvoker) { response: createExpectRbacForbidden('globaltype'), }, unknownType: { - description: 'empty result', + description: 'forbidden find wigwags message', statusCode: 403, response: createExpectRbacForbidden('wigwags'), }, @@ -407,7 +407,7 @@ export default function({ getService }: TestInvoker) { response: createExpectRbacForbidden('visualization'), }, unknownSearchField: { - description: 'empty result', + description: 'forbidden find wigwags message', statusCode: 403, response: createExpectRbacForbidden('wigwags'), }, From a78bfe7f7362c5243d36410be6a9b21c4f5aed5c Mon Sep 17 00:00:00 2001 From: kobelb Date: Wed, 6 Feb 2019 14:23:53 -0800 Subject: [PATCH 12/62] Adding reserved privileges tests --- .../privileges/privileges.test.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts index 9ae5d4c80ab59..eba823cbca933 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts @@ -629,3 +629,37 @@ describe('features', () => { }); }); }); + +describe('reserved', () => { + test(`are hard-coded and not based on features`, () => { + const features: Feature[] = []; + + const mockXPackMainPlugin = { + getFeatures: jest.fn().mockReturnValue(features), + }; + + const privileges = privilegesFactory(actions, mockXPackMainPlugin); + + const actual = privileges.get(); + expect(actual).toHaveProperty('reserved', { + apm: [ + actions.version, + actions.app.get('apm'), + ...actions.savedObject.readOperations('config'), + actions.ui.get('navLinks', 'apm'), + ], + ml: [ + actions.version, + actions.app.get('ml'), + ...actions.savedObject.readOperations('config'), + actions.ui.get('navLinks', 'ml'), + ], + monitoring: [ + actions.version, + actions.app.get('monitoring'), + ...actions.savedObject.readOperations('config'), + actions.ui.get('navLinks', 'monitoring'), + ], + }); + }); +}); From 392819b43ef0ff9c00de7193ecfdd2f732ec790f Mon Sep 17 00:00:00 2001 From: kobelb Date: Wed, 6 Feb 2019 15:37:43 -0800 Subject: [PATCH 13/62] Adding reserved privileges in a designated reserved bucket --- .../common/model/raw_kibana_privileges.ts | 1 + .../privilege_serializer.test.ts | 7 + .../lib/authorization/privilege_serializer.ts | 9 + .../server/lib/authorization/privileges.ts | 102 ------- .../privileges_serializer.test.ts | 36 +++ .../authorization/privileges_serializer.ts | 13 + .../register_privileges_with_cluster.test.js | 289 +++++++++++++++++- .../routes/api/public/privileges/get.test.ts | 5 + .../routes/api/public/privileges/get.ts | 1 + .../server/routes/api/public/roles/get.js | 5 +- 10 files changed, 359 insertions(+), 109 deletions(-) delete mode 100644 x-pack/plugins/security/server/lib/authorization/privileges.ts diff --git a/x-pack/plugins/security/common/model/raw_kibana_privileges.ts b/x-pack/plugins/security/common/model/raw_kibana_privileges.ts index bdef00e8f7c21..1b1584a4ce58c 100644 --- a/x-pack/plugins/security/common/model/raw_kibana_privileges.ts +++ b/x-pack/plugins/security/common/model/raw_kibana_privileges.ts @@ -14,4 +14,5 @@ export interface RawKibanaPrivileges { global: Record; features: RawKibanaFeaturePrivileges; space: Record; + reserved: Record; } diff --git a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts index 47177b3ed5b60..35d6663cc70b5 100644 --- a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts @@ -79,6 +79,13 @@ describe('#serializeFeaturePrivilege', () => { }); }); +describe('#serializeReservedPrivilege', () => { + test('returns `reserved_${privilegeName}`', () => { + const result = PrivilegeSerializer.serializeReservedPrivilege('foo'); + expect(result).toBe('reserved_foo'); + }); +}); + describe('#deserializeFeaturePrivilege', () => { [ { diff --git a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts index 1ddba2747d9cd..3b1df598ea1cd 100644 --- a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts +++ b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts @@ -6,6 +6,7 @@ const featurePrefix = 'feature_'; const spacePrefix = 'space_'; +const reservedPrefix = 'reserved_'; const basePrivilegeNames = ['all', 'read']; const globalBasePrivileges = [...basePrivilegeNames, 'apm', 'ml', 'monitoring']; const spaceBasePrivileges = basePrivilegeNames.map( @@ -29,6 +30,10 @@ export class PrivilegeSerializer { return spaceBasePrivileges.includes(privilegeName); } + public static isSerializedReservedPrivilege(privilegeName: string) { + return privilegeName.startsWith(reservedPrefix); + } + public static serializeGlobalBasePrivilege(privilegeName: string) { if (!globalBasePrivileges.includes(privilegeName)) { throw new Error('Unrecognized global base privilege'); @@ -49,6 +54,10 @@ export class PrivilegeSerializer { return `${featurePrefix}${featureId}.${privilegeName}`; } + public static serializeReservedPrivilege(privilegeName: string) { + return `${reservedPrefix}${privilegeName}`; + } + public static deserializeFeaturePrivilege(privilege: string): FeaturePrivilege { const match = privilege.match(deserializeFeaturePrivilegeRegexp); if (!match) { diff --git a/x-pack/plugins/security/server/lib/authorization/privileges.ts b/x-pack/plugins/security/server/lib/authorization/privileges.ts deleted file mode 100644 index 6494e1e4a7699..0000000000000 --- a/x-pack/plugins/security/server/lib/authorization/privileges.ts +++ /dev/null @@ -1,102 +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 { RawKibanaPrivileges } from 'x-pack/plugins/security/common/model'; -import { Feature } from '../../../../xpack_main/types'; -import { IGNORED_TYPES } from '../../../common/constants'; -import { Actions } from './actions'; -import { FeaturesPrivilegesBuilder } from './features_privileges_builder'; - -export interface PrivilegesService { - get(): RawKibanaPrivileges; -} - -interface XPackMainPlugin { - getFeatures(): Feature[]; -} - -export function privilegesFactory( - allSavedObjectTypes: string[], - actions: Actions, - xpackMainPlugin: XPackMainPlugin -) { - return { - get() { - // TODO: I'd like to ensure an explicit Error is thrown here if all - // plugins haven't had a chance to register their features yet - const features = xpackMainPlugin.getFeatures(); - const validSavedObjectTypes = allSavedObjectTypes.filter( - type => !IGNORED_TYPES.includes(type) - ); - const featuresPrivilegesBuilder = new FeaturesPrivilegesBuilder(actions); - - return { - features: featuresPrivilegesBuilder.buildFeaturesPrivileges(features), - global: { - all: [ - actions.login, - actions.version, - actions.api.all, - actions.app.all, - actions.savedObject.all, - actions.space.manage, - actions.ui.all, - ], - read: [ - actions.login, - actions.version, - ...featuresPrivilegesBuilder.getApiReadActions(features), - actions.app.all, - ...actions.savedObject.readOperations(validSavedObjectTypes), - ...featuresPrivilegesBuilder.getUIFeaturesReadActions(features), - ...featuresPrivilegesBuilder.getUIManagementReadActions(features), - ...featuresPrivilegesBuilder.getUICatalogueReadActions(features), - actions.ui.allNavLinks, - ], - apm: [ - actions.version, - actions.app.get('apm'), - ...actions.savedObject.readOperations('config'), - actions.ui.get('navLinks', 'apm'), - ], - ml: [ - actions.version, - actions.app.get('ml'), - ...actions.savedObject.readOperations('config'), - actions.ui.get('navLinks', 'ml'), - ], - monitoring: [ - actions.version, - actions.app.get('monitoring'), - ...actions.savedObject.readOperations('config'), - actions.ui.get('navLinks', 'monitoring'), - ], - }, - space: { - all: [ - actions.login, - actions.version, - actions.api.all, - actions.app.all, - ...actions.savedObject.allOperations(validSavedObjectTypes), - actions.ui.all, - ], - read: [ - actions.login, - actions.version, - ...featuresPrivilegesBuilder.getApiReadActions(features), - actions.app.all, - ...actions.savedObject.readOperations(validSavedObjectTypes), - ...featuresPrivilegesBuilder.getUIFeaturesReadActions(features), - ...featuresPrivilegesBuilder.getUIManagementReadActions(features), - ...featuresPrivilegesBuilder.getUICatalogueReadActions(features), - actions.ui.allNavLinks, - ], - }, - }; - }, - }; -} diff --git a/x-pack/plugins/security/server/lib/authorization/privileges_serializer.test.ts b/x-pack/plugins/security/server/lib/authorization/privileges_serializer.test.ts index 664467088adcb..4413c37ebcf09 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges_serializer.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges_serializer.test.ts @@ -11,6 +11,7 @@ test(`uses application as top-level key`, () => { global: {}, space: {}, features: {}, + reserved: {}, }); expect(Object.keys(result)).toEqual(['foo-application']); }); @@ -25,6 +26,7 @@ describe('global', () => { }, space: {}, features: {}, + reserved: {}, }); expect(result[application]).toEqual({ all: { @@ -51,6 +53,7 @@ describe('global', () => { }, space: {}, features: {}, + reserved: {}, }); }).toThrowErrorMatchingSnapshot(); }); @@ -66,6 +69,7 @@ describe('space', () => { read: ['action-3', 'action-4'], }, features: {}, + reserved: {}, }); expect(result[application]).toEqual({ space_all: { @@ -92,6 +96,7 @@ describe('space', () => { foo: ['action-1', 'action-2'], }, features: {}, + reserved: {}, }); }).toThrowErrorMatchingSnapshot(); }); @@ -113,6 +118,7 @@ describe('features', () => { qux: ['action-3', 'action-4'], }, }, + reserved: {}, }); expect(result[application]).toEqual({ 'feature_foo.quz': { @@ -155,6 +161,36 @@ describe('features', () => { bar_baz: ['action-1', 'action-2'], }, }, + reserved: {}, + }); + }); +}); + +describe('reserved', () => { + test(`includes reserved privileges with a reserved_ prefix`, () => { + const application = 'foo-application'; + const result = serializePrivileges(application, { + global: {}, + space: {}, + features: {}, + reserved: { + foo: ['action-1', 'action-2'], + bar: ['action-3', 'action-4'], + }, + }); + expect(result[application]).toEqual({ + reserved_foo: { + application, + name: 'reserved_foo', + actions: ['action-1', 'action-2'], + metadata: {}, + }, + reserved_bar: { + application, + name: 'reserved_bar', + actions: ['action-3', 'action-4'], + metadata: {}, + }, }); }); }); diff --git a/x-pack/plugins/security/server/lib/authorization/privileges_serializer.ts b/x-pack/plugins/security/server/lib/authorization/privileges_serializer.ts index c8246aaabf860..d24279e72f15d 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges_serializer.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges_serializer.ts @@ -73,6 +73,19 @@ export const serializePrivileges = ( }, {} as Record ), + ...Object.entries(privilegeMap.reserved).reduce( + (acc, [privilegeName, privilegeActions]) => { + const name = PrivilegeSerializer.serializeReservedPrivilege(privilegeName); + acc[name] = { + application, + name, + actions: privilegeActions, + metadata: {}, + }; + return acc; + }, + {} as Record + ), }, }; }; diff --git a/x-pack/plugins/security/server/lib/authorization/register_privileges_with_cluster.test.js b/x-pack/plugins/security/server/lib/authorization/register_privileges_with_cluster.test.js index e47c928030638..23a7a0f0d01ab 100644 --- a/x-pack/plugins/security/server/lib/authorization/register_privileges_with_cluster.test.js +++ b/x-pack/plugins/security/server/lib/authorization/register_privileges_with_cluster.test.js @@ -221,6 +221,9 @@ registerPrivilegesWithClusterTest(`inserts privileges when we don't have any exi bar: { read: ['action:bar_read'], } + }, + reserved: { + customApplication: ['action:customApplication'] } }, existingPrivileges: null, @@ -250,6 +253,12 @@ registerPrivilegesWithClusterTest(`inserts privileges when we don't have any exi name: 'feature_bar.read', actions: ['action:bar_read'], metadata: {}, + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, } } }); @@ -264,7 +273,8 @@ registerPrivilegesWithClusterTest(`deletes no-longer specified privileges`, { }, space: { read: ['action:bar'] - } + }, + reserved: {}, }, existingPrivileges: { [application]: { @@ -291,6 +301,12 @@ registerPrivilegesWithClusterTest(`deletes no-longer specified privileges`, { name: 'space_baz', actions: ['action:not-baz'], metadata: {}, + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, } } }, @@ -310,7 +326,7 @@ registerPrivilegesWithClusterTest(`deletes no-longer specified privileges`, { metadata: {}, } } - }, ['read', 'space_baz']); + }, ['read', 'space_baz', 'reserved_customApplication']); } }); @@ -330,6 +346,9 @@ registerPrivilegesWithClusterTest(`updates privileges when global actions don't bar: { read: ['action:quz'] } + }, + reserved: { + customApplication: ['action:customApplication'] } }, existingPrivileges: { @@ -355,6 +374,12 @@ registerPrivilegesWithClusterTest(`updates privileges when global actions don't application, name: 'feature_bar.read', actions: ['action:not-quz'], + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, } } }, @@ -384,6 +409,12 @@ registerPrivilegesWithClusterTest(`updates privileges when global actions don't name: 'feature_bar.read', actions: ['action:quz'], metadata: {}, + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, } } }); @@ -406,6 +437,9 @@ registerPrivilegesWithClusterTest(`updates privileges when space actions don't m bar: { read: ['action:quz'] } + }, + reserved: { + customApplication: ['action:customApplication'] } }, existingPrivileges: { @@ -431,6 +465,12 @@ registerPrivilegesWithClusterTest(`updates privileges when space actions don't m application, name: 'feature_bar.read', actions: ['action:quz'], + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, } } }, @@ -460,6 +500,12 @@ registerPrivilegesWithClusterTest(`updates privileges when space actions don't m name: 'feature_bar.read', actions: ['action:quz'], metadata: {}, + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, } } }); @@ -482,6 +528,9 @@ registerPrivilegesWithClusterTest(`updates privileges when feature actions don't bar: { read: ['action:quz'] } + }, + reserved: { + customApplication: ['action:customApplication'] } }, existingPrivileges: { @@ -507,6 +556,12 @@ registerPrivilegesWithClusterTest(`updates privileges when feature actions don't application, name: 'feature_bar.read', actions: ['action:not-quz'], + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, } } }, @@ -536,15 +591,97 @@ registerPrivilegesWithClusterTest(`updates privileges when feature actions don't name: 'feature_bar.read', actions: ['action:quz'], metadata: {}, + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, } } }); } }); -registerPrivilegesWithClusterTest(`updates privileges when global privilege added`, { +registerPrivilegesWithClusterTest(`updates privileges when reserved actions don't match`, { privilegeMap: { features: {}, + global: { + all: ['action:foo'] + }, + space: { + read: ['action:bar'] + }, + features: { + foo: { + all: ['action:baz'] + } + }, + reserved: { + customApplication: ['action:customApplication'] + } + }, + existingPrivileges: { + [application]: { + all: { + application, + name: 'all', + actions: ['action:foo'], + metadata: {}, + }, + space_read: { + application, + name: 'space_read', + actions: ['action:bar'], + metadata: {}, + }, + 'feature_foo.all': { + application, + name: 'feature_foo.all', + actions: ['action:baz'], + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:not-customApplication'], + metadata: {}, + } + } + }, + assert: ({ expectUpdatedPrivileges }) => { + expectUpdatedPrivileges({ + [application]: { + all: { + application, + name: 'all', + actions: ['action:foo'], + metadata: {}, + }, + space_read: { + application, + name: 'space_read', + actions: ['action:bar'], + metadata: {}, + }, + 'feature_foo.all': { + application, + name: 'feature_foo.all', + actions: ['action:baz'], + metadata: {}, + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, + } + } + }); + } +}); + +registerPrivilegesWithClusterTest(`updates privileges when global privilege added`, { + privilegeMap: { global: { all: ['action:foo'], read: ['action:quz'] @@ -556,6 +693,9 @@ registerPrivilegesWithClusterTest(`updates privileges when global privilege adde foo: { all: ['action:foo-all'] } + }, + reserved: { + customApplication: ['action:customApplication'] } }, existingPrivileges: { @@ -577,6 +717,12 @@ registerPrivilegesWithClusterTest(`updates privileges when global privilege adde name: 'feature_foo.all', actions: ['action:foo-all'], metadata: {}, + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, } } }, @@ -606,6 +752,12 @@ registerPrivilegesWithClusterTest(`updates privileges when global privilege adde name: 'feature_foo.all', actions: ['action:foo-all'], metadata: {}, + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, } } }); @@ -614,7 +766,6 @@ registerPrivilegesWithClusterTest(`updates privileges when global privilege adde registerPrivilegesWithClusterTest(`updates privileges when space privilege added`, { privilegeMap: { - features: {}, global: { all: ['action:foo'], }, @@ -626,6 +777,9 @@ registerPrivilegesWithClusterTest(`updates privileges when space privilege added foo: { all: ['action:foo-all'] } + }, + reserved: { + customApplication: ['action:customApplication'] } }, existingPrivileges: { @@ -647,6 +801,12 @@ registerPrivilegesWithClusterTest(`updates privileges when space privilege added name: 'feature_foo.all', actions: ['action:foo-all'], metadata: {}, + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, } } }, @@ -676,6 +836,12 @@ registerPrivilegesWithClusterTest(`updates privileges when space privilege added name: 'feature_foo.all', actions: ['action:foo-all'], metadata: {}, + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, } } }); @@ -696,6 +862,9 @@ registerPrivilegesWithClusterTest(`updates privileges when feature privilege add all: ['action:foo-all'], read: ['action:foo-read'] } + }, + reserved: { + customApplication: ['action:customApplication'] } }, existingPrivileges: { @@ -717,6 +886,12 @@ registerPrivilegesWithClusterTest(`updates privileges when feature privilege add name: 'feature_foo.all', actions: ['action:foo-all'], metadata: {}, + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, } } }, @@ -746,6 +921,97 @@ registerPrivilegesWithClusterTest(`updates privileges when feature privilege add name: 'feature_foo.read', actions: ['action:foo-read'], metadata: {}, + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication'], + metadata: {}, + } + } + }); + } +}); + +registerPrivilegesWithClusterTest(`updates privileges when reserved privilege added`, { + privilegeMap: { + features: {}, + global: { + all: ['action:foo'], + }, + space: { + all: ['action:bar'], + }, + features: { + foo: { + all: ['action:foo-all'], + } + }, + reserved: { + customApplication1: ['action:customApplication1'], + customApplication2: ['action:customApplication2'] + } + }, + existingPrivileges: { + [application]: { + all: { + application, + name: 'foo', + actions: ['action:not-foo'], + metadata: {}, + }, + space_all: { + application, + name: 'space_all', + actions: ['action:not-bar'], + metadata: {}, + }, + 'feature_foo.all': { + application, + name: 'feature_foo.all', + actions: ['action:foo-all'], + metadata: {}, + }, + reserved_customApplication1: { + application, + name: 'reserved_customApplication1', + actions: ['action:customApplication1'], + metadata: {}, + } + } + }, + assert: ({ expectUpdatedPrivileges }) => { + expectUpdatedPrivileges({ + [application]: { + all: { + application, + name: 'all', + actions: ['action:foo'], + metadata: {}, + }, + space_all: { + application, + name: 'space_all', + actions: ['action:bar'], + metadata: {}, + }, + 'feature_foo.all': { + application, + name: 'feature_foo.all', + actions: ['action:foo-all'], + metadata: {}, + }, + reserved_customApplication1: { + application, + name: 'reserved_customApplication1', + actions: ['action:customApplication1'], + metadata: {}, + }, + reserved_customApplication2: { + application, + name: 'reserved_customApplication2', + actions: ['action:customApplication2'], + metadata: {}, } } }); @@ -764,6 +1030,9 @@ registerPrivilegesWithClusterTest(`doesn't update privileges when order of actio foo: { all: ['action:foo-all', 'action:bar-all'] } + }, + reserved: { + customApplication: ['action:customApplication1', 'action:customApplication2'] } }, existingPrivileges: { @@ -785,6 +1054,12 @@ registerPrivilegesWithClusterTest(`doesn't update privileges when order of actio name: 'feature_foo.all', actions: ['action:bar-all', 'action:foo-all'], metadata: {}, + }, + reserved_customApplication: { + application, + name: 'reserved_customApplication', + actions: ['action:customApplication2', 'action:customApplication1'], + metadata: {}, } } }, @@ -797,7 +1072,8 @@ registerPrivilegesWithClusterTest(`throws and logs error when errors getting pri privilegeMap: { features: {}, global: {}, - space: {} + space: {}, + reserved: {}, }, throwErrorWhenGettingPrivileges: new Error('Error getting privileges'), assert: ({ expectErrorThrown }) => { @@ -813,7 +1089,8 @@ registerPrivilegesWithClusterTest(`throws and logs error when errors putting pri }, space: { read: [] - } + }, + reserved: {}, }, existingPrivileges: null, throwErrorWhenPuttingPrivileges: new Error('Error putting privileges'), diff --git a/x-pack/plugins/security/server/routes/api/public/privileges/get.test.ts b/x-pack/plugins/security/server/routes/api/public/privileges/get.test.ts index db956eaa6bb0c..e83116365a0fe 100644 --- a/x-pack/plugins/security/server/routes/api/public/privileges/get.test.ts +++ b/x-pack/plugins/security/server/routes/api/public/privileges/get.test.ts @@ -40,6 +40,10 @@ const createRawKibanaPrivileges: () => RawKibanaPrivileges = () => { all: ['*'], read: ['something:/read'], }, + reserved: { + customApplication1: ['custom-action1'], + customApplication2: ['custom-action2'], + }, }; }; @@ -130,6 +134,7 @@ describe('GET privileges', () => { feature1: ['all'], feature2: ['all'], }, + reserved: ['customApplication1', 'customApplication2'], }, }, }); diff --git a/x-pack/plugins/security/server/routes/api/public/privileges/get.ts b/x-pack/plugins/security/server/routes/api/public/privileges/get.ts index 9a96b2f9774a1..9bba0910a5b15 100644 --- a/x-pack/plugins/security/server/routes/api/public/privileges/get.ts +++ b/x-pack/plugins/security/server/routes/api/public/privileges/get.ts @@ -33,6 +33,7 @@ export function initGetPrivilegesApi( }, {} ), + reserved: Object.keys(privileges.reserved), }; }, config: { diff --git a/x-pack/plugins/security/server/routes/api/public/roles/get.js b/x-pack/plugins/security/server/routes/api/public/roles/get.js index 7f3045550ae74..a77fe4d785726 100644 --- a/x-pack/plugins/security/server/routes/api/public/roles/get.js +++ b/x-pack/plugins/security/server/routes/api/public/roles/get.js @@ -43,7 +43,10 @@ export function initGetRolesApi(server, callWithRequest, routePreCheckLicenseFn, // if we're dealing with a global entry, which we've ensured above is only possible if it's the only item in the array if (resources.length === 1 && resources[0] === GLOBAL_RESOURCE) { const basePrivileges = privileges.filter(privilege => PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege)); - const featurePrivileges = privileges.filter(privilege => !PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege)); + const featurePrivileges = privileges.filter(privilege => + !PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege) && + !PrivilegeSerializer.isSerializedReservedPrivilege(privilege) + ); return { base: basePrivileges.map(privilege => PrivilegeSerializer.serializeGlobalBasePrivilege(privilege)), From c0477aec837b06cbc52e2808377a293c765fb67c Mon Sep 17 00:00:00 2001 From: kobelb Date: Wed, 6 Feb 2019 16:28:06 -0800 Subject: [PATCH 14/62] Fixing ui capability tests --- .../security_and_spaces/tests/nav_links.ts | 8 +- .../security_only/scenarios.ts | 90 +++++++++---------- .../security_only/tests/nav_links.ts | 35 ++++---- 3 files changed, 66 insertions(+), 67 deletions(-) diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts index 1da2e17ccf57a..2bce9c392cb03 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts @@ -28,6 +28,10 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul ); switch (scenario.id) { case 'superuser at everything_space': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('navLinks'); + expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.all()); + break; case 'global_all at everything_space': case 'dual_privileges_all at everything_space': case 'dual_privileges_read at everything_space': @@ -36,7 +40,9 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul case 'everything_space_read at everything_space': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('navLinks'); - expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.all()); + expect(uiCapabilities.value!.navLinks).to.eql( + navLinksBuilder.except('apm', 'ml', 'monitoring') + ); break; case 'superuser at nothing_space': case 'global_all at nothing_space': diff --git a/x-pack/test/ui_capabilities/security_only/scenarios.ts b/x-pack/test/ui_capabilities/security_only/scenarios.ts index 4d3dfe8c8e233..abc25c6222065 100644 --- a/x-pack/test/ui_capabilities/security_only/scenarios.ts +++ b/x-pack/test/ui_capabilities/security_only/scenarios.ts @@ -132,23 +132,15 @@ const All: All = { }, }; -interface ApmAll extends User { - username: 'apm_all'; +interface ApmUser extends User { + username: 'apm_user'; } -const ApmAll: ApmAll = { - username: 'apm_all', - fullName: 'apm_all', - password: 'apm_all-password', +const ApmUser: ApmUser = { + username: 'apm_user', + fullName: 'apm_user', + password: 'apm_user-password', role: { - name: 'apm_all_role', - kibana: [ - { - feature: { - apm: ['all'], - }, - spaces: ['*'], - }, - ], + name: 'apm_user', }, }; @@ -412,43 +404,39 @@ const LogsRead: LogsRead = { }, }; -interface MlAll extends User { - username: 'ml_all'; +interface MachineLearningAdmin extends User { + username: 'machine_learning_admin'; } -const MlAll: MlAll = { - username: 'ml_all', - fullName: 'ml_all', - password: 'ml_all-password', +const MachineLearningAdmin: MachineLearningAdmin = { + username: 'machine_learning_admin', + fullName: 'machine_learning_admin', + password: 'machine_learning_admin-password', role: { - name: 'ml_all_role', - kibana: [ - { - feature: { - ml: ['all'], - }, - spaces: ['*'], - }, - ], + name: 'machine_learning_admin', }, }; -interface MonitoringAll extends User { - username: 'monitoring_all'; +interface MachineLearningUser extends User { + username: 'machine_learning_user'; } -const MonitoringAll: MonitoringAll = { - username: 'monitoring_all', - fullName: 'monitoring_all', - password: 'monitoring_all-password', +const MachineLearningUser: MachineLearningUser = { + username: 'machine_learning_user', + fullName: 'machine_learning_user', + password: 'machine_learning_user-password', role: { - name: 'monitoring_all_role', - kibana: [ - { - feature: { - monitoring: ['all'], - }, - spaces: ['*'], - }, - ], + name: 'machine_learning_user', + }, +}; + +interface MonitoringUser extends User { + username: 'monitoring_user'; +} +const MonitoringAll: MonitoringUser = { + username: 'monitoring_user', + fullName: 'monitoring_user', + password: 'monitoring_user-password', + role: { + name: 'monitoring_user_role', }, }; @@ -559,7 +547,7 @@ export type UserScenarios = | DualPrivilegesAll | DualPrivilegesRead | All - | ApmAll + | ApmUser | CanvasAll | CanvasRead | DashboardAll @@ -573,8 +561,9 @@ export type UserScenarios = | MapsRead | InfrastructureRead | LogsRead - | MonitoringAll - | MlAll + | MonitoringUser + | MachineLearningAdmin + | MachineLearningUser | TimelionAll | TimelionRead | UptimeRead @@ -587,7 +576,7 @@ export const UserScenarios: UserScenarios[] = [ DualPrivilegesAll, DualPrivilegesRead, All, - ApmAll, + ApmUser, CanvasAll, CanvasRead, DashboardAll, @@ -602,7 +591,8 @@ export const UserScenarios: UserScenarios[] = [ InfrastructureRead, LogsRead, MonitoringAll, - MlAll, + MachineLearningAdmin, + MachineLearningUser, TimelionAll, TimelionRead, UptimeRead, diff --git a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts index 9e78f0574aebd..658bca6c9b5aa 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts @@ -17,7 +17,7 @@ import { UserScenarios } from '../scenarios'; export default function navLinksTests({ getService }: KibanaFunctionalTestDefaultProviders) { const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities'); - describe('navLinks', () => { + describe.only('navLinks', () => { UserScenarios.forEach(scenario => { it(`${scenario.fullName}`, async () => { const uiCapabilities = await uiCapabilitiesService.get({ @@ -26,20 +26,23 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul }); switch (scenario.username) { case 'superuser': - case 'all': - case 'dual_privileges_all': - case 'dual_privileges_read': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('navLinks'); expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.all()); break; - case 'apm_all': + case 'all': + case 'dual_privileges_all': + case 'dual_privileges_read': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('navLinks'); expect(uiCapabilities.value!.navLinks).to.eql( - navLinksBuilder.only('apm', 'management') + navLinksBuilder.except('apm', 'ml', 'monitoring') ); break; + case 'apm_user': + expect(uiCapabilities.success).to.be(false); + expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); + break; case 'canvas_all': case 'canvas_read': expect(uiCapabilities.success).to.be(true); @@ -102,17 +105,17 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul navLinksBuilder.only('logs', 'management') ); break; - case 'ml_all': - expect(uiCapabilities.success).to.be(true); - expect(uiCapabilities.value).to.have.property('navLinks'); - expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.only('ml', 'management')); + case 'machine_learning_admin': + expect(uiCapabilities.success).to.be(false); + expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; - case 'monitoring_all': - expect(uiCapabilities.success).to.be(true); - expect(uiCapabilities.value).to.have.property('navLinks'); - expect(uiCapabilities.value!.navLinks).to.eql( - navLinksBuilder.only('monitoring', 'management') - ); + case 'machine_learning_user': + expect(uiCapabilities.success).to.be(false); + expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); + break; + case 'monitoring_user': + expect(uiCapabilities.success).to.be(false); + expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; case 'timelion_all': case 'timelion_read': From 8649157182390a658e9d4e1e6d979745f6d0301b Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 7 Feb 2019 10:11:25 -0800 Subject: [PATCH 15/62] Adding spaces api tests for apm/ml/monitoring users --- .../common/lib/authentication.ts | 16 ++++++ .../common/lib/create_users_and_roles.ts | 40 ++++++++++++++ .../security_and_spaces/apis/get_all.ts | 52 +++++++++++++++++++ 3 files changed, 108 insertions(+) diff --git a/x-pack/test/spaces_api_integration/common/lib/authentication.ts b/x-pack/test/spaces_api_integration/common/lib/authentication.ts index 04ffc291dd602..b901a7ed9516c 100644 --- a/x-pack/test/spaces_api_integration/common/lib/authentication.ts +++ b/x-pack/test/spaces_api_integration/common/lib/authentication.ts @@ -65,4 +65,20 @@ export const AUTHENTICATION = { username: 'a_kibana_rbac_space_1_2_read_user', password: 'password', }, + APM_USER: { + username: 'a_apm_user', + password: 'password', + }, + MACHINE_LEARING_ADMIN: { + username: 'a_machine_learning_admin', + password: 'password', + }, + MACHINE_LEARNING_USER: { + username: 'a_machine_learning_user', + password: 'password', + }, + MONITORING_USER: { + username: 'a_monitoring_user', + password: 'password', + }, }; diff --git a/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts b/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts index b7b70a7f0a70e..e480548c9493f 100644 --- a/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts +++ b/x-pack/test/spaces_api_integration/common/lib/create_users_and_roles.ts @@ -320,4 +320,44 @@ export const createUsersAndRoles = async (es: any, supertest: SuperTest) => email: 'a_kibana_rbac_space_1_2_readonly_user@elastic.co', }, }); + + await es.shield.putUser({ + username: AUTHENTICATION.APM_USER.username, + body: { + password: AUTHENTICATION.APM_USER.password, + roles: ['apm_user'], + full_name: 'a apm user', + email: 'a_apm_user@elastic.co', + }, + }); + + await es.shield.putUser({ + username: AUTHENTICATION.MACHINE_LEARING_ADMIN.username, + body: { + password: AUTHENTICATION.MACHINE_LEARING_ADMIN.password, + roles: ['machine_learning_admin'], + full_name: 'a machine learning admin', + email: 'a_machine_learning_admin@elastic.co', + }, + }); + + await es.shield.putUser({ + username: AUTHENTICATION.MACHINE_LEARNING_USER.username, + body: { + password: AUTHENTICATION.MACHINE_LEARNING_USER.password, + roles: ['machine_learning_user'], + full_name: 'a machine learning user', + email: 'a_machine_learning_user@elastic.co', + }, + }); + + await es.shield.putUser({ + username: AUTHENTICATION.MONITORING_USER.username, + body: { + password: AUTHENTICATION.MONITORING_USER.password, + roles: ['monitoring_user'], + full_name: 'a monitoring user', + email: 'a_monitoring_user@elastic.co', + }, + }); }; diff --git a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts index 48bdcfd74367c..076fd82c8477e 100644 --- a/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts +++ b/x-pack/test/spaces_api_integration/security_and_spaces/apis/get_all.ts @@ -35,6 +35,10 @@ export default function getAllSpacesTestSuite({ getService }: TestInvoker) { legacyAll: AUTHENTICATION.KIBANA_LEGACY_USER, dualAll: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_USER, dualRead: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_DASHBOARD_ONLY_USER, + apmUser: AUTHENTICATION.APM_USER, + machineLearningAdmin: AUTHENTICATION.MACHINE_LEARING_ADMIN, + machineLearningUser: AUTHENTICATION.MACHINE_LEARNING_USER, + monitoringUser: AUTHENTICATION.MONITORING_USER, }, }, { @@ -51,6 +55,10 @@ export default function getAllSpacesTestSuite({ getService }: TestInvoker) { legacyAll: AUTHENTICATION.KIBANA_LEGACY_USER, dualAll: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_USER, dualRead: AUTHENTICATION.KIBANA_DUAL_PRIVILEGES_DASHBOARD_ONLY_USER, + apmUser: AUTHENTICATION.APM_USER, + machineLearningAdmin: AUTHENTICATION.MACHINE_LEARING_ADMIN, + machineLearningUser: AUTHENTICATION.MACHINE_LEARNING_USER, + monitoringUser: AUTHENTICATION.MONITORING_USER, }, }, ].forEach(scenario => { @@ -180,6 +188,50 @@ export default function getAllSpacesTestSuite({ getService }: TestInvoker) { }, } ); + + getAllTest(`apm_user can't access any spaces from ${scenario.spaceId}`, { + spaceId: scenario.spaceId, + user: scenario.users.apmUser, + tests: { + exists: { + statusCode: 403, + response: expectRbacForbidden, + }, + }, + }); + + getAllTest(`machine_learning_admin can't access any spaces from ${scenario.spaceId}`, { + spaceId: scenario.spaceId, + user: scenario.users.machineLearningAdmin, + tests: { + exists: { + statusCode: 403, + response: expectRbacForbidden, + }, + }, + }); + + getAllTest(`machine_learning_user can't access any spaces from ${scenario.spaceId}`, { + spaceId: scenario.spaceId, + user: scenario.users.machineLearningUser, + tests: { + exists: { + statusCode: 403, + response: expectRbacForbidden, + }, + }, + }); + + getAllTest(`monitoring_user can't access any spaces from ${scenario.spaceId}`, { + spaceId: scenario.spaceId, + user: scenario.users.monitoringUser, + tests: { + exists: { + statusCode: 403, + response: expectRbacForbidden, + }, + }, + }); }); }); } From 132e7f498842bfd7f9356021ae08b703073719b7 Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 7 Feb 2019 11:27:44 -0800 Subject: [PATCH 16/62] Adding more roles to the security only ui capability tests --- x-pack/test/common/services/security/role.ts | 6 +- x-pack/test/ui_capabilities/common/types.ts | 3 +- .../security_and_spaces/tests/index.ts | 24 +++-- .../security_only/scenarios.ts | 96 ++++++++++++++++--- .../security_only/tests/dashboard.ts | 11 ++- .../security_only/tests/discover.ts | 13 ++- .../security_only/tests/index.ts | 23 +++-- .../security_only/tests/nav_links.ts | 42 ++++---- 8 files changed, 163 insertions(+), 55 deletions(-) diff --git a/x-pack/test/common/services/security/role.ts b/x-pack/test/common/services/security/role.ts index fc7526d22be39..e96f85c702337 100644 --- a/x-pack/test/common/services/security/role.ts +++ b/x-pack/test/common/services/security/role.ts @@ -35,9 +35,11 @@ export class Role { public async delete(name: string) { this.log.debug(`deleting role ${name}`); const { data, status, statusText } = await this.axios.delete(`/api/security/role/${name}`); - if (status !== 204) { + if (status !== 204 && status !== 404) { throw new Error( - `Expected status code of 204, received ${status} ${statusText}: ${util.inspect(data)}` + `Expected status code of 204 or 404, received ${status} ${statusText}: ${util.inspect( + data + )}` ); } this.log.debug(`deleted role ${name}`); diff --git a/x-pack/test/ui_capabilities/common/types.ts b/x-pack/test/ui_capabilities/common/types.ts index 130db7d635500..6012188162f98 100644 --- a/x-pack/test/ui_capabilities/common/types.ts +++ b/x-pack/test/ui_capabilities/common/types.ts @@ -51,7 +51,8 @@ export interface User { username: string; fullName: string; password: string; - role: ReservedRoleSpecification | CustomRoleSpecification; + role?: ReservedRoleSpecification | CustomRoleSpecification; + roles?: Array; } export interface Space { diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts index 2cc018f62be76..a953dd6137eb7 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/index.ts @@ -27,16 +27,20 @@ export default function uiCapabilitesTests({ } for (const user of Users) { + const roles = [...(user.role ? [user.role] : []), ...(user.roles ? user.roles : [])]; + await securityService.user.create(user.username, { password: user.password, full_name: user.fullName, - roles: [user.role.name], + roles: roles.map(role => role.name), }); - if (isCustomRoleSpecification(user.role)) { - await securityService.role.create(user.role.name, { - elasticsearch: user.role.elasticsearch, - kibana: user.role.kibana, - }); + + for (const role of roles) { + if (isCustomRoleSpecification(role)) { + await securityService.role.create(role.name, { + kibana: role.kibana, + }); + } } } }); @@ -48,8 +52,12 @@ export default function uiCapabilitesTests({ for (const user of Users) { await securityService.user.delete(user.username); - if (isCustomRoleSpecification(user.role)) { - await securityService.role.delete(user.role.name); + + const roles = [...(user.role ? [user.role] : []), ...(user.roles ? user.roles : [])]; + for (const role of roles) { + if (isCustomRoleSpecification(role)) { + await securityService.role.delete(role.name); + } } } }); diff --git a/x-pack/test/ui_capabilities/security_only/scenarios.ts b/x-pack/test/ui_capabilities/security_only/scenarios.ts index abc25c6222065..1ab73c5a66017 100644 --- a/x-pack/test/ui_capabilities/security_only/scenarios.ts +++ b/x-pack/test/ui_capabilities/security_only/scenarios.ts @@ -4,12 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { User } from '../common/types'; +import { CustomRoleSpecification, User } from '../common/types'; // For all scenarios, we define both an instance in addition // to a "type" definition so that we can use the exhaustive switch in // typescript to ensure all scenarios are handled. +const allRole: CustomRoleSpecification = { + name: 'all_role', + kibana: [ + { + base: ['all'], + spaces: ['*'], + }, + ], +}; + interface NoKibanaPrivileges extends User { username: 'no_kibana_privileges'; } @@ -121,15 +131,7 @@ const All: All = { username: 'all', fullName: 'all', password: 'all-password', - role: { - name: 'all_role', - kibana: [ - { - base: ['all'], - spaces: ['*'], - }, - ], - }, + role: allRole, }; interface ApmUser extends User { @@ -144,6 +146,21 @@ const ApmUser: ApmUser = { }, }; +interface ApmUserAndAll extends User { + username: 'apm_user_and_all'; +} +const ApmUserAndAll: ApmUserAndAll = { + username: 'apm_user_and_all', + fullName: 'apm_user_and_all', + password: 'apm_user_and_all-password', + roles: [ + { + name: 'apm_user', + }, + allRole, + ], +}; + interface CanvasAll extends User { username: 'canvas_all'; } @@ -416,6 +433,21 @@ const MachineLearningAdmin: MachineLearningAdmin = { }, }; +interface MachineLearningAdminAndAll extends User { + username: 'machine_learning_admin_and_all'; +} +const MachineLearningAdminAndAll: MachineLearningAdminAndAll = { + username: 'machine_learning_admin_and_all', + fullName: 'machine_learning_admin_and_all', + password: 'machine_learning_admin_and_all-password', + roles: [ + { + name: 'machine_learning_admin', + }, + allRole, + ], +}; + interface MachineLearningUser extends User { username: 'machine_learning_user'; } @@ -428,10 +460,25 @@ const MachineLearningUser: MachineLearningUser = { }, }; +interface MachineLearningUserAndAll extends User { + username: 'machine_learning_user_and_all'; +} +const MachineLearningUserAndAll: MachineLearningUserAndAll = { + username: 'machine_learning_user_and_all', + fullName: 'machine_learning_user_and_all', + password: 'machine_learning_user_and_all-password', + roles: [ + { + name: 'machine_learning_user', + }, + allRole, + ], +}; + interface MonitoringUser extends User { username: 'monitoring_user'; } -const MonitoringAll: MonitoringUser = { +const MonitoringUser: MonitoringUser = { username: 'monitoring_user', fullName: 'monitoring_user', password: 'monitoring_user-password', @@ -440,6 +487,21 @@ const MonitoringAll: MonitoringUser = { }, }; +interface MonitoringUserAndAll extends User { + username: 'monitoring_user_and_all'; +} +const MonitoringUserAndAll: MonitoringUserAndAll = { + username: 'monitoring_user_and_all', + fullName: 'monitoring_user_and_all', + password: 'monitoring_user_and_all-password', + roles: [ + { + name: 'monitoring_user', + }, + allRole, + ], +}; + interface TimelionAll extends User { username: 'timelion_all'; } @@ -548,6 +610,7 @@ export type UserScenarios = | DualPrivilegesRead | All | ApmUser + | ApmUserAndAll | CanvasAll | CanvasRead | DashboardAll @@ -561,9 +624,12 @@ export type UserScenarios = | MapsRead | InfrastructureRead | LogsRead - | MonitoringUser | MachineLearningAdmin + | MachineLearningAdminAndAll | MachineLearningUser + | MachineLearningUserAndAll + | MonitoringUser + | MonitoringUserAndAll | TimelionAll | TimelionRead | UptimeRead @@ -577,6 +643,7 @@ export const UserScenarios: UserScenarios[] = [ DualPrivilegesRead, All, ApmUser, + ApmUserAndAll, CanvasAll, CanvasRead, DashboardAll, @@ -590,9 +657,12 @@ export const UserScenarios: UserScenarios[] = [ MapsRead, InfrastructureRead, LogsRead, - MonitoringAll, MachineLearningAdmin, + MachineLearningAdminAndAll, MachineLearningUser, + MachineLearningUserAndAll, + MonitoringUser, + MonitoringUserAndAll, TimelionAll, TimelionRead, UptimeRead, diff --git a/x-pack/test/ui_capabilities/security_only/tests/dashboard.ts b/x-pack/test/ui_capabilities/security_only/tests/dashboard.ts index cbbfedc0481dc..9752c6aeb5a97 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/dashboard.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/dashboard.ts @@ -27,8 +27,12 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul // these users have a read/write view of Dashboard case 'superuser': case 'all': + case 'apm_user_and_all': case 'dual_privileges_all': case 'dashboard_all': + case 'machine_learning_admin_and_all': + case 'machine_learning_user_and_all': + case 'monitoring_user_and_all': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('dashboard'); expect(uiCapabilities.value!.dashboard).to.eql({ @@ -49,7 +53,6 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul }); break; // these users can't do anything with Dashboard - case 'apm_all': case 'canvas_all': case 'canvas_read': case 'dev_tools_read': @@ -61,8 +64,6 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul case 'maps_read': case 'infrastructure_read': case 'logs_read': - case 'ml_all': - case 'monitoring_all': case 'timelion_all': case 'timelion_read': case 'uptime_read': @@ -76,7 +77,11 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul showWriteControls: false, }); break; + case 'apm_user': case 'legacy_all': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': case 'no_kibana_privileges': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); diff --git a/x-pack/test/ui_capabilities/security_only/tests/discover.ts b/x-pack/test/ui_capabilities/security_only/tests/discover.ts index 7195d980c5d59..083bd05a6b1f5 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/discover.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/discover.ts @@ -31,8 +31,12 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul // these users have a read/write view of Discover case 'superuser': case 'all': + case 'apm_user_and_all': case 'dual_privileges_all': case 'discover_all': + case 'machine_learning_admin_and_all': + case 'machine_learning_user_and_all': + case 'monitoring_user_and_all': expect(uiCapabilities.success).to.be(true); expect(capabilities).to.have.property('discover'); expect(capabilities!.discover).to.eql({ @@ -53,7 +57,6 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul expect(capabilities.catalogue.discover).to.eql(true); break; // these users can't do anything with Discover - case 'apm_all': case 'canvas_all': case 'canvas_read': case 'dashboard_all': @@ -65,8 +68,6 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul case 'maps_read': case 'infrastructure_read': case 'logs_read': - case 'ml_all': - case 'monitoring_all': case 'timelion_all': case 'timelion_read': case 'uptime_read': @@ -80,8 +81,12 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul }); expect(capabilities.catalogue.discover).to.eql(false); break; - case 'no_kibana_privileges': + case 'apm_user': case 'legacy_all': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': + case 'no_kibana_privileges': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; diff --git a/x-pack/test/ui_capabilities/security_only/tests/index.ts b/x-pack/test/ui_capabilities/security_only/tests/index.ts index 5b067e1e49461..fb801905549c3 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/index.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/index.ts @@ -21,15 +21,20 @@ export default function uiCapabilitesTests({ before(async () => { for (const user of UserScenarios) { + const roles = [...(user.role ? [user.role] : []), ...(user.roles ? user.roles : [])]; + await securityService.user.create(user.username, { password: user.password, full_name: user.fullName, - roles: [user.role.name], + roles: roles.map(role => role.name), }); - if (isCustomRoleSpecification(user.role)) { - await securityService.role.create(user.role.name, { - kibana: user.role.kibana, - }); + + for (const role of roles) { + if (isCustomRoleSpecification(role)) { + await securityService.role.create(role.name, { + kibana: role.kibana, + }); + } } } }); @@ -37,8 +42,12 @@ export default function uiCapabilitesTests({ after(async () => { for (const user of UserScenarios) { await securityService.user.delete(user.username); - if (isCustomRoleSpecification(user.role)) { - await securityService.role.delete(user.role.name); + + const roles = [...(user.role ? [user.role] : []), ...(user.roles ? user.roles : [])]; + for (const role of roles) { + if (isCustomRoleSpecification(role)) { + await securityService.role.delete(role.name); + } } } }); diff --git a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts index 658bca6c9b5aa..96efc9669670a 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts @@ -17,7 +17,7 @@ import { UserScenarios } from '../scenarios'; export default function navLinksTests({ getService }: KibanaFunctionalTestDefaultProviders) { const uiCapabilitiesService: UICapabilitiesService = getService('uiCapabilities'); - describe.only('navLinks', () => { + describe('navLinks', () => { UserScenarios.forEach(scenario => { it(`${scenario.fullName}`, async () => { const uiCapabilities = await uiCapabilitiesService.get({ @@ -39,9 +39,12 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul navLinksBuilder.except('apm', 'ml', 'monitoring') ); break; - case 'apm_user': - expect(uiCapabilities.success).to.be(false); - expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); + case 'apm_user_and_all': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('navLinks'); + expect(uiCapabilities.value!.navLinks).to.eql( + navLinksBuilder.except('ml', 'monitoring') + ); break; case 'canvas_all': case 'canvas_read': @@ -83,6 +86,14 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul navLinksBuilder.only('graph', 'management') ); break; + case 'machine_learning_admin_and_all': + case 'machine_learning_user_and_all': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('navLinks'); + expect(uiCapabilities.value!.navLinks).to.eql( + navLinksBuilder.except('apm', 'monitoring') + ); + break; case 'maps_all': case 'maps_read': expect(uiCapabilities.success).to.be(true); @@ -91,6 +102,11 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul navLinksBuilder.only('maps', 'management') ); break; + case 'monitoring_user_and_all': + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('navLinks'); + expect(uiCapabilities.value!.navLinks).to.eql(navLinksBuilder.except('apm', 'ml')); + break; case 'infrastructure_read': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('navLinks'); @@ -105,18 +121,6 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul navLinksBuilder.only('logs', 'management') ); break; - case 'machine_learning_admin': - expect(uiCapabilities.success).to.be(false); - expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); - break; - case 'machine_learning_user': - expect(uiCapabilities.success).to.be(false); - expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); - break; - case 'monitoring_user': - expect(uiCapabilities.success).to.be(false); - expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); - break; case 'timelion_all': case 'timelion_read': expect(uiCapabilities.success).to.be(true); @@ -140,8 +144,12 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul navLinksBuilder.only('visualize', 'management') ); break; - case 'no_kibana_privileges': + case 'apm_user': case 'legacy_all': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': + case 'no_kibana_privileges': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; From 2912e7b8c4debf615779a351d1bff8f7ad78b776 Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 7 Feb 2019 14:09:29 -0800 Subject: [PATCH 17/62] You can put a role with reserved privileges using the API --- .../server/routes/api/public/roles/put.js | 12 +++- .../routes/api/public/roles/put.test.js | 61 ++++++++++++++++++- .../api_integration/apis/security/roles.js | 38 +++++++++++- 3 files changed, 105 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security/server/routes/api/public/roles/put.js b/x-pack/plugins/security/server/routes/api/public/roles/put.js index 3c99b8b9b191b..1075bc6b5fb3a 100644 --- a/x-pack/plugins/security/server/routes/api/public/roles/put.js +++ b/x-pack/plugins/security/server/routes/api/public/roles/put.js @@ -19,7 +19,7 @@ export function initPutRolesApi( ) { const transformKibanaPrivilegesToEs = (kibanaPrivileges = []) => { - return kibanaPrivileges.map(({ base, feature, spaces }) => { + return kibanaPrivileges.map(({ base, feature, spaces, _reserved }) => { if (spaces.length === 1 && spaces[0] === GLOBAL_RESOURCE) { return { privileges: [ @@ -33,6 +33,9 @@ export function initPutRolesApi( ) ) ) : [], + ..._reserved ? _reserved.map( + privilege => PrivilegeSerializer.serializeReservedPrivilege(privilege) + ) : [] ], application, resources: [GLOBAL_RESOURCE] @@ -93,7 +96,12 @@ export function initPutRolesApi( spaces: Joi.alternatives( allSpacesSchema, Joi.array().items(Joi.string().regex(/^[a-z0-9_-]+$/)), - ).default([GLOBAL_RESOURCE]) + ).default([GLOBAL_RESOURCE]), + _reserved: Joi.alternatives().when('spaces', { + is: allSpacesSchema, + then: Joi.array().items(Joi.string().valid(Object.keys(privileges.reserved))), + otherwise: Joi.any().forbidden(), + }) }) ).unique((a, b) => { return intersection(a.spaces, b.spaces).length !== 0; diff --git a/x-pack/plugins/security/server/routes/api/public/roles/put.test.js b/x-pack/plugins/security/server/routes/api/public/roles/put.test.js index 597f9a4b59163..5efbd856a0d34 100644 --- a/x-pack/plugins/security/server/routes/api/public/roles/put.test.js +++ b/x-pack/plugins/security/server/routes/api/public/roles/put.test.js @@ -45,6 +45,10 @@ const privilegeMap = { 'bar-privilege-1': [], 'bar-privilege-2': [], } + }, + reserved: { + customApplication1: [], + customApplication2: [], } }; @@ -220,6 +224,31 @@ describe('PUT role', () => { }, }); + putRoleTest(`only allows known Kibana reserved privileges`, { + name: 'foo-role', + payload: { + kibana: [ + { + _reserved: ['customApplication3'], + spaces: ['*'] + } + ] + }, + asserts: { + statusCode: 400, + result: { + error: 'Bad Request', + //eslint-disable-next-line max-len + message: `child \"kibana\" fails because [\"kibana\" at position 0 fails because [child \"_reserved\" fails because [\"_reserved\" at position 0 fails because [\"0\" must be one of [customApplication1, customApplication2]]]]]`, + statusCode: 400, + validation: { + keys: ['kibana.0._reserved.0'], + source: 'payload', + }, + }, + }, + }); + putRoleTest(`only allows one global entry`, { name: 'foo-role', payload: { @@ -361,6 +390,31 @@ describe('PUT role', () => { }, }, }); + + putRoleTest(`can't assign _reserved privilege at a space`, { + name: 'foo-role', + payload: { + kibana: [ + { + _reserved: ['customApplication1'], + spaces: ['marketing'] + }, + ] + }, + asserts: { + statusCode: 400, + result: { + error: 'Bad Request', + //eslint-disable-next-line max-len + message: `child \"kibana\" fails because [\"kibana\" at position 0 fails because [child \"_reserved\" fails because [\"_reserved\" is not allowed]]]`, + statusCode: 400, + validation: { + keys: ['kibana.0._reserved'], + source: 'payload' + } + }, + }, + }); }); putRoleTest(`returns result of routePreCheckLicense`, { @@ -410,7 +464,7 @@ describe('PUT role', () => { payload: { kibana: [ { - base: ['all'] + base: ['all'], } ] }, @@ -473,7 +527,8 @@ describe('PUT role', () => { foo: ['foo-privilege-1', 'foo-privilege-2'], bar: ['bar-privilege-1', 'bar-privilege-2'] }, - spaces: ['*'] + spaces: ['*'], + _reserved: ['customApplication1', 'customApplication2'] }, { base: ['all', 'read'], @@ -511,6 +566,8 @@ describe('PUT role', () => { 'feature_foo.foo-privilege-2', 'feature_bar.bar-privilege-1', 'feature_bar.bar-privilege-2', + 'reserved_customApplication1', + 'reserved_customApplication2', ], resources: [GLOBAL_RESOURCE], }, diff --git a/x-pack/test/api_integration/apis/security/roles.js b/x-pack/test/api_integration/apis/security/roles.js index e1f2147265258..f4686db64a0bf 100644 --- a/x-pack/test/api_integration/apis/security/roles.js +++ b/x-pack/test/api_integration/apis/security/roles.js @@ -100,6 +100,39 @@ export default function ({ getService }) { } }); }); + + it('should create a role with kibana reserved privileges', async () => { + await supertest.put('/api/security/role/role_with_kibana_reserved_privileges') + .set('kbn-xsrf', 'xxx') + .send({ + kibana: [ + { + _reserved: ['apm', 'ml', 'monitoring'] + } + ] + }) + .expect(204); + + const role = await es.shield.getRole({ name: 'role_with_kibana_reserved_privileges' }); + expect(role).to.eql({ + role_with_kibana_reserved_privileges: { + cluster: [], + indices: [], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['reserved_apm', 'reserved_ml', 'reserved_monitoring'], + resources: ['*'], + } + ], + run_as: [], + metadata: {}, + transient_metadata: { + enabled: true, + }, + } + }); + }); }); describe('Update Role', () => { @@ -122,7 +155,7 @@ export default function ({ getService }) { applications: [ { application: 'kibana-.kibana', - privileges: ['read'], + privileges: ['read', 'reserved_ml'], resources: ['*'], }, { @@ -161,6 +194,7 @@ export default function ({ getService }) { }, kibana: [ { + _reserved: ['apm', 'monitoring'], base: ['read'], feature: { dashboard: ["read"], @@ -200,7 +234,7 @@ export default function ({ getService }) { applications: [ { application: 'kibana-.kibana', - privileges: ['read', 'feature_dashboard.read', 'feature_dev_tools.all'], + privileges: ['read', 'feature_dashboard.read', 'feature_dev_tools.all', 'reserved_apm', 'reserved_monitoring'], resources: ['*'], }, { From dbe36f697d32e96fc361beb680675e3dc3d5538e Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 7 Feb 2019 15:28:06 -0800 Subject: [PATCH 18/62] Adding support to get roles with _reserved privileges --- .../privilege_serializer.test.ts.snap | 2 + .../privilege_serializer.test.ts | 13 +++ .../lib/authorization/privilege_serializer.ts | 8 ++ .../server/routes/api/public/roles/get.js | 4 + .../routes/api/public/roles/get.test.js | 103 ++++++++++++++++++ .../api_integration/apis/security/roles.js | 93 ++++++++++++++++ 6 files changed, 223 insertions(+) diff --git a/x-pack/plugins/security/server/lib/authorization/__snapshots__/privilege_serializer.test.ts.snap b/x-pack/plugins/security/server/lib/authorization/__snapshots__/privilege_serializer.test.ts.snap index dfb2ad87c3162..ea7540f2a082e 100644 --- a/x-pack/plugins/security/server/lib/authorization/__snapshots__/privilege_serializer.test.ts.snap +++ b/x-pack/plugins/security/server/lib/authorization/__snapshots__/privilege_serializer.test.ts.snap @@ -12,6 +12,8 @@ exports[`#deserializeFeaturePrivilege throws error when deserializing foo_featur exports[`#deserializeGlobalBasePrivilege throws Error if isn't a base privilege 1`] = `"Unrecognized global base privilege"`; +exports[`#deserializeReservedPrivilege throws Error if doesn't start with reserved_ 1`] = `"Unrecognized reserved privilege"`; + exports[`#deserializeSpaceBasePrivilege throws Error if prefixed with space_ but not a reserved privilege 1`] = `"Unrecognized space base privilege"`; exports[`#deserializeSpaceBasePrivilege throws Error if provided 'all' 1`] = `"Unrecognized space base privilege"`; diff --git a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts index 35d6663cc70b5..ef898cbcfad03 100644 --- a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts @@ -165,3 +165,16 @@ describe('#deserializeSpaceBasePrivilege', () => { expect(result).toBe('read'); }); }); + +describe('#deserializeReservedPrivilege', () => { + test(`throws Error if doesn't start with reserved_`, () => { + expect(() => + PrivilegeSerializer.deserializeReservedPrivilege('all') + ).toThrowErrorMatchingSnapshot(); + }); + + test(`returns 'customApplication1' unprefixed if provided 'reserved_customApplication1'`, () => { + const result = PrivilegeSerializer.deserializeReservedPrivilege('reserved_customApplication1'); + expect(result).toBe('customApplication1'); + }); +}); diff --git a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts index 3b1df598ea1cd..e578800020a50 100644 --- a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts +++ b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts @@ -85,4 +85,12 @@ export class PrivilegeSerializer { return privilege.slice(spacePrefix.length); } + + public static deserializeReservedPrivilege(privilege: string) { + if (!PrivilegeSerializer.isSerializedReservedPrivilege(privilege)) { + throw new Error('Unrecognized reserved privilege'); + } + + return privilege.slice(reservedPrefix.length); + } } diff --git a/x-pack/plugins/security/server/routes/api/public/roles/get.js b/x-pack/plugins/security/server/routes/api/public/roles/get.js index a77fe4d785726..de2bee6348ff6 100644 --- a/x-pack/plugins/security/server/routes/api/public/roles/get.js +++ b/x-pack/plugins/security/server/routes/api/public/roles/get.js @@ -42,6 +42,7 @@ export function initGetRolesApi(server, callWithRequest, routePreCheckLicenseFn, value: roleKibanaApplications.map(({ resources, privileges }) => { // if we're dealing with a global entry, which we've ensured above is only possible if it's the only item in the array if (resources.length === 1 && resources[0] === GLOBAL_RESOURCE) { + const reservedPrivileges = privileges.filter(privilege => PrivilegeSerializer.isSerializedReservedPrivilege(privilege)); const basePrivileges = privileges.filter(privilege => PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege)); const featurePrivileges = privileges.filter(privilege => !PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege) && @@ -49,6 +50,9 @@ export function initGetRolesApi(server, callWithRequest, routePreCheckLicenseFn, ); return { + ...reservedPrivileges.length ? { + _reserved: reservedPrivileges.map(privilege => PrivilegeSerializer.deserializeReservedPrivilege(privilege)) + } : {}, base: basePrivileges.map(privilege => PrivilegeSerializer.serializeGlobalBasePrivilege(privilege)), feature: featurePrivileges.reduce((acc, privilege) => { const featurePrivilege = PrivilegeSerializer.deserializeFeaturePrivilege(privilege); diff --git a/x-pack/plugins/security/server/routes/api/public/roles/get.test.js b/x-pack/plugins/security/server/routes/api/public/roles/get.test.js index b7b46fc2489c2..5c454bcab3730 100644 --- a/x-pack/plugins/security/server/routes/api/public/roles/get.test.js +++ b/x-pack/plugins/security/server/routes/api/public/roles/get.test.js @@ -243,6 +243,58 @@ describe('GET roles', () => { ], }, }); + + getRolesTest(`transforms matching applications with * resource to kibana _reserved privileges`, { + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['reserved_customApplication1', 'reserved_customApplication2'], + resources: ['*'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: [ + { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + _reserved: ['customApplication1', 'customApplication2'], + base: [], + feature: {}, + spaces: ['*'] + } + ], + _transform_error: [], + _unrecognized_applications: [], + }, + ], + }, + }); }); describe('space', () => { @@ -1018,6 +1070,57 @@ describe('GET role', () => { }, }, }); + + getRoleTest(`transforms matching applications with * resource to kibana _reserved privileges`, { + name: 'first_role', + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['reserved_customApplication1', 'reserved_customApplication2'], + resources: ['*'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + _reserved: ['customApplication1', 'customApplication2'], + base: [], + feature: {}, + spaces: ['*'] + } + ], + _transform_error: [], + _unrecognized_applications: [], + }, + }, + }); }); describe('space', () => { diff --git a/x-pack/test/api_integration/apis/security/roles.js b/x-pack/test/api_integration/apis/security/roles.js index f4686db64a0bf..cd5f6360b12e5 100644 --- a/x-pack/test/api_integration/apis/security/roles.js +++ b/x-pack/test/api_integration/apis/security/roles.js @@ -260,6 +260,99 @@ export default function ({ getService }) { }); }); + describe('Get Role', async () => { + await es.shield.putRole({ + name: 'role_to_get', + body: { + cluster: ['manage'], + indices: [ + { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + allow_restricted_indices: false, + field_security: { + grant: ['*'], + except: ['geo.*'] + }, + query: `{ "match": { "geo.src": "CN" } }`, + }, + ], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read', 'feature_dashboard.read', 'feature_dev_tools.all', 'reserved_apm', 'reserved_monitoring'], + resources: ['*'], + }, + { + application: 'kibana-.kibana', + privileges: ['space_all', 'feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'], + resources: ['space:marketing', 'space:sales'], + }, + { + application: 'logstash-default', + privileges: ['logstash-privilege'], + resources: ['*'], + }, + ], + run_as: ['watcher_user'], + metadata: { + foo: 'test-metadata', + }, + transient_metadata: { + enabled: true, + }, + } + }); + + await supertest.get('/api/security/role/role_to_get') + .set('kbn-xsrf', 'xxx') + .expect(200, { + name: 'role_to_get', + metadata: { + foo: 'test-metadata', + }, + transient_metadata: { enabled: true }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + field_security: { + grant: ['*'], + except: ['geo.*'] + }, + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + query: `{ "match": { "geo.src": "CN" } }`, + allow_restricted_indices: false + }, + ], + run_as: ['watcher_user'], + }, + kibana: [ + { + _reserved: ['apm', 'monitoring'], + base: ['read'], + feature: { + dashboard: ["read"], + dev_tools: ["all"], + }, + spaces: ['*'] + }, + { + base: ['all'], + feature: { + dashboard: ["read"], + discover: ["all"], + ml: ["all"] + }, + spaces: ['marketing', 'sales'] + } + ], + + _transform_error: [], + _unrecognized_applications: [ 'logstash-default' ] + }); + }); describe('Delete Role', () => { it('should delete the three roles we created', async () => { await supertest.delete('/api/security/role/empty_role').set('kbn-xsrf', 'xxx').expect(204); From 2044ccd6aae089b3d37f25195303b433bf0ecacc Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 7 Feb 2019 16:28:19 -0800 Subject: [PATCH 19/62] Adding APM functional tests --- test/functional/page_objects/common_page.js | 5 + .../apm/public/components/app/Main/index.js | 2 +- .../apps/apm/feature_controls/apm_security.ts | 91 ++++++++++++++++ .../apps/apm/feature_controls/apm_spaces.ts | 100 ++++++++++++++++++ .../apps/apm/feature_controls/index.ts | 14 +++ x-pack/test/functional/apps/apm/index.ts | 15 +++ x-pack/test/functional/config.js | 4 + 7 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 x-pack/test/functional/apps/apm/feature_controls/apm_security.ts create mode 100644 x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts create mode 100644 x-pack/test/functional/apps/apm/feature_controls/index.ts create mode 100644 x-pack/test/functional/apps/apm/index.ts diff --git a/test/functional/page_objects/common_page.js b/test/functional/page_objects/common_page.js index 67504272c9ca2..aa3249ae5bf0b 100644 --- a/test/functional/page_objects/common_page.js +++ b/test/functional/page_objects/common_page.js @@ -353,6 +353,11 @@ export function CommonPageProvider({ getService, getPageObjects }) { } } } + + async getBodyText() { + const el = await find.byCssSelector('body>pre'); + return await el.getVisibleText(); + } } return new CommonPage(); diff --git a/x-pack/plugins/apm/public/components/app/Main/index.js b/x-pack/plugins/apm/public/components/app/Main/index.js index 865bf756f7663..910cdd7fa855b 100644 --- a/x-pack/plugins/apm/public/components/app/Main/index.js +++ b/x-pack/plugins/apm/public/components/app/Main/index.js @@ -21,7 +21,7 @@ const MainContainer = styled.div` export default function Main() { return ( - + diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts new file mode 100644 index 0000000000000..8c1651cea38fb --- /dev/null +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts @@ -0,0 +1,91 @@ +/* + * 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 expect from 'expect.js'; +import { SecurityService } from 'x-pack/test/common/services'; +import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; + +// tslint:disable:no-default-export +export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { + const esArchiver = getService('esArchiver'); + const security: SecurityService = getService('security'); + const appsMenu = getService('appsMenu'); + const PageObjects = getPageObjects(['common', 'security']); + + describe('security', () => { + before(async () => { + await esArchiver.load('empty_kibana'); + + await security.role.create('global_all_role', { + elasticsearch: { + indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + base: ['all'], + spaces: ['*'], + }, + ], + }); + + // ensure we're logged out so we can login as the appropriate users + await PageObjects.security.logout(); + }); + + after(async () => { + await esArchiver.unload('empty_kibana'); + await security.role.delete('global_all_role'); + + // logout, so the other tests don't accidentally run as the custom users we're testing below + await PageObjects.security.logout(); + }); + + describe('global all', () => { + before(async () => { + await security.user.create('global_all', { + password: 'global_all-password', + roles: ['global_all_role'], + full_name: 'global all', + }); + + await PageObjects.security.login('global_all', 'global_all-password'); + }); + + after(async () => { + await security.user.delete('global_all'); + }); + + it(`doens't show apm navlink`, async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).not.to.contain('APM'); + }); + }); + + describe('apm_user and global all', () => { + before(async () => { + await security.user.create('apm_user', { + password: 'apm_user-password', + roles: ['apm_user', 'global_all_role'], + full_name: 'apm user', + }); + + await PageObjects.security.login('apm_user', 'apm_user-password'); + }); + + after(async () => { + await security.user.delete('apm_user'); + }); + + it('shows apm navlink', async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.contain('APM'); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts new file mode 100644 index 0000000000000..8c63424207f56 --- /dev/null +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts @@ -0,0 +1,100 @@ +/* + * 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 expect from 'expect.js'; +import { SpacesService } from 'x-pack/test/common/services'; +import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; + +// tslint:disable:no-default-export +export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { + const esArchiver = getService('esArchiver'); + const spacesService: SpacesService = getService('spaces'); + const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'spaceSelector']); + const appsMenu = getService('appsMenu'); + const testSubjects = getService('testSubjects'); + + describe.only('spaces', () => { + before(async () => { + await esArchiver.load('empty_kibana'); + }); + + after(async () => { + await esArchiver.unload('empty_kibana'); + }); + + describe('space with no features disabled', () => { + before(async () => { + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: [], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + }); + + it('shows apm navlink', async () => { + await PageObjects.common.navigateToApp('home', { + basePath: '/s/custom_space', + }); + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.contain('APM'); + }); + + it(`can navigate to app`, async () => { + await PageObjects.common.navigateToApp('apm', { + basePath: '/s/custom_space', + }); + + await testSubjects.existOrFail('apm-main-container', 10000); + }); + }); + + describe('space with APM disabled', () => { + before(async () => { + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: ['apm'], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + }); + + it(`doesn't show apm navlink`, async () => { + await PageObjects.common.navigateToApp('home', { + basePath: '/s/custom_space', + }); + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).not.to.contain('APM'); + }); + + it(`navigating to app returns a 404`, async () => { + await PageObjects.common.navigateToUrl('apm', '', { + basePath: '/s/custom_space', + shouldLoginIfPrompted: false, + ensureCurrentUrl: false, + }); + + const messageText = await PageObjects.common.getBodyText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }) + ); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/apm/feature_controls/index.ts b/x-pack/test/functional/apps/apm/feature_controls/index.ts new file mode 100644 index 0000000000000..4764fa276fd72 --- /dev/null +++ b/x-pack/test/functional/apps/apm/feature_controls/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; + +// tslint:disable:no-default-export +export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) { + describe('feature controls', () => { + loadTestFile(require.resolve('./apm_security')); + loadTestFile(require.resolve('./apm_spaces')); + }); +} diff --git a/x-pack/test/functional/apps/apm/index.ts b/x-pack/test/functional/apps/apm/index.ts new file mode 100644 index 0000000000000..e70b3ad65787c --- /dev/null +++ b/x-pack/test/functional/apps/apm/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; + +// tslint:disable:no-default-export +export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) { + describe('apm', function() { + this.tags('ciGroup3'); + + loadTestFile(require.resolve('./feature_controls')); + }); +} diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index b7562acd3bf4a..5eaeeed04b653 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -77,6 +77,7 @@ export default async function ({ readConfigFile }) { return { // list paths to the files that contain your plugins tests testFiles: [ + resolve(__dirname, './apps/apm'), resolve(__dirname, './apps/canvas'), resolve(__dirname, './apps/graph'), resolve(__dirname, './apps/monitoring'), @@ -212,6 +213,9 @@ export default async function ({ readConfigFile }) { }, uptime: { pathname: '/app/uptime', + }, + apm: { + pathname: '/app/apm' } }, From 1ef0d2c3e730b4309b12d73a6a4958bda28d8d34 Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 7 Feb 2019 17:09:02 -0800 Subject: [PATCH 20/62] Adding monitoring functional tests --- .../apps/apm/feature_controls/apm_spaces.ts | 2 +- .../apps/monitoring/feature_controls/index.ts | 14 +++ .../feature_controls/monitoring_security.ts | 91 ++++++++++++++++ .../feature_controls/monitoring_spaces.ts | 101 ++++++++++++++++++ .../test/functional/apps/monitoring/index.js | 2 + 5 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 x-pack/test/functional/apps/monitoring/feature_controls/index.ts create mode 100644 x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts create mode 100644 x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts index 8c63424207f56..60d8b7a71f043 100644 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts @@ -15,7 +15,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); - describe.only('spaces', () => { + describe('spaces', () => { before(async () => { await esArchiver.load('empty_kibana'); }); diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/index.ts b/x-pack/test/functional/apps/monitoring/feature_controls/index.ts new file mode 100644 index 0000000000000..0eb26f5e6bef1 --- /dev/null +++ b/x-pack/test/functional/apps/monitoring/feature_controls/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; + +// tslint:disable:no-default-export +export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) { + describe('feature controls', () => { + loadTestFile(require.resolve('./monitoring_security')); + loadTestFile(require.resolve('./monitoring_spaces')); + }); +} diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts new file mode 100644 index 0000000000000..bde4e2afe4298 --- /dev/null +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts @@ -0,0 +1,91 @@ +/* + * 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 expect from 'expect.js'; +import { SecurityService } from 'x-pack/test/common/services'; +import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; + +// tslint:disable:no-default-export +export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { + const esArchiver = getService('esArchiver'); + const security: SecurityService = getService('security'); + const appsMenu = getService('appsMenu'); + const PageObjects = getPageObjects(['common', 'security']); + + describe('securty', () => { + before(async () => { + await esArchiver.load('empty_kibana'); + + await security.role.create('global_all_role', { + elasticsearch: { + indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + base: ['all'], + spaces: ['*'], + }, + ], + }); + + // ensure we're logged out so we can login as the appropriate users + await PageObjects.security.logout(); + }); + + after(async () => { + await esArchiver.unload('empty_kibana'); + await security.role.delete('global_all_role'); + + // logout, so the other tests don't accidentally run as the custom users we're testing below + await PageObjects.security.logout(); + }); + + describe('global all', () => { + before(async () => { + await security.user.create('global_all', { + password: 'global_all-password', + roles: ['global_all_role'], + full_name: 'global all', + }); + + await PageObjects.security.login('global_all', 'global_all-password'); + }); + + after(async () => { + await security.user.delete('global_all'); + }); + + it(`doesn't show monitoring navlink`, async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).not.to.contain('Stack Monitoring'); + }); + }); + + describe('monitoring_user and global all', () => { + before(async () => { + await security.user.create('monitoring_user', { + password: 'monitoring_user-password', + roles: ['monitoring_user', 'global_all_role'], + full_name: 'monitoring user', + }); + + await PageObjects.security.login('monitoring_user', 'monitoring_user-password'); + }); + + after(async () => { + await security.user.delete('monitoring_user'); + }); + + it('shows monitoring navlink', async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.contain('Stack Monitoring'); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts new file mode 100644 index 0000000000000..ed24e5b62538e --- /dev/null +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts @@ -0,0 +1,101 @@ +/* + * 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 expect from 'expect.js'; +import { SpacesService } from 'x-pack/test/common/services'; +import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; + +// tslint:disable:no-default-export +export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { + const esArchiver = getService('esArchiver'); + const spacesService: SpacesService = getService('spaces'); + const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'spaceSelector']); + const appsMenu = getService('appsMenu'); + const find = getService('find'); + + describe.only('spaces', () => { + before(async () => { + await esArchiver.load('empty_kibana'); + }); + + after(async () => { + await esArchiver.unload('empty_kibana'); + }); + + describe('space with no features disabled', () => { + before(async () => { + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: [], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + }); + + it('shows stack monitoring navlink', async () => { + await PageObjects.common.navigateToApp('home', { + basePath: '/s/custom_space', + }); + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.contain('Stack Monitoring'); + }); + + it(`can navigate to app`, async () => { + await PageObjects.common.navigateToApp('monitoring', { + basePath: '/s/custom_space', + }); + + const exists = await find.existsByCssSelector('monitoring-main'); + expect(exists).to.be(true); + }); + }); + + describe('space with monitoring disabled', () => { + before(async () => { + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: ['monitoring'], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + }); + + it(`doesn't show stack monitoring navlink`, async () => { + await PageObjects.common.navigateToApp('home', { + basePath: '/s/custom_space', + }); + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).not.to.contain('Stack Monitoring'); + }); + + it(`navigating to app returns a 404`, async () => { + await PageObjects.common.navigateToUrl('monitoring', '', { + basePath: '/s/custom_space', + shouldLoginIfPrompted: false, + ensureCurrentUrl: false, + }); + + const messageText = await PageObjects.common.getBodyText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }) + ); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/monitoring/index.js b/x-pack/test/functional/apps/monitoring/index.js index a1551f2829a98..a22ad50d1338c 100644 --- a/x-pack/test/functional/apps/monitoring/index.js +++ b/x-pack/test/functional/apps/monitoring/index.js @@ -8,6 +8,8 @@ export default function ({ loadTestFile }) { describe('Monitoring app', function () { this.tags('ciGroup1'); + loadTestFile(require.resolve('./feature_controls')); + loadTestFile(require.resolve('./cluster/list')); loadTestFile(require.resolve('./cluster/overview')); loadTestFile(require.resolve('./cluster/alerts')); From 7fe8e30275c2eaa03f31988c272e322fa1afef1a Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 8 Feb 2019 07:25:43 -0800 Subject: [PATCH 21/62] Fixing typo --- .../security_and_spaces/apis/find.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts index 1344d58312f2e..f15122ef20f81 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts @@ -108,7 +108,7 @@ export default function({ getService }: TestInvoker) { response: expectNotSpaceAwareResults, }, unknownType: { - description: 'emptry result', + description: 'empty result', statusCode: 200, response: createExpectEmpty(1, 20, 0), }, From 03bb72ca603d3b47b4fdb2fad84cb9e493318234 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 8 Feb 2019 08:10:52 -0800 Subject: [PATCH 22/62] Ensuring apm_user, monitoring_user alone don't authorize you --- .../apps/apm/feature_controls/apm_security.ts | 20 +++++++++++++++++++ .../feature_controls/monitoring_security.ts | 20 +++++++++++++++++++ .../feature_controls/monitoring_spaces.ts | 2 +- .../functional/page_objects/security_page.js | 16 ++++++++++++++- 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts index 8c1651cea38fb..e96f264ca0201 100644 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts @@ -42,6 +42,26 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await PageObjects.security.logout(); }); + describe('apm_user', () => { + before(async () => { + await security.user.create('apm_user', { + password: 'apm_user-password', + roles: ['apm_user'], + full_name: 'apm user', + }); + }); + + after(async () => { + await security.user.delete('apm_user'); + }); + + it('gets forbidden after login', async () => { + await PageObjects.security.login('apm_user', 'apm_user-password', { + expectForbidden: true, + }); + }); + }); + describe('global all', () => { before(async () => { await security.user.create('global_all', { diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts index bde4e2afe4298..96465a5811dc3 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts @@ -42,6 +42,26 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await PageObjects.security.logout(); }); + describe('monitoring_user', () => { + before(async () => { + await security.user.create('monitoring_user', { + password: 'monitoring_user-password', + roles: ['monitoring_user'], + full_name: 'monitoring all', + }); + }); + + after(async () => { + await security.user.delete('monitoring_user'); + }); + + it('gets forbidden after login', async () => { + await PageObjects.security.login('monitoring_user', 'monitoring_user-password', { + expectForbidden: true, + }); + }); + }); + describe('global all', () => { before(async () => { await security.user.create('global_all', { diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts index ed24e5b62538e..9320f0dc10f0a 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts @@ -15,7 +15,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa const appsMenu = getService('appsMenu'); const find = getService('find'); - describe.only('spaces', () => { + describe('spaces', () => { before(async () => { await esArchiver.load('empty_kibana'); }); diff --git a/x-pack/test/functional/page_objects/security_page.js b/x-pack/test/functional/page_objects/security_page.js index 88409ca7c1eb0..92e5675fc7998 100644 --- a/x-pack/test/functional/page_objects/security_page.js +++ b/x-pack/test/functional/page_objects/security_page.js @@ -27,6 +27,7 @@ export function SecurityPageProvider({ getService, getPageObjects }) { const expectSpaceSelector = options.expectSpaceSelector || false; const expectSuccess = options.expectSuccess; + const expectForbidden = options.expectForbidden || false; await PageObjects.common.navigateToApp('login'); await testSubjects.setValue('loginUsername', username); @@ -37,6 +38,19 @@ export function SecurityPageProvider({ getService, getPageObjects }) { if (expectSpaceSelector) { await retry.try(() => testSubjects.find('kibanaSpaceSelector')); log.debug(`Finished login process, landed on space selector. currentUrl = ${await browser.getCurrentUrl()}`); + } else if (expectForbidden) { + await retry.try(async () => { + const bodyText = await PageObjects.common.getBodyText(); + const forbiddenMessage = { + statusCode: 403, + error: 'Forbidden', + message: 'Forbidden' + }; + if (bodyText !== JSON.stringify(forbiddenMessage)) { + throw new Error(`Expected forbidden message, but didn't find it`); + } + }); + log.debug(`Finished login process, found forbidden message. currentUrl = ${await browser.getCurrentUrl()}`); } else if (expectSuccess) { await find.byCssSelector('[data-test-subj="kibanaChrome"] nav:not(.ng-hide) ', 20000); log.debug(`Finished login process currentUrl = ${await browser.getCurrentUrl()}`); @@ -73,7 +87,7 @@ export function SecurityPageProvider({ getService, getPageObjects }) { async login(username, password, options = {}) { await this.loginPage.login(username, password, options); - if (options.expectSpaceSelector) { + if (options.expectSpaceSelector || options.expectForbidden) { return; } From cb198233a503d8672fb7d215f417ad78fe8699e7 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 8 Feb 2019 10:04:01 -0800 Subject: [PATCH 23/62] Adding ml functional tests --- .../jobs_list_view/jobs_list_view.js | 2 +- .../feature_controls/index.ts | 14 +++ .../feature_controls/ml_security.ts | 115 ++++++++++++++++++ .../feature_controls/ml_spaces.ts | 100 +++++++++++++++ .../functional/apps/machine_learning/index.ts | 15 +++ x-pack/test/functional/config.js | 4 + 6 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 x-pack/test/functional/apps/machine_learning/feature_controls/index.ts create mode 100644 x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts create mode 100644 x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts create mode 100644 x-pack/test/functional/apps/machine_learning/index.ts diff --git a/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js index cc2acc170afbd..adea108133b11 100644 --- a/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js @@ -393,7 +393,7 @@ export class JobsListView extends Component { -
+
diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/index.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/index.ts new file mode 100644 index 0000000000000..7f31aa6d3e497 --- /dev/null +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; + +// tslint:disable:no-default-export +export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) { + describe('feature controls', () => { + loadTestFile(require.resolve('./ml_security')); + loadTestFile(require.resolve('./ml_spaces')); + }); +} diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts new file mode 100644 index 0000000000000..068cceb05da34 --- /dev/null +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts @@ -0,0 +1,115 @@ +/* + * 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 expect from 'expect.js'; +import { SecurityService } from 'x-pack/test/common/services'; +import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; + +// tslint:disable:no-default-export +export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { + const esArchiver = getService('esArchiver'); + const security: SecurityService = getService('security'); + const appsMenu = getService('appsMenu'); + const PageObjects = getPageObjects(['common', 'security']); + + describe('security', () => { + before(async () => { + await esArchiver.load('empty_kibana'); + + await security.role.create('global_all_role', { + elasticsearch: { + indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }], + }, + kibana: [ + { + base: ['all'], + spaces: ['*'], + }, + ], + }); + + // ensure we're logged out so we can login as the appropriate users + await PageObjects.security.logout(); + }); + + after(async () => { + await esArchiver.unload('empty_kibana'); + await security.role.delete('global_all_role'); + + // logout, so the other tests don't accidentally run as the custom users we're testing below + await PageObjects.security.logout(); + }); + + describe('machine_learning_user', () => { + before(async () => { + await security.user.create('machine_learning_user', { + password: 'machine_learning_user-password', + roles: ['machine_learning_user'], + full_name: 'machine learning user', + }); + }); + + after(async () => { + await security.user.delete('machine_learning_user'); + }); + + it('gets forbidden after login', async () => { + await PageObjects.security.login( + 'machine_learning_user', + 'machine_learning_user-password', + { + expectForbidden: true, + } + ); + }); + }); + + describe('global all', () => { + before(async () => { + await security.user.create('global_all', { + password: 'global_all-password', + roles: ['global_all_role'], + full_name: 'global all', + }); + + await PageObjects.security.login('global_all', 'global_all-password'); + }); + + after(async () => { + await security.user.delete('global_all'); + }); + + it(`doesn't show ml navlink`, async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).not.to.contain('Machine Learning'); + }); + }); + + describe('machine_learning_user and global all', () => { + before(async () => { + await security.user.create('machine_learning_user', { + password: 'machine_learning_user-password', + roles: ['machine_learning_user', 'global_all_role'], + full_name: 'machine learning user and global all user', + }); + + await PageObjects.security.login('machine_learning_user', 'machine_learning_user-password'); + }); + + after(async () => { + await security.user.delete('machine_learning_user'); + }); + + it('shows ML navlink', async () => { + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.contain('Machine Learning'); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts new file mode 100644 index 0000000000000..f5f69db6e5562 --- /dev/null +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts @@ -0,0 +1,100 @@ +/* + * 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 expect from 'expect.js'; +import { SpacesService } from 'x-pack/test/common/services'; +import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; + +// tslint:disable:no-default-export +export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { + const esArchiver = getService('esArchiver'); + const spacesService: SpacesService = getService('spaces'); + const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'spaceSelector']); + const appsMenu = getService('appsMenu'); + const testSubjects = getService('testSubjects'); + + describe('spaces', () => { + before(async () => { + await esArchiver.load('empty_kibana'); + }); + + after(async () => { + await esArchiver.unload('empty_kibana'); + }); + + describe('space with no features disabled', () => { + before(async () => { + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: [], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + }); + + it('shows apm navlink', async () => { + await PageObjects.common.navigateToApp('home', { + basePath: '/s/custom_space', + }); + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).to.contain('Machine Learning'); + }); + + it(`can navigate to app`, async () => { + await PageObjects.common.navigateToApp('ml', { + basePath: '/s/custom_space', + }); + + await testSubjects.existOrFail('ml-jobs-list', 10000); + }); + }); + + describe('space with ML disabled', () => { + before(async () => { + await spacesService.create({ + id: 'custom_space', + name: 'custom_space', + disabledFeatures: ['ml'], + }); + }); + + after(async () => { + await spacesService.delete('custom_space'); + }); + + it(`doesn't show apm navlink`, async () => { + await PageObjects.common.navigateToApp('home', { + basePath: '/s/custom_space', + }); + const navLinks = (await appsMenu.readLinks()).map( + (link: Record) => link.text + ); + expect(navLinks).not.to.contain('Machine Learning'); + }); + + it(`navigating to app returns a 404`, async () => { + await PageObjects.common.navigateToUrl('ml', '', { + basePath: '/s/custom_space', + shouldLoginIfPrompted: false, + ensureCurrentUrl: false, + }); + + const messageText = await PageObjects.common.getBodyText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 404, + error: 'Not Found', + message: 'Not Found', + }) + ); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/machine_learning/index.ts b/x-pack/test/functional/apps/machine_learning/index.ts new file mode 100644 index 0000000000000..ba4dc4a818fef --- /dev/null +++ b/x-pack/test/functional/apps/machine_learning/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; + +// tslint:disable:no-default-export +export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) { + describe('machine learning', function() { + this.tags('ciGroup3'); + + loadTestFile(require.resolve('./feature_controls')); + }); +} diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index de391953f207b..303c31a7cfbd8 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -91,6 +91,7 @@ export default async function ({ readConfigFile }) { resolve(__dirname, './apps/logstash'), resolve(__dirname, './apps/grok_debugger'), resolve(__dirname, './apps/infra'), + resolve(__dirname, './apps/machine_learning'), resolve(__dirname, './apps/maps'), resolve(__dirname, './apps/status_page'), resolve(__dirname, './apps/upgrade_assistant'), @@ -219,6 +220,9 @@ export default async function ({ readConfigFile }) { }, apm: { pathname: '/app/apm' + }, + ml: { + pathname: '/app/ml' } }, From d9e38b9fb0c9ae3729666c20d00131a169a9134e Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 8 Feb 2019 10:39:13 -0800 Subject: [PATCH 24/62] Fixing test --- .../server/lib/authorization/privileges_serializer.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security/server/lib/authorization/privileges_serializer.test.ts b/x-pack/plugins/security/server/lib/authorization/privileges_serializer.test.ts index 6d077ef35eb36..9c4dbd8e80921 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges_serializer.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges_serializer.test.ts @@ -160,6 +160,7 @@ describe('features', () => { }, bar: {}, }, + reserved: {}, }); expect(result[application]).toEqual({ 'feature_foo.quz': { From e467f1bf61adb6d2b87b0dca66cdfefb3614c6e9 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 8 Feb 2019 12:08:29 -0800 Subject: [PATCH 25/62] Fixing some type errors --- .../default_privilege_definition.ts | 1 + .../feature_table/feature_table.test.tsx | 1 + .../kibana/kibana_privileges_region.test.tsx | 1 + .../simple_privilege_section.test.tsx | 1 + .../privilege_matrix.test.tsx | 1 + .../privilege_space_form.test.tsx | 2 ++ .../space_aware_privilege_section.test.tsx | 1 + .../privileges/privileges.test.ts | 2 +- x-pack/test/ui_capabilities/common/types.ts | 28 ++++++++----------- .../security_only/tests/advanced_settings.ts | 11 ++++++-- 10 files changed, 28 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/__fixtures__/default_privilege_definition.ts b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/__fixtures__/default_privilege_definition.ts index d95e20c476fbe..d598a9da67a51 100644 --- a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/__fixtures__/default_privilege_definition.ts +++ b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/__fixtures__/default_privilege_definition.ts @@ -34,4 +34,5 @@ export const defaultPrivilegeDefinition = new KibanaPrivileges({ all: ['ui:/feature3/foo', 'ui:/feature3/foo/*'], }, }, + reserved: {}, }); diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.test.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.test.tsx index 063c561c9cf48..9648bf1d111bf 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.test.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.test.tsx @@ -36,6 +36,7 @@ const defaultPrivilegeDefinition = new KibanaPrivileges({ all: ['somethingObsecure:/foo'], }, }, + reserved: {}, }); interface BuildRoleOpts { diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/kibana_privileges_region.test.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/kibana_privileges_region.test.tsx index da2514c6088aa..bcbec6575b0d9 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/kibana_privileges_region.test.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/kibana_privileges_region.test.tsx @@ -43,6 +43,7 @@ const buildProps = (customProps = {}) => { global: {}, space: {}, features: {}, + reserved: {}, }), intl: null as any, uiCapabilities: { diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx index 18b1f7b9fbb54..b2804b6756cad 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx @@ -23,6 +23,7 @@ const buildProps = (customProps: any = {}) => { }, global: {}, space: {}, + reserved: {}, }); const role = { diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx index 71fe2b1d671cf..fb8a67144aa8e 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.test.tsx @@ -103,6 +103,7 @@ describe('PrivilegeMatrix', () => { all: [], read: [], }, + reserved: {}, }) ).getInstance(role); diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx index a57b97dcd9e4b..3ef291eafe690 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.test.tsx @@ -33,12 +33,14 @@ const buildProps = (customProps = {}) => { features: {}, global: {}, space: {}, + reserved: {}, }), privilegeCalculatorFactory: new KibanaPrivilegeCalculatorFactory( new KibanaPrivileges({ global: {}, features: {}, space: {}, + reserved: {}, }) ), features: [], diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx index ef369c762aa3c..a2a53f191fafc 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx @@ -55,6 +55,7 @@ const buildProps = (customProps: any = {}) => { }, global: {}, space: {}, + reserved: {}, }) ), ...customProps, diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts index 58a929b7ef3b3..c7ad75646183a 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts @@ -658,7 +658,7 @@ describe('reserved', () => { getFeatures: jest.fn().mockReturnValue(features), }; - const privileges = privilegesFactory(actions, mockXPackMainPlugin); + const privileges = privilegesFactory(actions, mockXPackMainPlugin as any); const actual = privileges.get(); expect(actual).toHaveProperty('reserved', { diff --git a/x-pack/test/ui_capabilities/common/types.ts b/x-pack/test/ui_capabilities/common/types.ts index 6012188162f98..06e4ab2b2f6e4 100644 --- a/x-pack/test/ui_capabilities/common/types.ts +++ b/x-pack/test/ui_capabilities/common/types.ts @@ -3,6 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +interface FeaturesPrivileges { + [featureId: string]: string[]; +} // TODO: Consolidate the following type definitions interface CustomRoleSpecificationElasticsearchIndices { @@ -10,28 +13,19 @@ interface CustomRoleSpecificationElasticsearchIndices { privileges: string[]; } -interface CustomRoleSpecification { +export interface RoleKibanaPrivilege { + spaces: string[]; + base?: string[]; + feature?: FeaturesPrivileges; +} + +export interface CustomRoleSpecification { name: string; elasticsearch?: { cluster: string[]; indices: CustomRoleSpecificationElasticsearchIndices[]; }; - kibana?: { - global: { - minimum?: string[]; - feature?: { - [featureName: string]: string[]; - }; - }; - space?: { - [spaceId: string]: { - minimum?: string[]; - feature?: { - [featureName: string]: string[]; - }; - }; - }; - }; + kibana?: RoleKibanaPrivilege[]; } interface ReservedRoleSpecification { diff --git a/x-pack/test/ui_capabilities/security_only/tests/advanced_settings.ts b/x-pack/test/ui_capabilities/security_only/tests/advanced_settings.ts index 2675d6a9b220c..677e4e7b38a7d 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/advanced_settings.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/advanced_settings.ts @@ -31,6 +31,10 @@ export default function advancedSettingsTests({ case 'all': case 'dual_privileges_all': case 'advancedSettings_all': + case 'apm_user_and_all': + case 'machine_learning_admin_and_all': + case 'machine_learning_user_and_all': + case 'monitoring_user_and_all': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('advancedSettings'); expect(uiCapabilities.value!.advancedSettings).to.eql({ @@ -47,7 +51,7 @@ export default function advancedSettingsTests({ }); break; // these users can't do anything with Advanced Settings - case 'apm_all': + case 'apm_user': case 'canvas_all': case 'canvas_read': case 'dev_tools_read': @@ -61,8 +65,9 @@ export default function advancedSettingsTests({ case 'maps_read': case 'infrastructure_read': case 'logs_read': - case 'ml_all': - case 'monitoring_all': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': case 'timelion_all': case 'timelion_read': case 'uptime_read': From 908a3645e2b9180d9d0e109ae35662ebba4ec88f Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 8 Feb 2019 14:24:35 -0800 Subject: [PATCH 26/62] Updating snapshots --- .../kibana/__snapshots__/kibana_privileges_region.test.tsx.snap | 2 ++ .../__snapshots__/privilege_space_form.test.tsx.snap | 1 + 2 files changed, 3 insertions(+) diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/__snapshots__/kibana_privileges_region.test.tsx.snap b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/__snapshots__/kibana_privileges_region.test.tsx.snap index 599c4f1c9e193..617335dc9fb34 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/__snapshots__/kibana_privileges_region.test.tsx.snap +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/__snapshots__/kibana_privileges_region.test.tsx.snap @@ -13,6 +13,7 @@ exports[` renders without crashing 1`] = ` "rawKibanaPrivileges": Object { "features": Object {}, "global": Object {}, + "reserved": Object {}, "space": Object {}, }, } @@ -24,6 +25,7 @@ exports[` renders without crashing 1`] = ` "rawKibanaPrivileges": Object { "features": Object {}, "global": Object {}, + "reserved": Object {}, "space": Object {}, }, }, diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap index 6d4f28dff2c35..f0cfbe80e8f6b 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap @@ -429,6 +429,7 @@ exports[` renders without crashing 1`] = ` "rawKibanaPrivileges": Object { "features": Object {}, "global": Object {}, + "reserved": Object {}, "space": Object {}, }, } From c1634bb8416e15fdad281aa411bd533bd502c384 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 8 Feb 2019 14:50:42 -0800 Subject: [PATCH 27/62] Fixing privileges tests --- .../privileges/privileges.test.ts | 7 +- .../authorization/privileges/privileges.ts | 23 ++-- .../apis/security/privileges.ts | 108 +++++------------- 3 files changed, 49 insertions(+), 89 deletions(-) diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts index c7ad75646183a..e40f96009345d 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts @@ -235,7 +235,7 @@ describe('features', () => { }); }); - test(`features with no privileges are specified with an empty object`, () => { + test(`features with no privileges aren't listed`, () => { const features: Feature[] = [ { id: 'foo', @@ -253,7 +253,7 @@ describe('features', () => { const privileges = privilegesFactory(actions, mockXPackMainPlugin as any); const actual = privileges.get(); - expect(actual).toHaveProperty('features.foo', {}); + expect(actual).not.toHaveProperty('features.foo'); }); }); @@ -666,18 +666,21 @@ describe('reserved', () => { actions.version, actions.app.get('apm'), ...actions.savedObject.readOperations('config'), + actions.ui.get('catalogue', 'apm'), actions.ui.get('navLinks', 'apm'), ], ml: [ actions.version, actions.app.get('ml'), ...actions.savedObject.readOperations('config'), + actions.ui.get('catalogue', 'ml'), actions.ui.get('navLinks', 'ml'), ], monitoring: [ actions.version, actions.app.get('monitoring'), ...actions.savedObject.readOperations('config'), + actions.ui.get('catalogue', 'monitoring'), actions.ui.get('navLinks', 'monitoring'), ], }); diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts index 05c0d115ea509..386efa3332f86 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts @@ -65,15 +65,17 @@ export function privilegesFactory(actions: Actions, xpackMainPlugin: XPackMainPl return { features: features.reduce((acc: RawKibanaFeaturePrivileges, feature: Feature) => { - acc[feature.id] = mapValues(feature.privileges, privilege => [ - actions.login, - actions.version, - ...flatten( - featurePrivilegeBuilders.map(featurePrivilegeBuilder => - featurePrivilegeBuilder.getActions(privilege, feature) - ) - ), - ]); + if (Object.keys(feature.privileges).length > 0) { + acc[feature.id] = mapValues(feature.privileges, privilege => [ + actions.login, + actions.version, + ...flatten( + featurePrivilegeBuilders.map(featurePrivilegeBuilder => + featurePrivilegeBuilder.getActions(privilege, feature) + ) + ), + ]); + } return acc; }, {}), global: { @@ -95,18 +97,21 @@ export function privilegesFactory(actions: Actions, xpackMainPlugin: XPackMainPl actions.version, actions.app.get('apm'), ...actions.savedObject.readOperations('config'), + actions.ui.get('catalogue', 'apm'), actions.ui.get('navLinks', 'apm'), ], ml: [ actions.version, actions.app.get('ml'), ...actions.savedObject.readOperations('config'), + actions.ui.get('catalogue', 'ml'), actions.ui.get('navLinks', 'ml'), ], monitoring: [ actions.version, actions.app.get('monitoring'), ...actions.savedObject.readOperations('config'), + actions.ui.get('catalogue', 'monitoring'), actions.ui.get('navLinks', 'monitoring'), ], }, diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index 425f164c1f95c..24e372071538e 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -335,45 +335,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:graph-workspace/find', ], }, - monitoring: { - all: [ - 'login:', - `version:${version}`, - 'app:monitoring', - 'app:kibana', - 'ui:catalogue/monitoring', - 'ui:navLinks/monitoring', - 'saved_object:config/bulk_get', - 'saved_object:config/get', - 'saved_object:config/find', - ], - }, - ml: { - all: [ - 'login:', - `version:${version}`, - 'app:ml', - 'app:kibana', - 'ui:catalogue/ml', - 'ui:navLinks/ml', - 'saved_object:config/bulk_get', - 'saved_object:config/get', - 'saved_object:config/find', - ], - }, - apm: { - all: [ - 'login:', - `version:${version}`, - 'app:apm', - 'app:kibana', - 'ui:catalogue/apm', - 'ui:navLinks/apm', - 'saved_object:config/bulk_get', - 'saved_object:config/get', - 'saved_object:config/find', - ], - }, maps: { all: [ 'login:', @@ -584,15 +545,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:graph-workspace/bulk_create', 'saved_object:graph-workspace/update', 'saved_object:graph-workspace/delete', - 'app:monitoring', - 'ui:catalogue/monitoring', - 'ui:navLinks/monitoring', - 'app:ml', - 'ui:catalogue/ml', - 'ui:navLinks/ml', - 'app:apm', - 'ui:catalogue/apm', - 'ui:navLinks/apm', 'app:maps', 'ui:catalogue/maps', 'ui:navLinks/maps', @@ -672,15 +624,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:graph-workspace/bulk_get', 'saved_object:graph-workspace/get', 'saved_object:graph-workspace/find', - 'app:monitoring', - 'ui:catalogue/monitoring', - 'ui:navLinks/monitoring', - 'app:ml', - 'ui:catalogue/ml', - 'ui:navLinks/ml', - 'app:apm', - 'ui:catalogue/apm', - 'ui:navLinks/apm', 'app:maps', 'ui:catalogue/maps', 'ui:navLinks/maps', @@ -786,15 +729,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:graph-workspace/bulk_create', 'saved_object:graph-workspace/update', 'saved_object:graph-workspace/delete', - 'app:monitoring', - 'ui:catalogue/monitoring', - 'ui:navLinks/monitoring', - 'app:ml', - 'ui:catalogue/ml', - 'ui:navLinks/ml', - 'app:apm', - 'ui:catalogue/apm', - 'ui:navLinks/apm', 'app:maps', 'ui:catalogue/maps', 'ui:navLinks/maps', @@ -874,15 +808,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:graph-workspace/bulk_get', 'saved_object:graph-workspace/get', 'saved_object:graph-workspace/find', - 'app:monitoring', - 'ui:catalogue/monitoring', - 'ui:navLinks/monitoring', - 'app:ml', - 'ui:catalogue/ml', - 'ui:navLinks/ml', - 'app:apm', - 'ui:catalogue/apm', - 'ui:navLinks/apm', 'app:maps', 'ui:catalogue/maps', 'ui:navLinks/maps', @@ -902,6 +827,35 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'ui:navLinks/uptime', ], }, + reserved: { + apm: [ + `version:${version}`, + 'app:apm', + 'saved_object:config/bulk_get', + 'saved_object:config/get', + 'saved_object:config/find', + 'ui:catalogue/apm', + 'ui:navLinks/apm', + ], + ml: [ + `version:${version}`, + 'app:ml', + 'saved_object:config/bulk_get', + 'saved_object:config/get', + 'saved_object:config/find', + 'ui:catalogue/ml', + 'ui:navLinks/ml', + ], + monitoring: [ + `version:${version}`, + 'app:monitoring', + 'saved_object:config/bulk_get', + 'saved_object:config/get', + 'saved_object:config/find', + 'ui:catalogue/monitoring', + 'ui:navLinks/monitoring', + ], + }, }); }); @@ -921,9 +875,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { indexPatterns: ['all', 'read'], timelion: ['all', 'read'], graph: ['all', 'read'], - monitoring: ['all'], - ml: ['all'], - apm: ['all'], maps: ['all', 'read'], canvas: ['all', 'read'], infrastructure: ['read'], @@ -932,6 +883,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { }, global: ['all', 'read'], space: ['all', 'read'], + reserved: ['apm', 'ml', 'monitoring'], }); }); }); From 5396c6f9756b9b8de4eac59eb923be1d1fe5fe13 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 8 Feb 2019 15:12:04 -0800 Subject: [PATCH 28/62] Trying to force this to run from source --- test/scripts/jenkins_xpack_ci_group.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scripts/jenkins_xpack_ci_group.sh b/test/scripts/jenkins_xpack_ci_group.sh index f6404bb2792f0..617bbd52681e3 100755 --- a/test/scripts/jenkins_xpack_ci_group.sh +++ b/test/scripts/jenkins_xpack_ci_group.sh @@ -39,6 +39,6 @@ tar -xzf "$linuxBuild" -C "$installDir" --strip=1 export TEST_ES_FROM=${TEST_ES_FROM:-source} echo " -> Running functional and api tests" cd "$XPACK_DIR" -node scripts/functional_tests --debug --bail --kibana-install-dir "$installDir" --include-tag "ciGroup$CI_GROUP" +node scripts/functional_tests --debug --bail --kibana-install-dir "$installDir" --include-tag "ciGroup$CI_GROUP" --esFrom "source" echo "" echo "" From b2506724a873624dbfb3059dad94d024ca01646a Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 11 Feb 2019 07:11:53 -0800 Subject: [PATCH 29/62] Fixing TS errors --- .../ui_capabilities/security_only/tests/canvas.ts | 11 ++++++++--- .../test/ui_capabilities/security_only/tests/maps.ts | 11 ++++++++--- .../ui_capabilities/security_only/tests/timelion.ts | 11 ++++++++--- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/x-pack/test/ui_capabilities/security_only/tests/canvas.ts b/x-pack/test/ui_capabilities/security_only/tests/canvas.ts index 35751c52a2f43..c0d856dc77368 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/canvas.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/canvas.ts @@ -28,7 +28,11 @@ export default function canvasTests({ getService }: KibanaFunctionalTestDefaultP case 'superuser': case 'all': case 'dual_privileges_all': + case 'apm_user_and_all': case 'canvas_all': + case 'machine_learning_admin_and_all': + case 'machine_learning_user_and_all': + case 'monitoring_user_and_all': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('canvas'); expect(uiCapabilities.value!.canvas).to.eql({ @@ -47,7 +51,7 @@ export default function canvasTests({ getService }: KibanaFunctionalTestDefaultP // these users can't do anything with Canvas case 'advancedSettings_all': case 'advancedSettings_read': - case 'apm_all': + case 'apm_user': case 'dashboard_all': case 'dashboard_read': case 'dev_tools_read': @@ -57,10 +61,11 @@ export default function canvasTests({ getService }: KibanaFunctionalTestDefaultP case 'graph_read': case 'infrastructure_read': case 'logs_read': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': case 'maps_all': case 'maps_read': - case 'ml_all': - case 'monitoring_all': case 'timelion_all': case 'timelion_read': case 'uptime_read': diff --git a/x-pack/test/ui_capabilities/security_only/tests/maps.ts b/x-pack/test/ui_capabilities/security_only/tests/maps.ts index d4313d8e8aa44..ad2b1f6cca754 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/maps.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/maps.ts @@ -28,7 +28,11 @@ export default function mapsTests({ getService }: KibanaFunctionalTestDefaultPro case 'superuser': case 'all': case 'dual_privileges_all': + case 'apm_user_and_all': + case 'machine_learning_admin_and_all': + case 'machine_learning_user_and_all': case 'maps_all': + case 'monitoring_user_and_all': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('maps'); expect(uiCapabilities.value!.maps).to.eql({ @@ -47,7 +51,7 @@ export default function mapsTests({ getService }: KibanaFunctionalTestDefaultPro // these users can't do anything with Advanced Settings case 'advancedSettings_all': case 'advancedSettings_read': - case 'apm_all': + case 'apm_user': case 'canvas_all': case 'canvas_read': case 'dev_tools_read': @@ -59,8 +63,9 @@ export default function mapsTests({ getService }: KibanaFunctionalTestDefaultPro case 'graph_read': case 'infrastructure_read': case 'logs_read': - case 'ml_all': - case 'monitoring_all': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': case 'timelion_all': case 'timelion_read': case 'uptime_read': diff --git a/x-pack/test/ui_capabilities/security_only/tests/timelion.ts b/x-pack/test/ui_capabilities/security_only/tests/timelion.ts index 9bd6858486568..d0c7a7f275a28 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/timelion.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/timelion.ts @@ -28,6 +28,10 @@ export default function timelionTests({ getService }: KibanaFunctionalTestDefaul case 'superuser': case 'all': case 'dual_privileges_all': + case 'apm_user_and_all': + case 'machine_learning_admin_and_all': + case 'machine_learning_user_and_all': + case 'monitoring_user_and_all': case 'timelion_all': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('timelion'); @@ -47,7 +51,7 @@ export default function timelionTests({ getService }: KibanaFunctionalTestDefaul // these users can't do anything with Timelion case 'advancedSettings_all': case 'advancedSettings_read': - case 'apm_all': + case 'apm_user': case 'canvas_all': case 'canvas_read': case 'dev_tools_read': @@ -61,8 +65,9 @@ export default function timelionTests({ getService }: KibanaFunctionalTestDefaul case 'maps_read': case 'infrastructure_read': case 'logs_read': - case 'ml_all': - case 'monitoring_all': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': case 'uptime_read': case 'visualize_all': case 'visualize_read': From f3d837660803bbc54a4a2568fe4d569927e5cb05 Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 11 Feb 2019 07:31:56 -0800 Subject: [PATCH 30/62] Being a less noisy neighbor --- .../apps/monitoring/feature_controls/monitoring_spaces.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts index 9320f0dc10f0a..98fc48bfa1d36 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts @@ -20,10 +20,6 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await esArchiver.load('empty_kibana'); }); - after(async () => { - await esArchiver.unload('empty_kibana'); - }); - describe('space with no features disabled', () => { before(async () => { await spacesService.create({ From 0693a06b50d4a66af8b78e5fef9e0c7079258ed1 Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 11 Feb 2019 09:23:05 -0800 Subject: [PATCH 31/62] Forcing logout for apm/dashboard feature controls security tests --- .../apps/apm/feature_controls/apm_security.ts | 4 ++-- .../feature_controls/dashboard_security.ts | 4 ++-- .../functional/page_objects/security_page.js | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts index e96f264ca0201..18405562c7231 100644 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts @@ -31,7 +31,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa }); // ensure we're logged out so we can login as the appropriate users - await PageObjects.security.logout(); + await PageObjects.security.forceLogout(); }); after(async () => { @@ -39,7 +39,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await security.role.delete('global_all_role'); // logout, so the other tests don't accidentally run as the custom users we're testing below - await PageObjects.security.logout(); + await PageObjects.security.forceLogout(); }); describe('apm_user', () => { diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts index 14b73f31fec77..95d9548f6b46e 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts @@ -26,14 +26,14 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await esArchiver.loadIfNeeded('logstash_functional'); // ensure we're logged out so we can login as the appropriate users - await PageObjects.security.logout(); + await PageObjects.security.forceLogout(); }); after(async () => { await esArchiver.unload('dashboard/feature_controls/security'); // logout, so the other tests don't accidentally run as the custom users we're testing below - await PageObjects.security.logout(); + await PageObjects.security.forceLogout(); }); describe('global dashboard all privileges', () => { diff --git a/x-pack/test/functional/page_objects/security_page.js b/x-pack/test/functional/page_objects/security_page.js index 92e5675fc7998..d162d8e2b4c28 100644 --- a/x-pack/test/functional/page_objects/security_page.js +++ b/x-pack/test/functional/page_objects/security_page.js @@ -111,6 +111,22 @@ export function SecurityPageProvider({ getService, getPageObjects }) { )); } + async forceLogout() { + log.debug('SecurityPage.forceLogout'); + if (await find.existsByDisplayedByCssSelector('.login-form', 100)) { + log.debug('Already on the login page, not forcing anything'); + return; + } + + log.debug('Redirecting to /logout to force the logout'); + const url = PageObjects.common.getHostPort() + '/logout'; + await browser.get(url); + log.debug('Waiting on the login form to appear'); + await retry.waitForWithTimeout('login form', config.get('timeouts.waitFor') * 5, async () => ( + await find.existsByDisplayedByCssSelector('.login-form') + )); + } + async clickRolesSection() { await testSubjects.click('roles'); } From 71785e4726493d676a804f8081981896fee48617 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 12 Feb 2019 06:56:37 -0800 Subject: [PATCH 32/62] Fixing the security only ui capability tests --- .../security_only/tests/advanced_settings.ts | 8 ++++---- x-pack/test/ui_capabilities/security_only/tests/canvas.ts | 8 ++++---- x-pack/test/ui_capabilities/security_only/tests/maps.ts | 8 ++++---- .../test/ui_capabilities/security_only/tests/timelion.ts | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/x-pack/test/ui_capabilities/security_only/tests/advanced_settings.ts b/x-pack/test/ui_capabilities/security_only/tests/advanced_settings.ts index 677e4e7b38a7d..a123c582de844 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/advanced_settings.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/advanced_settings.ts @@ -51,7 +51,6 @@ export default function advancedSettingsTests({ }); break; // these users can't do anything with Advanced Settings - case 'apm_user': case 'canvas_all': case 'canvas_read': case 'dev_tools_read': @@ -65,9 +64,6 @@ export default function advancedSettingsTests({ case 'maps_read': case 'infrastructure_read': case 'logs_read': - case 'machine_learning_admin': - case 'machine_learning_user': - case 'monitoring_user': case 'timelion_all': case 'timelion_read': case 'uptime_read': @@ -81,6 +77,10 @@ export default function advancedSettingsTests({ break; case 'legacy_all': case 'no_kibana_privileges': + case 'apm_user': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; diff --git a/x-pack/test/ui_capabilities/security_only/tests/canvas.ts b/x-pack/test/ui_capabilities/security_only/tests/canvas.ts index c0d856dc77368..979560790fd39 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/canvas.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/canvas.ts @@ -51,7 +51,6 @@ export default function canvasTests({ getService }: KibanaFunctionalTestDefaultP // these users can't do anything with Canvas case 'advancedSettings_all': case 'advancedSettings_read': - case 'apm_user': case 'dashboard_all': case 'dashboard_read': case 'dev_tools_read': @@ -61,9 +60,6 @@ export default function canvasTests({ getService }: KibanaFunctionalTestDefaultP case 'graph_read': case 'infrastructure_read': case 'logs_read': - case 'machine_learning_admin': - case 'machine_learning_user': - case 'monitoring_user': case 'maps_all': case 'maps_read': case 'timelion_all': @@ -79,6 +75,10 @@ export default function canvasTests({ getService }: KibanaFunctionalTestDefaultP break; case 'legacy_all': case 'no_kibana_privileges': + case 'apm_user': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; diff --git a/x-pack/test/ui_capabilities/security_only/tests/maps.ts b/x-pack/test/ui_capabilities/security_only/tests/maps.ts index ad2b1f6cca754..089aac45a5f42 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/maps.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/maps.ts @@ -51,7 +51,6 @@ export default function mapsTests({ getService }: KibanaFunctionalTestDefaultPro // these users can't do anything with Advanced Settings case 'advancedSettings_all': case 'advancedSettings_read': - case 'apm_user': case 'canvas_all': case 'canvas_read': case 'dev_tools_read': @@ -63,9 +62,6 @@ export default function mapsTests({ getService }: KibanaFunctionalTestDefaultPro case 'graph_read': case 'infrastructure_read': case 'logs_read': - case 'machine_learning_admin': - case 'machine_learning_user': - case 'monitoring_user': case 'timelion_all': case 'timelion_read': case 'uptime_read': @@ -79,6 +75,10 @@ export default function mapsTests({ getService }: KibanaFunctionalTestDefaultPro break; case 'legacy_all': case 'no_kibana_privileges': + case 'apm_user': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; diff --git a/x-pack/test/ui_capabilities/security_only/tests/timelion.ts b/x-pack/test/ui_capabilities/security_only/tests/timelion.ts index d0c7a7f275a28..ed54dabf6998f 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/timelion.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/timelion.ts @@ -51,7 +51,6 @@ export default function timelionTests({ getService }: KibanaFunctionalTestDefaul // these users can't do anything with Timelion case 'advancedSettings_all': case 'advancedSettings_read': - case 'apm_user': case 'canvas_all': case 'canvas_read': case 'dev_tools_read': @@ -65,9 +64,6 @@ export default function timelionTests({ getService }: KibanaFunctionalTestDefaul case 'maps_read': case 'infrastructure_read': case 'logs_read': - case 'machine_learning_admin': - case 'machine_learning_user': - case 'monitoring_user': case 'uptime_read': case 'visualize_all': case 'visualize_read': @@ -79,6 +75,10 @@ export default function timelionTests({ getService }: KibanaFunctionalTestDefaul break; case 'legacy_all': case 'no_kibana_privileges': + case 'apm_user': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; From cd3b11a93e5301ed3e88f99fbf66b3822b1bba4b Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 12 Feb 2019 07:24:31 -0800 Subject: [PATCH 33/62] Removing test that monitoring now tests itself --- .../test/functional/apps/security/secure_roles_perm.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/x-pack/test/functional/apps/security/secure_roles_perm.js b/x-pack/test/functional/apps/security/secure_roles_perm.js index 587ee3b49a435..ef1bb281e7257 100644 --- a/x-pack/test/functional/apps/security/secure_roles_perm.js +++ b/x-pack/test/functional/apps/security/secure_roles_perm.js @@ -64,15 +64,6 @@ export default function ({ getService, getPageObjects }) { await PageObjects.security.login('Rashmi', 'changeme'); }); - //Verify the Access Denied message is displayed - it('Kibana User navigating to Monitoring gets Access Denied', async function () { - const expectedMessage = 'Access Denied'; - await PageObjects.monitoring.navigateTo(); - const actualMessage = await PageObjects.monitoring.getAccessDeniedMessage(); - expect(actualMessage).to.be(expectedMessage); - }); - - it('Kibana User navigating to Management gets permission denied', async function () { await PageObjects.settings.navigateTo(); await PageObjects.security.clickElasticsearchUsers(); From b3a048e20de504c97c3e657f330bb55ab1b8674a Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 15 Feb 2019 09:24:11 -0800 Subject: [PATCH 34/62] Fixing some ui capability tests --- .../ui_capabilities/security_only/tests/dashboard.ts | 4 ++++ .../ui_capabilities/security_only/tests/dev_tools.ts | 8 ++++++++ .../ui_capabilities/security_only/tests/discover.ts | 6 +++++- .../test/ui_capabilities/security_only/tests/graph.ts | 10 +++++++++- .../ui_capabilities/security_only/tests/visualize.ts | 8 ++++++++ 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/x-pack/test/ui_capabilities/security_only/tests/dashboard.ts b/x-pack/test/ui_capabilities/security_only/tests/dashboard.ts index bb6a6b8b1cba6..7a01d33675b38 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/dashboard.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/dashboard.ts @@ -55,6 +55,10 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul // these users have no access to even get the ui capabilities case 'legacy_all': case 'no_kibana_privileges': + case 'apm_user': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; diff --git a/x-pack/test/ui_capabilities/security_only/tests/dev_tools.ts b/x-pack/test/ui_capabilities/security_only/tests/dev_tools.ts index 0351799377a6b..417ef3cc0a59f 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/dev_tools.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/dev_tools.ts @@ -27,8 +27,12 @@ export default function devToolsTests({ getService }: KibanaFunctionalTestDefaul // these users have a read/write view of Dev Tools case 'superuser': case 'all': + case 'apm_user_and_all': case 'dual_privileges_all': case 'dev_tools_all': + case 'machine_learning_admin_and_all': + case 'machine_learning_user_and_all': + case 'monitoring_user_and_all': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('dev_tools'); expect(uiCapabilities.value!.dev_tools).to.eql({ @@ -49,6 +53,10 @@ export default function devToolsTests({ getService }: KibanaFunctionalTestDefaul // these users have no access to even get the ui capabilities case 'legacy_all': case 'no_kibana_privileges': + case 'apm_user': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; diff --git a/x-pack/test/ui_capabilities/security_only/tests/discover.ts b/x-pack/test/ui_capabilities/security_only/tests/discover.ts index a7ca8e3c81f06..eba4bf87e4e26 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/discover.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/discover.ts @@ -57,8 +57,12 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul expect(capabilities.catalogue.discover).to.eql(true); break; // these users have no access to even get the ui capabilities - case 'no_kibana_privileges': case 'legacy_all': + case 'no_kibana_privileges': + case 'apm_user': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; diff --git a/x-pack/test/ui_capabilities/security_only/tests/graph.ts b/x-pack/test/ui_capabilities/security_only/tests/graph.ts index 893e2e56b6411..402e658e1b05f 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/graph.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/graph.ts @@ -31,8 +31,12 @@ export default function graphTests({ getService }: KibanaFunctionalTestDefaultPr // these users have a read/write view of Graph case 'superuser': case 'all': + case 'apm_user_and_all': case 'dual_privileges_all': case 'graph_all': + case 'machine_learning_admin_and_all': + case 'machine_learning_user_and_all': + case 'monitoring_user_and_all': expect(uiCapabilities.success).to.be(true); expect(capabilities).to.have.property('graph'); expect(capabilities!.graph).to.eql({ @@ -51,8 +55,12 @@ export default function graphTests({ getService }: KibanaFunctionalTestDefaultPr }); break; // these users have no access to even get the ui capabilities - case 'no_kibana_privileges': case 'legacy_all': + case 'no_kibana_privileges': + case 'apm_user': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; diff --git a/x-pack/test/ui_capabilities/security_only/tests/visualize.ts b/x-pack/test/ui_capabilities/security_only/tests/visualize.ts index 73d3128b11c83..9e5ba051e9dbb 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/visualize.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/visualize.ts @@ -27,7 +27,11 @@ export default function visualizeTests({ getService }: KibanaFunctionalTestDefau // these users have a read/write view of Visualize case 'superuser': case 'all': + case 'apm_user_and_all': case 'dual_privileges_all': + case 'machine_learning_admin_and_all': + case 'machine_learning_user_and_all': + case 'monitoring_user_and_all': case 'visualize_all': expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('visualize'); @@ -49,6 +53,10 @@ export default function visualizeTests({ getService }: KibanaFunctionalTestDefau // these users have no access to even get the ui capabilities case 'legacy_all': case 'no_kibana_privileges': + case 'apm_user': + case 'machine_learning_admin': + case 'machine_learning_user': + case 'monitoring_user': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; From 6c28a1e04f6d01154f37ebd2ad1d7c7de1a63d94 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 15 Feb 2019 09:34:41 -0800 Subject: [PATCH 35/62] Cleaning up the error page services --- test/functional/page_objects/error_page.js | 17 +++++++++++++---- .../apps/apm/feature_controls/apm_spaces.ts | 11 ++--------- .../feature_controls/ml_spaces.ts | 15 ++++----------- .../feature_controls/monitoring_spaces.ts | 15 ++++----------- .../functional/page_objects/security_page.js | 12 ++---------- 5 files changed, 25 insertions(+), 45 deletions(-) diff --git a/test/functional/page_objects/error_page.js b/test/functional/page_objects/error_page.js index 945dbcf6161e4..5cfa847e268d8 100644 --- a/test/functional/page_objects/error_page.js +++ b/test/functional/page_objects/error_page.js @@ -18,13 +18,22 @@ */ import expect from 'expect.js'; -export function ErrorPageProvider({ getService }) { - const find = getService('find'); +export function ErrorPageProvider({ getPageObjects }) { + const PageObjects = getPageObjects(['common']); class ErrorPage { + async expectForbidden() { + const messageText = await PageObjects.common.getBodyText(); + expect(messageText).to.eql( + JSON.stringify({ + statusCode: 403, + error: 'Forbidden', + message: 'Forbidden' + }) + ); + } async expectNotFound() { - const el = await find.byCssSelector('body>pre'); - const messageText = await el.getVisibleText(); + const messageText = await PageObjects.common.getBodyText(); expect(messageText).to.eql( JSON.stringify({ statusCode: 404, diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts index 60d8b7a71f043..783c45fedc4af 100644 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts @@ -11,7 +11,7 @@ import { KibanaFunctionalTestDefaultProviders } from '../../../../types/provider export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { const esArchiver = getService('esArchiver'); const spacesService: SpacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'spaceSelector']); + const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'error']); const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); @@ -86,14 +86,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa ensureCurrentUrl: false, }); - const messageText = await PageObjects.common.getBodyText(); - expect(messageText).to.eql( - JSON.stringify({ - statusCode: 404, - error: 'Not Found', - message: 'Not Found', - }) - ); + await PageObjects.error.expectNotFound(); }); }); }); diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts index f5f69db6e5562..5735227716065 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts @@ -11,7 +11,7 @@ import { KibanaFunctionalTestDefaultProviders } from '../../../../types/provider export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { const esArchiver = getService('esArchiver'); const spacesService: SpacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'spaceSelector']); + const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'error']); const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); @@ -37,7 +37,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await spacesService.delete('custom_space'); }); - it('shows apm navlink', async () => { + it('shows Machine Learning navlink', async () => { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); @@ -69,7 +69,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await spacesService.delete('custom_space'); }); - it(`doesn't show apm navlink`, async () => { + it(`doesn't show Machine Learning navlink`, async () => { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); @@ -86,14 +86,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa ensureCurrentUrl: false, }); - const messageText = await PageObjects.common.getBodyText(); - expect(messageText).to.eql( - JSON.stringify({ - statusCode: 404, - error: 'Not Found', - message: 'Not Found', - }) - ); + await PageObjects.error.expectNotFound(); }); }); }); diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts index 98fc48bfa1d36..836e65ca69226 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts @@ -11,7 +11,7 @@ import { KibanaFunctionalTestDefaultProviders } from '../../../../types/provider export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { const esArchiver = getService('esArchiver'); const spacesService: SpacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'spaceSelector']); + const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'error']); const appsMenu = getService('appsMenu'); const find = getService('find'); @@ -33,7 +33,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await spacesService.delete('custom_space'); }); - it('shows stack monitoring navlink', async () => { + it('shows Stack Monitoring navlink', async () => { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); @@ -66,7 +66,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await spacesService.delete('custom_space'); }); - it(`doesn't show stack monitoring navlink`, async () => { + it(`doesn't show Stack Monitoring navlink`, async () => { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); @@ -83,14 +83,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa ensureCurrentUrl: false, }); - const messageText = await PageObjects.common.getBodyText(); - expect(messageText).to.eql( - JSON.stringify({ - statusCode: 404, - error: 'Not Found', - message: 'Not Found', - }) - ); + await PageObjects.error.expectNotFound(); }); }); }); diff --git a/x-pack/test/functional/page_objects/security_page.js b/x-pack/test/functional/page_objects/security_page.js index d162d8e2b4c28..581b831c45648 100644 --- a/x-pack/test/functional/page_objects/security_page.js +++ b/x-pack/test/functional/page_objects/security_page.js @@ -16,7 +16,7 @@ export function SecurityPageProvider({ getService, getPageObjects }) { const testSubjects = getService('testSubjects'); const esArchiver = getService('esArchiver'); const userMenu = getService('userMenu'); - const PageObjects = getPageObjects(['common', 'header', 'settings', 'home']); + const PageObjects = getPageObjects(['common', 'header', 'settings', 'home', 'error']); class LoginPage { async login(username, password, options = {}) { @@ -40,15 +40,7 @@ export function SecurityPageProvider({ getService, getPageObjects }) { log.debug(`Finished login process, landed on space selector. currentUrl = ${await browser.getCurrentUrl()}`); } else if (expectForbidden) { await retry.try(async () => { - const bodyText = await PageObjects.common.getBodyText(); - const forbiddenMessage = { - statusCode: 403, - error: 'Forbidden', - message: 'Forbidden' - }; - if (bodyText !== JSON.stringify(forbiddenMessage)) { - throw new Error(`Expected forbidden message, but didn't find it`); - } + await PageObjects.error.expectForbidden(); }); log.debug(`Finished login process, found forbidden message. currentUrl = ${await browser.getCurrentUrl()}`); } else if (expectSuccess) { From db19c22bcb783fb1a0a0c1eb6bfc51116ace3f3f Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 15 Feb 2019 09:38:54 -0800 Subject: [PATCH 36/62] Fixing misspelling in comment --- .../plugins/security/public/views/management/edit_role/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security/public/views/management/edit_role/index.js b/x-pack/plugins/security/public/views/management/edit_role/index.js index 118edf6ce973f..c51512c938b73 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/index.js +++ b/x-pack/plugins/security/public/views/management/edit_role/index.js @@ -135,7 +135,7 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, { $scope.$$postDigest(async () => { const domNode = document.getElementById('editRoleReactRoot'); - // we filter out the features here which don't have any privileges to simplify the login within + // we filter out the features here which don't have any privileges to simplify the logic within const featuresWithPrivileges = features.filter(feature => Object.keys(feature.privileges).length > 0); render( From 50b5ddd91775efc2650bb4fd7295115e5e023c02 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 15 Feb 2019 11:17:32 -0800 Subject: [PATCH 37/62] Using forceLogout for monitoring --- .../apps/monitoring/feature_controls/monitoring_security.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts index 96465a5811dc3..bd90b5e867db8 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts @@ -31,7 +31,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa }); // ensure we're logged out so we can login as the appropriate users - await PageObjects.security.logout(); + await PageObjects.security.forceLogout(); }); after(async () => { @@ -39,7 +39,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa await security.role.delete('global_all_role'); // logout, so the other tests don't accidentally run as the custom users we're testing below - await PageObjects.security.logout(); + await PageObjects.security.forceLogout(); }); describe('monitoring_user', () => { From 10bbc4eac75853646c60046675b6b1035c8fe20c Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 15 Feb 2019 12:31:48 -0800 Subject: [PATCH 38/62] Removing code that never should have been there, sorry Larry --- .../security/server/lib/authorization/privilege_serializer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts index e578800020a50..14c9350d4bb4d 100644 --- a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts +++ b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts @@ -8,7 +8,7 @@ const featurePrefix = 'feature_'; const spacePrefix = 'space_'; const reservedPrefix = 'reserved_'; const basePrivilegeNames = ['all', 'read']; -const globalBasePrivileges = [...basePrivilegeNames, 'apm', 'ml', 'monitoring']; +const globalBasePrivileges = [...basePrivilegeNames]; const spaceBasePrivileges = basePrivilegeNames.map( privilegeName => `${spacePrefix}${privilegeName}` ); From f72469466931ea50c233db434c4122dd2812937b Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 15 Feb 2019 13:32:15 -0800 Subject: [PATCH 39/62] Less leniency with the get roles --- .../privilege_serializer.test.ts | 37 +++ .../lib/authorization/privilege_serializer.ts | 4 + .../server/routes/api/public/roles/get.js | 35 ++- .../routes/api/public/roles/get.test.js | 297 ++++++++++++++++++ 4 files changed, 368 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts index ef898cbcfad03..4386061a8f690 100644 --- a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts @@ -36,6 +36,43 @@ describe(`#isSerializedSpaceBasePrivilege`, () => { }); }); +describe(`#isSerializedReservedPrivilege`, () => { + ['reserved_foo', 'reserved_bar'].forEach(validValue => { + test(`returns true for '${validValue}'`, () => { + expect(PrivilegeSerializer.isSerializedReservedPrivilege(validValue)).toBe(true); + }); + }); + + [ + 'all', + 'read', + 'space_all', + 'space_reserved', + 'foo_reserved', + 'bar', + 'feature_foo', + 'feature_foo.privilege1', + ].forEach(validValue => { + test(`returns true for '${validValue}'`, () => { + expect(PrivilegeSerializer.isSerializedReservedPrivilege(validValue)).toBe(false); + }); + }); +}); + +describe(`#isSerializedFeaturePrivilege`, () => { + ['feature_foo.privilege1', 'feature_bar.privilege2'].forEach(validValue => { + test(`returns true for '${validValue}'`, () => { + expect(PrivilegeSerializer.isSerializedFeaturePrivilege(validValue)).toBe(true); + }); + }); + + ['all', 'read', 'space_all', 'space_read', 'reserved_foo', 'reserved_bar'].forEach(validValue => { + test(`returns true for '${validValue}'`, () => { + expect(PrivilegeSerializer.isSerializedFeaturePrivilege(validValue)).toBe(false); + }); + }); +}); + describe('#serializeGlobalBasePrivilege', () => { test('throws Error if unrecognized privilege used', () => { expect(() => diff --git a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts index 14c9350d4bb4d..2bbebaa1cc951 100644 --- a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts +++ b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.ts @@ -34,6 +34,10 @@ export class PrivilegeSerializer { return privilegeName.startsWith(reservedPrefix); } + public static isSerializedFeaturePrivilege(privilegeName: string) { + return privilegeName.startsWith(featurePrefix); + } + public static serializeGlobalBasePrivilege(privilegeName: string) { if (!globalBasePrivileges.includes(privilegeName)) { throw new Error('Unrecognized global base privilege'); diff --git a/x-pack/plugins/security/server/routes/api/public/roles/get.js b/x-pack/plugins/security/server/routes/api/public/roles/get.js index de2bee6348ff6..2765f7b77cb27 100644 --- a/x-pack/plugins/security/server/routes/api/public/roles/get.js +++ b/x-pack/plugins/security/server/routes/api/public/roles/get.js @@ -15,6 +15,34 @@ export function initGetRolesApi(server, callWithRequest, routePreCheckLicenseFn, const roleKibanaApplications = roleApplications .filter(roleApplication => roleApplication.application === application); + // if any application entry contains an empty resource, we throw an error + if (roleKibanaApplications.some(entry => entry.resources.length === 0)) { + throw new Error(`ES returned an application entry without resources, can't process this`); + } + + // if space privilege assigned globally, we can't transform these + if (roleKibanaApplications.some(entry => + entry.resources.includes(GLOBAL_RESOURCE) && + entry.privileges.some(privilege => PrivilegeSerializer.isSerializedSpaceBasePrivilege(privilege))) + ) { + return { + success: false + }; + } + + // if global base or reserved privilege assigned at a space, we can't transform these + if (roleKibanaApplications.some(entry => + !entry.resources.includes(GLOBAL_RESOURCE) && + entry.privileges.some(privilege => + PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege) || + PrivilegeSerializer.isSerializedReservedPrivilege(privilege) + )) + ) { + return { + success: false + }; + } + // if any application entry contains the '*' resource in addition to another resource, we can't transform these if (roleKibanaApplications.some(entry => entry.resources.includes(GLOBAL_RESOURCE) && entry.resources.length > 1)) { return { @@ -44,10 +72,7 @@ export function initGetRolesApi(server, callWithRequest, routePreCheckLicenseFn, if (resources.length === 1 && resources[0] === GLOBAL_RESOURCE) { const reservedPrivileges = privileges.filter(privilege => PrivilegeSerializer.isSerializedReservedPrivilege(privilege)); const basePrivileges = privileges.filter(privilege => PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege)); - const featurePrivileges = privileges.filter(privilege => - !PrivilegeSerializer.isSerializedGlobalBasePrivilege(privilege) && - !PrivilegeSerializer.isSerializedReservedPrivilege(privilege) - ); + const featurePrivileges = privileges.filter(privilege => PrivilegeSerializer.isSerializedFeaturePrivilege(privilege)); return { ...reservedPrivileges.length ? { @@ -69,7 +94,7 @@ export function initGetRolesApi(server, callWithRequest, routePreCheckLicenseFn, } const basePrivileges = privileges.filter(privilege => PrivilegeSerializer.isSerializedSpaceBasePrivilege(privilege)); - const featurePrivileges = privileges.filter(privilege => !PrivilegeSerializer.isSerializedSpaceBasePrivilege(privilege)); + const featurePrivileges = privileges.filter(privilege => PrivilegeSerializer.isSerializedFeaturePrivilege(privilege)); return { base: basePrivileges.map(privilege => PrivilegeSerializer.deserializeSpaceBasePrivilege(privilege)), feature: featurePrivileges.reduce((acc, privilege) => { diff --git a/x-pack/plugins/security/server/routes/api/public/roles/get.test.js b/x-pack/plugins/security/server/routes/api/public/roles/get.test.js index 5c454bcab3730..e2e6ff0ebbf4d 100644 --- a/x-pack/plugins/security/server/routes/api/public/roles/get.test.js +++ b/x-pack/plugins/security/server/routes/api/public/roles/get.test.js @@ -647,6 +647,156 @@ describe('GET roles', () => { }, }); + getRolesTest(`space privilege assigned globally returns empty kibana section with _transform_error set to ['kibana']`, { + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['space_all'], + resources: ['*'], + }, + { + application, + privileges: ['space_read'], + resources: ['space:engineering'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: [ + { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + ], + }, + }); + + getRolesTest(`global base privilege assigned at a space returns empty kibana section with _transform_error set to ['kibana']`, { + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['all'], + resources: ['space:marketing'], + }, + { + application, + privileges: ['space_read'], + resources: ['space:engineering'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: [ + { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + ], + }, + }); + + getRolesTest(`reserved privilege assigned at a space returns empty kibana section with _transform_error set to ['kibana']`, { + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['reserved_foo'], + resources: ['space:marketing'], + }, + { + application, + privileges: ['space_read'], + resources: ['space:engineering'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: [ + { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + ], + }, + }); + getRolesTest(`transforms unrecognized applications`, { callWithRequestImpl: async () => ({ first_role: { @@ -1387,6 +1537,153 @@ describe('GET role', () => { }, }); + getRoleTest(`space privilege assigned globally returns empty kibana section with _transform_error set to ['kibana']`, { + name: 'first_role', + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['space_all'], + resources: ['*'], + }, + { + application, + privileges: ['space_read'], + resources: ['space:engineering'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + }, + }); + + getRoleTest(`global base privilege assigned at a space returns empty kibana section with _transform_error set to ['kibana']`, { + name: 'first_role', + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['all'], + resources: ['space:marketing'], + }, + { + application, + privileges: ['space_read'], + resources: ['space:engineering'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + }, + }); + + getRoleTest(`reserved privilege assigned at a space returns empty kibana section with _transform_error set to ['kibana']`, { + name: 'first_role', + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['reserved_foo'], + resources: ['space:marketing'], + }, + { + application, + privileges: ['space_read'], + resources: ['space:engineering'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + }, + }); + getRoleTest(`transforms unrecognized applications`, { name: 'first_role', callWithRequestImpl: async () => ({ From fa44f6644febf5cfe557d601dc2de7ef3d679d30 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 15 Feb 2019 13:35:00 -0800 Subject: [PATCH 40/62] Barely alphabetical for a bit --- x-pack/test/functional/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index f9d62acfae7b7..b34fd6fb5f600 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -81,8 +81,8 @@ export default async function ({ readConfigFile }) { return { // list paths to the files that contain your plugins tests testFiles: [ - resolve(__dirname, './apps/apm'), resolve(__dirname, './apps/advanced_settings'), + resolve(__dirname, './apps/apm'), resolve(__dirname, './apps/canvas'), resolve(__dirname, './apps/graph'), resolve(__dirname, './apps/monitoring'), From 1e4ca00c35c785919bb843ba54d690a1f4d89ac4 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Fri, 15 Feb 2019 14:17:39 -0800 Subject: [PATCH 41/62] Apply suggestions from code review Co-Authored-By: kobelb --- .../lib/authorization/privilege_serializer.test.ts | 12 ++++++------ .../apps/apm/feature_controls/apm_spaces.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts index 4386061a8f690..4035a43ae02c8 100644 --- a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts @@ -52,9 +52,9 @@ describe(`#isSerializedReservedPrivilege`, () => { 'bar', 'feature_foo', 'feature_foo.privilege1', - ].forEach(validValue => { - test(`returns true for '${validValue}'`, () => { - expect(PrivilegeSerializer.isSerializedReservedPrivilege(validValue)).toBe(false); + ].forEach(invalidValue => { + test(`returns false for '${invalidValue}'`, () => { + expect(PrivilegeSerializer.isSerializedReservedPrivilege(invalidValue)).toBe(false); }); }); }); @@ -66,9 +66,9 @@ describe(`#isSerializedFeaturePrivilege`, () => { }); }); - ['all', 'read', 'space_all', 'space_read', 'reserved_foo', 'reserved_bar'].forEach(validValue => { - test(`returns true for '${validValue}'`, () => { - expect(PrivilegeSerializer.isSerializedFeaturePrivilege(validValue)).toBe(false); + ['all', 'read', 'space_all', 'space_read', 'reserved_foo', 'reserved_bar'].forEach(invalidValue => { + test(`returns false for '${invalidValue}'`, () => { + expect(PrivilegeSerializer.isSerializedFeaturePrivilege(invalidValue)).toBe(false); }); }); }); diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts index 783c45fedc4af..81032a597aa0e 100644 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts @@ -52,7 +52,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa basePath: '/s/custom_space', }); - await testSubjects.existOrFail('apm-main-container', 10000); + await testSubjects.existOrFail('apm-main-container'); }); }); From 3a5033be992538f403008781638aa6db489f917f Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 15 Feb 2019 14:18:08 -0800 Subject: [PATCH 42/62] Removing errant timeout --- .../apps/machine_learning/feature_controls/ml_spaces.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts index 5735227716065..9deb7eea1f147 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts @@ -52,7 +52,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa basePath: '/s/custom_space', }); - await testSubjects.existOrFail('ml-jobs-list', 10000); + await testSubjects.existOrFail('ml-jobs-list'); }); }); From 03ee483829d7d4eb049d13dba6d6bb29b34248cc Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 15 Feb 2019 14:19:13 -0800 Subject: [PATCH 43/62] No more hard coded esFrom source --- test/scripts/jenkins_xpack_ci_group.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scripts/jenkins_xpack_ci_group.sh b/test/scripts/jenkins_xpack_ci_group.sh index 617bbd52681e3..f6404bb2792f0 100755 --- a/test/scripts/jenkins_xpack_ci_group.sh +++ b/test/scripts/jenkins_xpack_ci_group.sh @@ -39,6 +39,6 @@ tar -xzf "$linuxBuild" -C "$installDir" --strip=1 export TEST_ES_FROM=${TEST_ES_FROM:-source} echo " -> Running functional and api tests" cd "$XPACK_DIR" -node scripts/functional_tests --debug --bail --kibana-install-dir "$installDir" --include-tag "ciGroup$CI_GROUP" --esFrom "source" +node scripts/functional_tests --debug --bail --kibana-install-dir "$installDir" --include-tag "ciGroup$CI_GROUP" echo "" echo "" From 06712092196f1275af79ad2db0cf8a11461ddfac Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 15 Feb 2019 14:21:47 -0800 Subject: [PATCH 44/62] More nits --- .../authorization/privilege_serializer.test.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts index 4035a43ae02c8..ecfe0d34fdbcb 100644 --- a/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privilege_serializer.test.ts @@ -29,9 +29,9 @@ describe(`#isSerializedSpaceBasePrivilege`, () => { }); }); - ['all', 'read', 'foo', 'bar', 'feature_foo', 'feature_foo.privilege1'].forEach(validValue => { - test(`returns true for '${validValue}'`, () => { - expect(PrivilegeSerializer.isSerializedSpaceBasePrivilege(validValue)).toBe(false); + ['all', 'read', 'foo', 'bar', 'feature_foo', 'feature_foo.privilege1'].forEach(invalid => { + test(`returns false for '${invalid}'`, () => { + expect(PrivilegeSerializer.isSerializedSpaceBasePrivilege(invalid)).toBe(false); }); }); }); @@ -66,11 +66,13 @@ describe(`#isSerializedFeaturePrivilege`, () => { }); }); - ['all', 'read', 'space_all', 'space_read', 'reserved_foo', 'reserved_bar'].forEach(invalidValue => { - test(`returns false for '${invalidValue}'`, () => { - expect(PrivilegeSerializer.isSerializedFeaturePrivilege(invalidValue)).toBe(false); - }); - }); + ['all', 'read', 'space_all', 'space_read', 'reserved_foo', 'reserved_bar'].forEach( + invalidValue => { + test(`returns false for '${invalidValue}'`, () => { + expect(PrivilegeSerializer.isSerializedFeaturePrivilege(invalidValue)).toBe(false); + }); + } + ); }); describe('#serializeGlobalBasePrivilege', () => { From b71cd2709eff34c46d6cc5872816bdca1b211862 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 15 Feb 2019 16:31:42 -0800 Subject: [PATCH 45/62] Adding back esFrom source --- test/scripts/jenkins_xpack_ci_group.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scripts/jenkins_xpack_ci_group.sh b/test/scripts/jenkins_xpack_ci_group.sh index f6404bb2792f0..617bbd52681e3 100755 --- a/test/scripts/jenkins_xpack_ci_group.sh +++ b/test/scripts/jenkins_xpack_ci_group.sh @@ -39,6 +39,6 @@ tar -xzf "$linuxBuild" -C "$installDir" --strip=1 export TEST_ES_FROM=${TEST_ES_FROM:-source} echo " -> Running functional and api tests" cd "$XPACK_DIR" -node scripts/functional_tests --debug --bail --kibana-install-dir "$installDir" --include-tag "ciGroup$CI_GROUP" +node scripts/functional_tests --debug --bail --kibana-install-dir "$installDir" --include-tag "ciGroup$CI_GROUP" --esFrom "source" echo "" echo "" From 11ed79a1413ac56e7f419d15710b6023154c5eb9 Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 15 Mar 2019 13:51:33 -0700 Subject: [PATCH 46/62] APM no longer uses reserved privileges, reserved privileges are pluggable --- x-pack/plugins/ml/index.js | 7 + x-pack/plugins/monitoring/init.js | 7 + .../privileges/privileges.test.ts | 229 ++++++++++++++++-- .../authorization/privileges/privileges.ts | 29 +-- .../feature_registry/feature_registry.test.ts | 13 + .../lib/feature_registry/feature_registry.ts | 48 ++-- .../apis/security/privileges.ts | 76 +----- .../api_integration/apis/security/roles.js | 8 +- .../apps/apm/feature_controls/apm_security.ts | 111 --------- .../apps/apm/feature_controls/apm_spaces.ts | 93 ------- .../apps/apm/feature_controls/index.ts | 14 -- x-pack/test/functional/apps/apm/index.ts | 15 -- x-pack/test/functional/config.js | 1 - 13 files changed, 283 insertions(+), 368 deletions(-) delete mode 100644 x-pack/test/functional/apps/apm/feature_controls/apm_security.ts delete mode 100644 x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts delete mode 100644 x-pack/test/functional/apps/apm/feature_controls/index.ts delete mode 100644 x-pack/test/functional/apps/apm/index.ts diff --git a/x-pack/plugins/ml/index.js b/x-pack/plugins/ml/index.js index 3bc853d24921a..6995aeeaf7764 100644 --- a/x-pack/plugins/ml/index.js +++ b/x-pack/plugins/ml/index.js @@ -89,6 +89,13 @@ export const ml = (kibana) => { app: ['ml', 'kibana'], catalogue: ['ml'], privileges: {}, + reservedPrivilege: { + savedObject: { + all: [], + read: ['config'] + }, + ui: [], + } }); // Add server routes and initialize the plugin here diff --git a/x-pack/plugins/monitoring/init.js b/x-pack/plugins/monitoring/init.js index a01552a031b6b..4997213837ba8 100644 --- a/x-pack/plugins/monitoring/init.js +++ b/x-pack/plugins/monitoring/init.js @@ -65,6 +65,13 @@ export const init = (monitoringPlugin, server) => { app: ['monitoring', 'kibana'], catalogue: ['monitoring'], privileges: {}, + reservedPrivilege: { + savedObject: { + all: [], + read: ['config'] + }, + ui: [], + } }); const bulkUploader = initBulkUploader(kbnServer, server); diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts index cf4e25d67b669..b7b32c08895a5 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts @@ -703,12 +703,70 @@ describe('features', () => { actions.ui.get('foo', 'read-ui-2'), ]); }); + + test('actions defined in a reserved privilege are not included in `all` or `read`', () => { + const features: Feature[] = [ + { + id: 'foo', + name: 'Foo Feature', + icon: 'arrowDown', + navLinkId: 'kibana:foo', + app: [], + catalogue: ['ignore-me-1', 'ignore-me-2'], + management: { + foo: ['ignore-me-1', 'ignore-me-2'], + }, + privileges: {}, + reservedPrivilege: { + savedObject: { + all: ['ignore-me-1', 'ignore-me-2'], + read: ['ignore-me-1', 'ignore-me-2'], + }, + ui: ['ignore-me-1'], + }, + }, + ]; + + const mockXPackMainPlugin = { + getFeatures: jest.fn().mockReturnValue(features), + }; + + const privileges = privilegesFactory(actions, mockXPackMainPlugin as any); + + const actual = privileges.get(); + expect(actual).toHaveProperty(`${group}.all`, [ + actions.login, + actions.version, + ...(expectManageSpaces ? [actions.space.manage, actions.ui.get('spaces', 'manage')] : []), + ]); + expect(actual).toHaveProperty(`${group}.read`, [actions.login, actions.version]); + }); }); }); describe('reserved', () => { - test(`are hard-coded and not based on features`, () => { - const features: Feature[] = []; + test('actions defined at the feature cascade to the privileges', () => { + const features: Feature[] = [ + { + id: 'foo', + name: 'Foo Feature', + icon: 'arrowDown', + navLinkId: 'kibana:foo', + app: ['app-1', 'app-2'], + catalogue: ['catalogue-1', 'catalogue-2'], + management: { + foo: ['management-1', 'management-2'], + }, + privileges: {}, + reservedPrivilege: { + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + }, + ]; const mockXPackMainPlugin = { getFeatures: jest.fn().mockReturnValue(features), @@ -717,28 +775,149 @@ describe('reserved', () => { const privileges = privilegesFactory(actions, mockXPackMainPlugin as any); const actual = privileges.get(); - expect(actual).toHaveProperty('reserved', { - apm: [ - actions.version, - actions.app.get('apm'), - ...actions.savedObject.readOperations('config'), - actions.ui.get('catalogue', 'apm'), - actions.ui.get('navLinks', 'apm'), - ], - ml: [ - actions.version, - actions.app.get('ml'), - ...actions.savedObject.readOperations('config'), - actions.ui.get('catalogue', 'ml'), - actions.ui.get('navLinks', 'ml'), - ], - monitoring: [ - actions.version, - actions.app.get('monitoring'), - ...actions.savedObject.readOperations('config'), - actions.ui.get('catalogue', 'monitoring'), - actions.ui.get('navLinks', 'monitoring'), - ], - }); + expect(actual).toHaveProperty('reserved.foo', [ + actions.version, + actions.app.get('app-1'), + actions.app.get('app-2'), + actions.ui.get('catalogue', 'catalogue-1'), + actions.ui.get('catalogue', 'catalogue-2'), + actions.ui.get('management', 'foo', 'management-1'), + actions.ui.get('management', 'foo', 'management-2'), + actions.ui.get('navLinks', 'kibana:foo'), + ]); + }); + + test('actions defined at the reservedPrivilege take precedence', () => { + const features: Feature[] = [ + { + id: 'foo', + name: 'Foo Feature', + icon: 'arrowDown', + app: ['ignore-me-1', 'ignore-me-2'], + catalogue: ['ignore-me-1', 'ignore-me-2'], + management: { + foo: ['ignore-me-1', 'ignore-me-2'], + }, + privileges: {}, + reservedPrivilege: { + app: ['app-1', 'app-2'], + catalogue: ['catalogue-1', 'catalogue-2'], + management: { + bar: ['management-1', 'management-2'], + }, + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + }, + ]; + + const mockXPackMainPlugin = { + getFeatures: jest.fn().mockReturnValue(features), + }; + + const privileges = privilegesFactory(actions, mockXPackMainPlugin as any); + + const actual = privileges.get(); + expect(actual).toHaveProperty('reserved.foo', [ + actions.version, + actions.app.get('app-1'), + actions.app.get('app-2'), + actions.ui.get('catalogue', 'catalogue-1'), + actions.ui.get('catalogue', 'catalogue-2'), + actions.ui.get('management', 'bar', 'management-1'), + actions.ui.get('management', 'bar', 'management-2'), + ]); + }); + + test(`actions only specified at the privilege are alright too`, () => { + const features: Feature[] = [ + { + id: 'foo', + name: 'Foo Feature', + icon: 'arrowDown', + app: [], + privileges: {}, + reservedPrivilege: { + savedObject: { + all: ['savedObject-all-1', 'savedObject-all-2'], + read: ['savedObject-read-1', 'savedObject-read-2'], + }, + ui: ['ui-1', 'ui-2'], + }, + }, + ]; + + const mockXPackMainPlugin = { + getFeatures: jest.fn().mockReturnValue(features), + }; + + const privileges = privilegesFactory(actions, mockXPackMainPlugin as any); + + const actual = privileges.get(); + expect(actual).toHaveProperty('reserved.foo', [ + actions.version, + actions.savedObject.get('savedObject-all-1', 'bulk_get'), + actions.savedObject.get('savedObject-all-1', 'get'), + actions.savedObject.get('savedObject-all-1', 'find'), + actions.savedObject.get('savedObject-all-1', 'create'), + actions.savedObject.get('savedObject-all-1', 'bulk_create'), + actions.savedObject.get('savedObject-all-1', 'update'), + actions.savedObject.get('savedObject-all-1', 'delete'), + actions.savedObject.get('savedObject-all-2', 'bulk_get'), + actions.savedObject.get('savedObject-all-2', 'get'), + actions.savedObject.get('savedObject-all-2', 'find'), + actions.savedObject.get('savedObject-all-2', 'create'), + actions.savedObject.get('savedObject-all-2', 'bulk_create'), + actions.savedObject.get('savedObject-all-2', 'update'), + actions.savedObject.get('savedObject-all-2', 'delete'), + actions.savedObject.get('savedObject-read-1', 'bulk_get'), + actions.savedObject.get('savedObject-read-1', 'get'), + actions.savedObject.get('savedObject-read-1', 'find'), + actions.savedObject.get('savedObject-read-2', 'bulk_get'), + actions.savedObject.get('savedObject-read-2', 'get'), + actions.savedObject.get('savedObject-read-2', 'find'), + actions.ui.get('savedObjectsManagement', 'savedObject-all-1', 'delete'), + actions.ui.get('savedObjectsManagement', 'savedObject-all-1', 'edit'), + actions.ui.get('savedObjectsManagement', 'savedObject-all-1', 'read'), + actions.ui.get('savedObjectsManagement', 'savedObject-all-2', 'delete'), + actions.ui.get('savedObjectsManagement', 'savedObject-all-2', 'edit'), + actions.ui.get('savedObjectsManagement', 'savedObject-all-2', 'read'), + actions.ui.get('savedObjectsManagement', 'savedObject-read-1', 'read'), + actions.ui.get('savedObjectsManagement', 'savedObject-read-2', 'read'), + actions.ui.get('foo', 'ui-1'), + actions.ui.get('foo', 'ui-2'), + ]); + }); + + test(`features with no reservedPrivileges aren't listed`, () => { + const features: Feature[] = [ + { + id: 'foo', + name: 'Foo Feature', + icon: 'arrowDown', + app: [], + privileges: { + all: { + savedObject: { + all: [], + read: [], + }, + ui: ['foo'], + }, + }, + }, + ]; + + const mockXPackMainPlugin = { + getFeatures: jest.fn().mockReturnValue(features), + }; + + const privileges = privilegesFactory(actions, mockXPackMainPlugin as any); + + const actual = privileges.get(); + expect(actual).not.toHaveProperty('reserved.foo'); }); }); diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts index 5cdacb56ee4d1..41afcbee96fb7 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts @@ -92,22 +92,19 @@ export function privilegesFactory(actions: Actions, xpackMainPlugin: XPackMainPl all: [actions.login, actions.version, ...allActions], read: [actions.login, actions.version, ...readActions], }, - reserved: { - ml: [ - actions.version, - actions.app.get('ml'), - ...actions.savedObject.readOperations('config'), - actions.ui.get('catalogue', 'ml'), - actions.ui.get('navLinks', 'ml'), - ], - monitoring: [ - actions.version, - actions.app.get('monitoring'), - ...actions.savedObject.readOperations('config'), - actions.ui.get('catalogue', 'monitoring'), - actions.ui.get('navLinks', 'monitoring'), - ], - }, + reserved: features.reduce((acc: Record, feature: Feature) => { + if (feature.reservedPrivilege) { + acc[feature.id] = [ + actions.version, + ...flatten( + featurePrivilegeBuilders.map(featurePrivilegeBuilder => + featurePrivilegeBuilder.getActions(feature.reservedPrivilege!, feature) + ) + ), + ]; + } + return acc; + }, {}), }; }, }; diff --git a/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts b/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts index f11165465f821..c87be92a31633 100644 --- a/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts +++ b/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts @@ -55,6 +55,19 @@ describe('FeatureRegistry', () => { }, }, privilegesTooltip: 'some fancy tooltip', + reservedPrivilege: { + catalogue: ['foo'], + management: { + foo: ['bar'], + }, + app: ['app1'], + savedObject: { + all: ['config', 'space', 'etc'], + read: ['canvas'], + }, + api: ['someApiEndpointTag', 'anotherEndpointTag'], + ui: ['allowsFoo', 'showBar', 'showBaz'], + }, }; const featureRegistry = new FeatureRegistry(); diff --git a/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts b/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts index 3fbe2dd2ba43b..25780d54c1e81 100644 --- a/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts +++ b/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts @@ -44,6 +44,7 @@ export interface Feature = Privileges catalogue?: string[]; privileges: TPrivileges; privilegesTooltip?: string; + reservedPrivilege?: FeatureKibanaPrivileges; } // Each feature gets its own property on the UICapabilities object, @@ -60,6 +61,25 @@ const managementSchema = Joi.object().pattern( ); const catalogueSchema = Joi.array().items(Joi.string()); +const privilegeSchema = Joi.object({ + grantWithBaseRead: Joi.bool(), + 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) @@ -75,30 +95,12 @@ const schema = Joi.object({ .required(), management: managementSchema, catalogue: catalogueSchema, - privileges: Joi.object() - .pattern( - /^(read|all)$/, - Joi.object({ - grantWithBaseRead: Joi.bool(), - 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(), - }) - ) - .required(), + privileges: Joi.object({ + all: privilegeSchema, + read: privilegeSchema, + }).required(), privilegesTooltip: Joi.string(), + reservedPrivilege: privilegeSchema, }); export class FeatureRegistry { diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts index f7d9582cf2478..641a7a5e44709 100644 --- a/x-pack/test/api_integration/apis/security/privileges.ts +++ b/x-pack/test/api_integration/apis/security/privileges.ts @@ -452,34 +452,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'ui:savedObjectsManagement/graph-workspace/read', ], }, - monitoring: { - all: [ - 'login:', - `version:${version}`, - 'app:monitoring', - 'app:kibana', - 'ui:catalogue/monitoring', - 'ui:navLinks/monitoring', - 'saved_object:config/bulk_get', - 'saved_object:config/get', - 'saved_object:config/find', - 'ui:savedObjectsManagement/config/read', - ], - }, - ml: { - all: [ - 'login:', - `version:${version}`, - 'app:ml', - 'app:kibana', - 'ui:catalogue/ml', - 'ui:navLinks/ml', - 'saved_object:config/bulk_get', - 'saved_object:config/get', - 'saved_object:config/find', - 'ui:savedObjectsManagement/config/read', - ], - }, apm: { all: [ 'login:', @@ -836,12 +808,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'ui:savedObjectsManagement/graph-workspace/read', 'ui:graph/save', 'ui:graph/delete', - 'app:monitoring', - 'ui:catalogue/monitoring', - 'ui:navLinks/monitoring', - 'app:ml', - 'ui:catalogue/ml', - 'ui:navLinks/ml', 'app:apm', 'ui:catalogue/apm', 'ui:navLinks/apm', @@ -959,12 +925,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:graph-workspace/get', 'saved_object:graph-workspace/find', 'ui:savedObjectsManagement/graph-workspace/read', - 'app:monitoring', - 'ui:catalogue/monitoring', - 'ui:navLinks/monitoring', - 'app:ml', - 'ui:catalogue/ml', - 'ui:navLinks/ml', 'app:apm', 'ui:catalogue/apm', 'ui:navLinks/apm', @@ -1120,12 +1080,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'ui:savedObjectsManagement/graph-workspace/read', 'ui:graph/save', 'ui:graph/delete', - 'app:monitoring', - 'ui:catalogue/monitoring', - 'ui:navLinks/monitoring', - 'app:ml', - 'ui:catalogue/ml', - 'ui:navLinks/ml', 'app:apm', 'ui:catalogue/apm', 'ui:navLinks/apm', @@ -1243,12 +1197,6 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { 'saved_object:graph-workspace/get', 'saved_object:graph-workspace/find', 'ui:savedObjectsManagement/graph-workspace/read', - 'app:monitoring', - 'ui:catalogue/monitoring', - 'ui:navLinks/monitoring', - 'app:ml', - 'ui:catalogue/ml', - 'ui:navLinks/ml', 'app:apm', 'ui:catalogue/apm', 'ui:navLinks/apm', @@ -1281,32 +1229,27 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { ], }, reserved: { - apm: [ + monitoring: [ `version:${version}`, - 'app:apm', + 'app:monitoring', + 'app:kibana', + 'ui:catalogue/monitoring', + 'ui:navLinks/monitoring', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'ui:catalogue/apm', - 'ui:navLinks/apm', + 'ui:savedObjectsManagement/config/read', ], ml: [ `version:${version}`, 'app:ml', - 'saved_object:config/bulk_get', - 'saved_object:config/get', - 'saved_object:config/find', + 'app:kibana', 'ui:catalogue/ml', 'ui:navLinks/ml', - ], - monitoring: [ - `version:${version}`, - 'app:monitoring', 'saved_object:config/bulk_get', 'saved_object:config/get', 'saved_object:config/find', - 'ui:catalogue/monitoring', - 'ui:navLinks/monitoring', + 'ui:savedObjectsManagement/config/read', ], }, }); @@ -1333,10 +1276,11 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) { infrastructure: ['all', 'read'], logs: ['all', 'read'], uptime: ['all', 'read'], + apm: ['all'], }, global: ['all', 'read'], space: ['all', 'read'], - reserved: ['apm', 'ml', 'monitoring'], + reserved: ['monitoring', 'ml'], }); }); }); diff --git a/x-pack/test/api_integration/apis/security/roles.js b/x-pack/test/api_integration/apis/security/roles.js index 3e6e816ced90a..26d9e0d4f7c83 100644 --- a/x-pack/test/api_integration/apis/security/roles.js +++ b/x-pack/test/api_integration/apis/security/roles.js @@ -107,7 +107,7 @@ export default function ({ getService }) { .send({ kibana: [ { - _reserved: ['apm', 'ml', 'monitoring'] + _reserved: ['ml', 'monitoring'] } ] }) @@ -121,7 +121,7 @@ export default function ({ getService }) { applications: [ { application: 'kibana-.kibana', - privileges: ['reserved_apm', 'reserved_ml', 'reserved_monitoring'], + privileges: ['reserved_ml', 'reserved_monitoring'], resources: ['*'], } ], @@ -195,7 +195,7 @@ export default function ({ getService }) { }, kibana: [ { - _reserved: ['apm', 'monitoring'], + _reserved: ['monitoring'], base: ['read'], feature: { dashboard: ['read'], @@ -235,7 +235,7 @@ export default function ({ getService }) { applications: [ { application: 'kibana-.kibana', - privileges: ['read', 'feature_dashboard.read', 'feature_dev_tools.all', 'reserved_apm', 'reserved_monitoring'], + privileges: ['read', 'feature_dashboard.read', 'feature_dev_tools.all', 'reserved_monitoring'], resources: ['*'], }, { diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts deleted file mode 100644 index 18405562c7231..0000000000000 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts +++ /dev/null @@ -1,111 +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 expect from 'expect.js'; -import { SecurityService } from 'x-pack/test/common/services'; -import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; - -// tslint:disable:no-default-export -export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { - const esArchiver = getService('esArchiver'); - const security: SecurityService = getService('security'); - const appsMenu = getService('appsMenu'); - const PageObjects = getPageObjects(['common', 'security']); - - describe('security', () => { - before(async () => { - await esArchiver.load('empty_kibana'); - - await security.role.create('global_all_role', { - elasticsearch: { - indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }], - }, - kibana: [ - { - base: ['all'], - spaces: ['*'], - }, - ], - }); - - // ensure we're logged out so we can login as the appropriate users - await PageObjects.security.forceLogout(); - }); - - after(async () => { - await esArchiver.unload('empty_kibana'); - await security.role.delete('global_all_role'); - - // logout, so the other tests don't accidentally run as the custom users we're testing below - await PageObjects.security.forceLogout(); - }); - - describe('apm_user', () => { - before(async () => { - await security.user.create('apm_user', { - password: 'apm_user-password', - roles: ['apm_user'], - full_name: 'apm user', - }); - }); - - after(async () => { - await security.user.delete('apm_user'); - }); - - it('gets forbidden after login', async () => { - await PageObjects.security.login('apm_user', 'apm_user-password', { - expectForbidden: true, - }); - }); - }); - - describe('global all', () => { - before(async () => { - await security.user.create('global_all', { - password: 'global_all-password', - roles: ['global_all_role'], - full_name: 'global all', - }); - - await PageObjects.security.login('global_all', 'global_all-password'); - }); - - after(async () => { - await security.user.delete('global_all'); - }); - - it(`doens't show apm navlink`, async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); - expect(navLinks).not.to.contain('APM'); - }); - }); - - describe('apm_user and global all', () => { - before(async () => { - await security.user.create('apm_user', { - password: 'apm_user-password', - roles: ['apm_user', 'global_all_role'], - full_name: 'apm user', - }); - - await PageObjects.security.login('apm_user', 'apm_user-password'); - }); - - after(async () => { - await security.user.delete('apm_user'); - }); - - it('shows apm navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); - expect(navLinks).to.contain('APM'); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts deleted file mode 100644 index 3f208453c410f..0000000000000 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts +++ /dev/null @@ -1,93 +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 expect from 'expect.js'; -import { SpacesService } from 'x-pack/test/common/services'; -import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; - -// tslint:disable:no-default-export -export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { - const esArchiver = getService('esArchiver'); - const spacesService: SpacesService = getService('spaces'); - const PageObjects = getPageObjects(['common', 'dashboard', 'security', 'error']); - const appsMenu = getService('appsMenu'); - const testSubjects = getService('testSubjects'); - - describe('spaces', () => { - before(async () => { - await esArchiver.load('empty_kibana'); - }); - - after(async () => { - await esArchiver.unload('empty_kibana'); - }); - - describe('space with no features disabled', () => { - before(async () => { - await spacesService.create({ - id: 'custom_space', - name: 'custom_space', - disabledFeatures: [], - }); - }); - - after(async () => { - await spacesService.delete('custom_space'); - }); - - it('shows apm navlink', async () => { - await PageObjects.common.navigateToApp('home', { - basePath: '/s/custom_space', - }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); - expect(navLinks).to.contain('APM'); - }); - - it(`can navigate to app`, async () => { - await PageObjects.common.navigateToApp('apm', { - basePath: '/s/custom_space', - }); - - await testSubjects.existOrFail('apmMainContainer'); - }); - }); - - describe('space with APM disabled', () => { - before(async () => { - await spacesService.create({ - id: 'custom_space', - name: 'custom_space', - disabledFeatures: ['apm'], - }); - }); - - after(async () => { - await spacesService.delete('custom_space'); - }); - - it(`doesn't show apm navlink`, async () => { - await PageObjects.common.navigateToApp('home', { - basePath: '/s/custom_space', - }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); - expect(navLinks).not.to.contain('APM'); - }); - - it(`navigating to app returns a 404`, async () => { - await PageObjects.common.navigateToUrl('apm', '', { - basePath: '/s/custom_space', - shouldLoginIfPrompted: false, - ensureCurrentUrl: false, - }); - - await PageObjects.error.expectNotFound(); - }); - }); - }); -} diff --git a/x-pack/test/functional/apps/apm/feature_controls/index.ts b/x-pack/test/functional/apps/apm/feature_controls/index.ts deleted file mode 100644 index 4764fa276fd72..0000000000000 --- a/x-pack/test/functional/apps/apm/feature_controls/index.ts +++ /dev/null @@ -1,14 +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 { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; - -// tslint:disable:no-default-export -export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) { - describe('feature controls', () => { - loadTestFile(require.resolve('./apm_security')); - loadTestFile(require.resolve('./apm_spaces')); - }); -} diff --git a/x-pack/test/functional/apps/apm/index.ts b/x-pack/test/functional/apps/apm/index.ts deleted file mode 100644 index e70b3ad65787c..0000000000000 --- a/x-pack/test/functional/apps/apm/index.ts +++ /dev/null @@ -1,15 +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 { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; - -// tslint:disable:no-default-export -export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) { - describe('apm', function() { - this.tags('ciGroup3'); - - loadTestFile(require.resolve('./feature_controls')); - }); -} diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index a65c72b6ccdb3..7b85adf75c67f 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -82,7 +82,6 @@ export default async function ({ readConfigFile }) { // list paths to the files that contain your plugins tests testFiles: [ resolve(__dirname, './apps/advanced_settings'), - resolve(__dirname, './apps/apm'), resolve(__dirname, './apps/canvas'), resolve(__dirname, './apps/graph'), resolve(__dirname, './apps/monitoring'), From 13f88310d625d2287c4458190c7c0552c2c4c116 Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 18 Mar 2019 08:54:00 -0700 Subject: [PATCH 47/62] Fixing typescript errors --- x-pack/test/ui_capabilities/security_only/tests/catalogue.ts | 4 ---- x-pack/test/ui_capabilities/security_only/tests/foo.ts | 4 ---- x-pack/test/ui_capabilities/security_only/tests/nav_links.ts | 4 ---- 3 files changed, 12 deletions(-) diff --git a/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts b/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts index bb5b8c70ab4f8..5da27b8e209da 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts @@ -52,10 +52,6 @@ export default function catalogueTests({ getService }: KibanaFunctionalTestDefau // these users have no access to even get the ui capabilities case 'legacy_all': case 'no_kibana_privileges': - case 'apm_user': - case 'machine_learning_admin': - case 'machine_learning_user': - case 'monitoring_user': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; diff --git a/x-pack/test/ui_capabilities/security_only/tests/foo.ts b/x-pack/test/ui_capabilities/security_only/tests/foo.ts index 31f422f12c255..1d10eaedebcce 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/foo.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/foo.ts @@ -54,10 +54,6 @@ export default function fooTests({ getService }: KibanaFunctionalTestDefaultProv // these users have no access to even get the ui capabilities case 'legacy_all': case 'no_kibana_privileges': - case 'apm_user': - case 'machine_learning_admin': - case 'machine_learning_user': - case 'monitoring_user': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); break; diff --git a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts index ad379b9bc74fb..3869470c7f938 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts @@ -56,11 +56,7 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul navLinksBuilder.only('management', 'foo') ); break; - case 'apm_user': case 'legacy_all': - case 'machine_learning_admin': - case 'machine_learning_user': - case 'monitoring_user': case 'no_kibana_privileges': expect(uiCapabilities.success).to.be(false); expect(uiCapabilities.failureReason).to.be(GetUICapabilitiesFailureReason.NotFound); From 3f75085623e406a53c30723be318bd82c885385d Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 18 Mar 2019 09:41:34 -0700 Subject: [PATCH 48/62] Fixing ui capability test themselves --- .../security_and_spaces/tests/catalogue.ts | 16 +++++++++++++--- .../security_and_spaces/tests/nav_links.ts | 2 +- .../security_only/tests/catalogue.ts | 16 +++++++++++++--- .../security_only/tests/nav_links.ts | 2 +- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts index 290808b2541b2..33b0755433ddb 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/catalogue.ts @@ -27,7 +27,14 @@ export default function catalogueTests({ getService }: KibanaFunctionalTestDefau space.id ); switch (scenario.id) { - case 'superuser at everything_space': + case 'superuser at everything_space': { + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('catalogue'); + // everything is enabled + const expected = mapValues(uiCapabilities.value!.catalogue, () => true); + expect(uiCapabilities.value!.catalogue).to.eql(expected); + break; + } case 'global_all at everything_space': case 'dual_privileges_all at everything_space': case 'everything_space_all at everything_space': @@ -36,8 +43,11 @@ export default function catalogueTests({ getService }: KibanaFunctionalTestDefau case 'everything_space_read at everything_space': { expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('catalogue'); - // everything is enabled - const expected = mapValues(uiCapabilities.value!.catalogue, () => true); + // everything except ml and monitoring is enabled + const expected = mapValues( + uiCapabilities.value!.catalogue, + (enabled, catalogueId) => catalogueId !== 'ml' && catalogueId !== 'monitoring' + ); expect(uiCapabilities.value!.catalogue).to.eql(expected); break; } diff --git a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts index 0fcf7525fae8c..f1e220f81ae98 100644 --- a/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_and_spaces/tests/nav_links.ts @@ -49,7 +49,7 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('navLinks'); expect(uiCapabilities.value!.navLinks).to.eql( - navLinksBuilder.except('apm', 'ml', 'monitoring') + navLinksBuilder.except('ml', 'monitoring') ); break; case 'superuser at nothing_space': diff --git a/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts b/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts index 5da27b8e209da..ae0f8979ad852 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/catalogue.ts @@ -25,15 +25,25 @@ export default function catalogueTests({ getService }: KibanaFunctionalTestDefau password: scenario.password, }); switch (scenario.username) { - case 'superuser': + case 'superuser': { + expect(uiCapabilities.success).to.be(true); + expect(uiCapabilities.value).to.have.property('catalogue'); + // everything is enabled + const expected = mapValues(uiCapabilities.value!.catalogue, () => true); + expect(uiCapabilities.value!.catalogue).to.eql(expected); + break; + } case 'all': case 'read': case 'dual_privileges_all': case 'dual_privileges_read': { expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('catalogue'); - // everything is enabled - const expected = mapValues(uiCapabilities.value!.catalogue, () => true); + // everything except ml and monitoring is enabled + const expected = mapValues( + uiCapabilities.value!.catalogue, + (enabled, catalogueId) => catalogueId !== 'ml' && catalogueId !== 'monitoring' + ); expect(uiCapabilities.value!.catalogue).to.eql(expected); break; } diff --git a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts index 3869470c7f938..23971598f160f 100644 --- a/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts +++ b/x-pack/test/ui_capabilities/security_only/tests/nav_links.ts @@ -45,7 +45,7 @@ export default function navLinksTests({ getService }: KibanaFunctionalTestDefaul expect(uiCapabilities.success).to.be(true); expect(uiCapabilities.value).to.have.property('navLinks'); expect(uiCapabilities.value!.navLinks).to.eql( - navLinksBuilder.except('apm', 'ml', 'monitoring') + navLinksBuilder.except('ml', 'monitoring') ); break; case 'foo_all': From 0b265c3d26a4bc0f1a2c30a584b7e301eb0725e2 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 19 Mar 2019 15:43:02 -0700 Subject: [PATCH 49/62] Displaying reserved privileges for the space aware and simple forms --- x-pack/plugins/security/common/model/role.ts | 1 + .../kibana_privilege_calculator.ts | 1 + .../kibana_privilege_calculator_types.ts | 1 + .../simple_privilege_section.test.tsx | 25 +- .../simple_privilege_section.tsx | 228 ++++++++++-------- .../privilege_space_table.tsx | 7 +- 6 files changed, 154 insertions(+), 109 deletions(-) diff --git a/x-pack/plugins/security/common/model/role.ts b/x-pack/plugins/security/common/model/role.ts index 516fdb199b8c3..19bea7ccdfefb 100644 --- a/x-pack/plugins/security/common/model/role.ts +++ b/x-pack/plugins/security/common/model/role.ts @@ -19,6 +19,7 @@ export interface RoleKibanaPrivilege { spaces: string[]; base: string[]; feature: FeaturesPrivileges; + _reserved?: string[]; } export interface Role { diff --git a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator.ts b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator.ts index dd6f3414958b9..de8156a692765 100644 --- a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator.ts +++ b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator.ts @@ -76,6 +76,7 @@ export class KibanaPrivilegeCalculator { ignoreAssigned ), feature: {}, + _reserved: privilegeSpec._reserved, }; // If calculations wish to ignoreAssigned, then we still need to know what the real effective base privilege is diff --git a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator_types.ts b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator_types.ts index 65dd9248c32bd..d56c8043b437a 100644 --- a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator_types.ts +++ b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator_types.ts @@ -34,6 +34,7 @@ export interface CalculatedPrivilege { feature: { [featureId: string]: PrivilegeExplanation; }; + _reserved: undefined | string[]; } export interface PrivilegeScenario { diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx index 40ed0b9014226..5b33bc525fcfe 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ // @ts-ignore -import { EuiButtonGroup, EuiButtonGroupProps, EuiSuperSelect } from '@elastic/eui'; +import { EuiButtonGroup, EuiButtonGroupProps, EuiComboBox, EuiSuperSelect } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { Feature } from 'x-pack/plugins/xpack_main/types'; @@ -131,6 +131,29 @@ describe('', () => { expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0); }); + it('displays the reserved privilege', () => { + const props = buildProps({ + role: { + elasticsearch: {}, + kibana: [ + { + spaces: ['*'], + base: [], + feature: {}, + _reserved: ['foo'], + }, + ], + }, + }); + const wrapper = shallowWithIntl(); + const selector = wrapper.find(EuiComboBox); + expect(selector.props()).toMatchObject({ + isDisabled: true, + selectedOptions: [{ label: 'foo' }], + }); + expect(wrapper.find(UnsupportedSpacePrivilegesWarning)).toHaveLength(0); + }); + it('fires its onChange callback when the privilege changes', () => { const props = buildProps(); const wrapper = mountWithIntl(); diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx index 8b67acf0afb2c..3b541f7c2798c 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx @@ -5,6 +5,7 @@ */ import { + EuiComboBox, // @ts-ignore EuiDescribedFormGroup, EuiFormRow, @@ -15,7 +16,7 @@ import { import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; import { Feature } from 'x-pack/plugins/xpack_main/types'; -import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; +import { KibanaPrivileges, Role, RoleKibanaPrivilege } from '../../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; import { isGlobalPrivilegeDefinition } from '../../../../../../../lib/privilege_utils'; import { copyRole } from '../../../../../../../lib/role_utils'; @@ -65,6 +66,11 @@ export class SimplePrivilegeSection extends Component { this.state.globalPrivsIndex ]; + const hasReservedPrivileges = + calculatedPrivileges && + calculatedPrivileges._reserved != null && + calculatedPrivileges._reserved.length > 0; + const description = (

{ description={description} > - - - - ), - dropdownDisplay: ( - - + {hasReservedPrivileges ? ( + ({ + label: privilege, + }))} + isDisabled + /> + ) : ( + - -

- -

- - ), - }, - { - value: CUSTOM_PRIVILEGE_VALUE, - inputDisplay: ( - - - - ), - dropdownDisplay: ( - - + + ), + dropdownDisplay: ( + + + + +

+ +

+
+ ), + }, + { + value: CUSTOM_PRIVILEGE_VALUE, + inputDisplay: ( + -
-

- -

- - ), - }, - { - value: 'read', - inputDisplay: ( - - - - ), - dropdownDisplay: ( - - + + ), + dropdownDisplay: ( + + + + +

+ +

+
+ ), + }, + { + value: 'read', + inputDisplay: ( + -
-

+ + ), + dropdownDisplay: ( + + + + +

+ +

+ + ), + }, + { + value: 'all', + inputDisplay: ( + -

-
- ), - }, - { - value: 'all', - inputDisplay: ( - - - - ), - dropdownDisplay: ( - - - - -

- -

-
- ), - }, - ]} - hasDividers - valueOfSelected={kibanaPrivilege} - /> + + ), + dropdownDisplay: ( + + + + +

+ +

+
+ ), + }, + ]} + hasDividers + valueOfSelected={kibanaPrivilege} + /> + )} {this.state.isCustomizingGlobalPrivilege && ( @@ -312,7 +328,7 @@ export class SimplePrivilegeSection extends Component { return spacePrivileges.find(privileges => isGlobalPrivilegeDefinition(privileges)); }; - private createGlobalPrivilegeEntry(role: Role) { + private createGlobalPrivilegeEntry(role: Role): RoleKibanaPrivilege { const newEntry = { spaces: ['*'], base: [], diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx index 24afb2d31d43a..11847f7b828dc 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx @@ -184,14 +184,17 @@ export class PrivilegeSpaceTable extends Component { name: 'Privileges', render: (privileges: RoleKibanaPrivilege, record: TableRow) => { const hasCustomizations = hasAssignedFeaturePrivileges(privileges); - const basePrivilege = effectivePrivileges[record.spacesIndex].base; + const effectivePrivilege = effectivePrivileges[record.spacesIndex]; + const basePrivilege = effectivePrivilege.base; const isAllowedCustomizations = allowedPrivileges[record.spacesIndex].base.privileges.length > 1; const showCustomize = hasCustomizations && isAllowedCustomizations; - if (record.isGlobal) { + if (effectivePrivilege._reserved != null && effectivePrivilege._reserved.length > 0) { + return ; + } else if (record.isGlobal) { return ( Date: Tue, 19 Mar 2019 16:18:59 -0700 Subject: [PATCH 50/62] Removing ability to PUT roles with _reserved privileges. Removing ability to GET roles that have entries with both reserved and feature/base privileges. --- .../server/routes/api/public/roles/get.js | 14 +- .../routes/api/public/roles/get.test.js | 182 +++++++++++++++++ .../server/routes/api/public/roles/put.js | 10 +- .../routes/api/public/roles/put.test.js | 15 +- .../api_integration/apis/security/roles.js | 192 ++++++++---------- 5 files changed, 281 insertions(+), 132 deletions(-) diff --git a/x-pack/plugins/security/server/routes/api/public/roles/get.js b/x-pack/plugins/security/server/routes/api/public/roles/get.js index 2765f7b77cb27..18aa7e1d79fb2 100644 --- a/x-pack/plugins/security/server/routes/api/public/roles/get.js +++ b/x-pack/plugins/security/server/routes/api/public/roles/get.js @@ -43,13 +43,23 @@ export function initGetRolesApi(server, callWithRequest, routePreCheckLicenseFn, }; } - // if any application entry contains the '*' resource in addition to another resource, we can't transform these - if (roleKibanaApplications.some(entry => entry.resources.includes(GLOBAL_RESOURCE) && entry.resources.length > 1)) { + // if reserved privilege assigned with feature or base privileges, we won't transform these + if (roleKibanaApplications.some(entry => + entry.privileges.some(privilege => PrivilegeSerializer.isSerializedReservedPrivilege(privilege)) && + entry.privileges.some(privilege => !PrivilegeSerializer.isSerializedReservedPrivilege(privilege))) + ) { return { success: false }; } + // if any application entry contains the '*' resource in addition to another resource, we can't transform these + {if (roleKibanaApplications.some(entry => entry.resources.includes(GLOBAL_RESOURCE) && entry.resources.length > 1)) { + return { + success: false + }; + }} + const allResources = _.flatten(roleKibanaApplications.map(entry => entry.resources)); // if we have improperly formatted resource entries, we can't transform these if (allResources.some(resource => resource !== GLOBAL_RESOURCE && !ResourceSerializer.isSerializedSpaceResource(resource))) { diff --git a/x-pack/plugins/security/server/routes/api/public/roles/get.test.js b/x-pack/plugins/security/server/routes/api/public/roles/get.test.js index e2e6ff0ebbf4d..8e5d3e0bdda5a 100644 --- a/x-pack/plugins/security/server/routes/api/public/roles/get.test.js +++ b/x-pack/plugins/security/server/routes/api/public/roles/get.test.js @@ -797,6 +797,98 @@ describe('GET roles', () => { }, }); + getRolesTest( + `reserved privilege assigned with a base privilege returns empty kibana section with _transform_error set to ['kibana']`, { + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['reserved_foo', 'read'], + resources: ['*'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: [ + { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + ], + }, + }); + + getRolesTest( + `reserved privilege assigned with a feature privilege returns empty kibana section with _transform_error set to ['kibana']`, { + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['reserved_foo', 'feature_foo.foo-privilege-1'], + resources: ['*'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: [ + { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + ], + }, + }); + getRolesTest(`transforms unrecognized applications`, { callWithRequestImpl: async () => ({ first_role: { @@ -1684,6 +1776,96 @@ describe('GET role', () => { }, }); + getRoleTest( + `reserved privilege assigned with a base privilege returns empty kibana section with _transform_error set to ['kibana']`, { + name: 'first_role', + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['reserved_foo', 'read'], + resources: ['*'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + }, + }); + + getRoleTest( + `reserved privilege assigned with a feature privilege returns empty kibana section with _transform_error set to ['kibana']`, { + name: 'first_role', + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application, + privileges: ['reserved_foo', 'feature_foo.foo-privilege-1'], + resources: ['*'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + }, + }); + getRoleTest(`transforms unrecognized applications`, { name: 'first_role', callWithRequestImpl: async () => ({ diff --git a/x-pack/plugins/security/server/routes/api/public/roles/put.js b/x-pack/plugins/security/server/routes/api/public/roles/put.js index 94d110e39f6a6..70149da43d338 100644 --- a/x-pack/plugins/security/server/routes/api/public/roles/put.js +++ b/x-pack/plugins/security/server/routes/api/public/roles/put.js @@ -19,7 +19,7 @@ export function initPutRolesApi( ) { const transformKibanaPrivilegesToEs = (kibanaPrivileges = []) => { - return kibanaPrivileges.map(({ base, feature, spaces, _reserved }) => { + return kibanaPrivileges.map(({ base, feature, spaces }) => { if (spaces.length === 1 && spaces[0] === GLOBAL_RESOURCE) { return { privileges: [ @@ -32,9 +32,6 @@ export function initPutRolesApi( privilege => PrivilegeSerializer.serializeFeaturePrivilege(featureName, privilege) ) ) - ) : [], - ..._reserved ? _reserved.map( - privilege => PrivilegeSerializer.serializeReservedPrivilege(privilege) ) : [] ], application, @@ -97,11 +94,6 @@ export function initPutRolesApi( allSpacesSchema, Joi.array().items(Joi.string().regex(/^[a-z0-9_-]+$/)), ).default([GLOBAL_RESOURCE]), - _reserved: Joi.alternatives().when('spaces', { - is: allSpacesSchema, - then: Joi.array().items(Joi.string().valid(Object.keys(privileges.reserved))), - otherwise: Joi.any().forbidden(), - }) }) ).unique((a, b) => { return intersection(a.spaces, b.spaces).length !== 0; diff --git a/x-pack/plugins/security/server/routes/api/public/roles/put.test.js b/x-pack/plugins/security/server/routes/api/public/roles/put.test.js index 5efbd856a0d34..b9d20d5a28e93 100644 --- a/x-pack/plugins/security/server/routes/api/public/roles/put.test.js +++ b/x-pack/plugins/security/server/routes/api/public/roles/put.test.js @@ -224,12 +224,12 @@ describe('PUT role', () => { }, }); - putRoleTest(`only allows known Kibana reserved privileges`, { + putRoleTest(`doesn't allow Kibana reserved privileges`, { name: 'foo-role', payload: { kibana: [ { - _reserved: ['customApplication3'], + _reserved: ['customApplication1'], spaces: ['*'] } ] @@ -239,10 +239,10 @@ describe('PUT role', () => { result: { error: 'Bad Request', //eslint-disable-next-line max-len - message: `child \"kibana\" fails because [\"kibana\" at position 0 fails because [child \"_reserved\" fails because [\"_reserved\" at position 0 fails because [\"0\" must be one of [customApplication1, customApplication2]]]]]`, + message: `child \"kibana\" fails because [\"kibana\" at position 0 fails because [\"_reserved\" is not allowed]]`, statusCode: 400, validation: { - keys: ['kibana.0._reserved.0'], + keys: ['kibana.0._reserved'], source: 'payload', }, }, @@ -391,7 +391,7 @@ describe('PUT role', () => { }, }); - putRoleTest(`can't assign _reserved privilege at a space`, { + putRoleTest(`doesn't allow Kibana reserved privileges`, { name: 'foo-role', payload: { kibana: [ @@ -406,7 +406,7 @@ describe('PUT role', () => { result: { error: 'Bad Request', //eslint-disable-next-line max-len - message: `child \"kibana\" fails because [\"kibana\" at position 0 fails because [child \"_reserved\" fails because [\"_reserved\" is not allowed]]]`, + message: `child \"kibana\" fails because [\"kibana\" at position 0 fails because [\"_reserved\" is not allowed]]`, statusCode: 400, validation: { keys: ['kibana.0._reserved'], @@ -528,7 +528,6 @@ describe('PUT role', () => { bar: ['bar-privilege-1', 'bar-privilege-2'] }, spaces: ['*'], - _reserved: ['customApplication1', 'customApplication2'] }, { base: ['all', 'read'], @@ -566,8 +565,6 @@ describe('PUT role', () => { 'feature_foo.foo-privilege-2', 'feature_bar.bar-privilege-1', 'feature_bar.bar-privilege-2', - 'reserved_customApplication1', - 'reserved_customApplication2', ], resources: [GLOBAL_RESOURCE], }, diff --git a/x-pack/test/api_integration/apis/security/roles.js b/x-pack/test/api_integration/apis/security/roles.js index 26d9e0d4f7c83..0114470f5cebe 100644 --- a/x-pack/test/api_integration/apis/security/roles.js +++ b/x-pack/test/api_integration/apis/security/roles.js @@ -100,39 +100,6 @@ export default function ({ getService }) { } }); }); - - it('should create a role with kibana reserved privileges', async () => { - await supertest.put('/api/security/role/role_with_kibana_reserved_privileges') - .set('kbn-xsrf', 'xxx') - .send({ - kibana: [ - { - _reserved: ['ml', 'monitoring'] - } - ] - }) - .expect(204); - - const role = await es.shield.getRole({ name: 'role_with_kibana_reserved_privileges' }); - expect(role).to.eql({ - role_with_kibana_reserved_privileges: { - cluster: [], - indices: [], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['reserved_ml', 'reserved_monitoring'], - resources: ['*'], - } - ], - run_as: [], - metadata: {}, - transient_metadata: { - enabled: true, - }, - } - }); - }); }); describe('Update Role', () => { @@ -155,7 +122,7 @@ export default function ({ getService }) { applications: [ { application: 'kibana-.kibana', - privileges: ['read', 'reserved_ml'], + privileges: ['read'], resources: ['*'], }, { @@ -195,7 +162,6 @@ export default function ({ getService }) { }, kibana: [ { - _reserved: ['monitoring'], base: ['read'], feature: { dashboard: ['read'], @@ -235,7 +201,7 @@ export default function ({ getService }) { applications: [ { application: 'kibana-.kibana', - privileges: ['read', 'feature_dashboard.read', 'feature_dev_tools.all', 'reserved_monitoring'], + privileges: ['read', 'feature_dashboard.read', 'feature_dev_tools.all'], resources: ['*'], }, { @@ -262,97 +228,99 @@ export default function ({ getService }) { }); describe('Get Role', async () => { - await es.shield.putRole({ - name: 'role_to_get', - body: { - cluster: ['manage'], - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - allow_restricted_indices: false, - field_security: { - grant: ['*'], - except: ['geo.*'] - }, - query: `{ "match": { "geo.src": "CN" } }`, - }, - ], - applications: [ - { - application: 'kibana-.kibana', - privileges: ['read', 'feature_dashboard.read', 'feature_dev_tools.all', 'reserved_apm', 'reserved_monitoring'], - resources: ['*'], - }, - { - application: 'kibana-.kibana', - privileges: ['space_all', 'feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'], - resources: ['space:marketing', 'space:sales'], - }, - { - application: 'logstash-default', - privileges: ['logstash-privilege'], - resources: ['*'], - }, - ], - run_as: ['watcher_user'], - metadata: { - foo: 'test-metadata', - }, - transient_metadata: { - enabled: true, - }, - } - }); - - await supertest.get('/api/security/role/role_to_get') - .set('kbn-xsrf', 'xxx') - .expect(200, { + it('should get roles', async () => { + await es.shield.putRole({ name: 'role_to_get', - metadata: { - foo: 'test-metadata', - }, - transient_metadata: { enabled: true }, - elasticsearch: { + body: { cluster: ['manage'], indices: [ { + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + allow_restricted_indices: false, field_security: { grant: ['*'], except: ['geo.*'] }, - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], query: `{ "match": { "geo.src": "CN" } }`, - allow_restricted_indices: false }, ], - run_as: ['watcher_user'], - }, - kibana: [ - { - _reserved: ['apm', 'monitoring'], - base: ['read'], - feature: { - dashboard: ['read'], - dev_tools: ['all'], + applications: [ + { + application: 'kibana-.kibana', + privileges: ['read', 'feature_dashboard.read', 'feature_dev_tools.all', 'reserved_apm', 'reserved_monitoring'], + resources: ['*'], + }, + { + application: 'kibana-.kibana', + privileges: ['space_all', 'feature_dashboard.read', 'feature_discover.all', 'feature_ml.all'], + resources: ['space:marketing', 'space:sales'], + }, + { + application: 'logstash-default', + privileges: ['logstash-privilege'], + resources: ['*'], }, - spaces: ['*'] + ], + run_as: ['watcher_user'], + metadata: { + foo: 'test-metadata', + }, + transient_metadata: { + enabled: true, + }, + } + }); + + await supertest.get('/api/security/role/role_to_get') + .set('kbn-xsrf', 'xxx') + .expect(200, { + name: 'role_to_get', + metadata: { + foo: 'test-metadata', }, - { - base: ['all'], - feature: { - dashboard: ['read'], - discover: ['all'], - ml: ['all'] + transient_metadata: { enabled: true }, + elasticsearch: { + cluster: ['manage'], + indices: [ + { + field_security: { + grant: ['*'], + except: ['geo.*'] + }, + names: ['logstash-*'], + privileges: ['read', 'view_index_metadata'], + query: `{ "match": { "geo.src": "CN" } }`, + allow_restricted_indices: false + }, + ], + run_as: ['watcher_user'], + }, + kibana: [ + { + _reserved: ['apm', 'monitoring'], + base: ['read'], + feature: { + dashboard: ['read'], + dev_tools: ['all'], + }, + spaces: ['*'] }, - spaces: ['marketing', 'sales'] - } - ], + { + base: ['all'], + feature: { + dashboard: ['read'], + discover: ['all'], + ml: ['all'] + }, + spaces: ['marketing', 'sales'] + } + ], - _transform_error: [], - _unrecognized_applications: [ 'logstash-default' ] - }); + _transform_error: [], + _unrecognized_applications: [ 'logstash-default' ] + }); + }); }); describe('Delete Role', () => { it('should delete the three roles we created', async () => { From 1a495fded86357801b4f7ea6fae5bc887a7e0a7e Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 21 Mar 2019 10:54:58 -0700 Subject: [PATCH 51/62] Updating jest snapshots --- .../__snapshots__/privilege_space_form.test.tsx.snap | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap index 4a000186c35ef..dc74fd16ce2ca 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap @@ -314,6 +314,7 @@ exports[` renders without crashing 1`] = ` } calculatedPrivileges={ Object { + "_reserved": undefined, "base": Object { "actualPrivilege": "none", "actualPrivilegeSource": 40, From 70a9ffc8315c9400fb0c0b06ae63d599a4e0ac5b Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 21 Mar 2019 15:47:26 -0700 Subject: [PATCH 52/62] Changing the interface for a feature to register a reserved privilege to include a description as well --- x-pack/plugins/ml/index.js | 15 +++-- x-pack/plugins/monitoring/init.js | 15 +++-- .../privileges/privileges.test.ts | 62 +++++++++++-------- .../authorization/privileges/privileges.ts | 4 +- .../feature_registry/feature_registry.test.ts | 25 ++++---- .../lib/feature_registry/feature_registry.ts | 10 ++- 6 files changed, 81 insertions(+), 50 deletions(-) diff --git a/x-pack/plugins/ml/index.js b/x-pack/plugins/ml/index.js index 6995aeeaf7764..7bdca896d2999 100644 --- a/x-pack/plugins/ml/index.js +++ b/x-pack/plugins/ml/index.js @@ -89,12 +89,17 @@ export const ml = (kibana) => { app: ['ml', 'kibana'], catalogue: ['ml'], privileges: {}, - reservedPrivilege: { - savedObject: { - all: [], - read: ['config'] + reserved: { + privilege: { + savedObject: { + all: [], + read: ['config'] + }, + ui: [], }, - ui: [], + description: i18n.translate('xpack.ml.feature.reserved.description', { + defaultMessage: 'To grant users access, you should also assign either the machine_learning_user or machine_learning_admin role.' + }) } }); diff --git a/x-pack/plugins/monitoring/init.js b/x-pack/plugins/monitoring/init.js index 4997213837ba8..358fd715d7df6 100644 --- a/x-pack/plugins/monitoring/init.js +++ b/x-pack/plugins/monitoring/init.js @@ -65,12 +65,17 @@ export const init = (monitoringPlugin, server) => { app: ['monitoring', 'kibana'], catalogue: ['monitoring'], privileges: {}, - reservedPrivilege: { - savedObject: { - all: [], - read: ['config'] + reserved: { + privilege: { + savedObject: { + all: [], + read: ['config'] + }, + ui: [], }, - ui: [], + description: i18n.translate('xpack.monitoring.feature.reserved.description', { + defaultMessage: 'To grant users access, you should also assign the monitoring_user role.' + }) } }); diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts index b7b32c08895a5..fad3ae04882a4 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts @@ -717,12 +717,15 @@ describe('features', () => { foo: ['ignore-me-1', 'ignore-me-2'], }, privileges: {}, - reservedPrivilege: { - savedObject: { - all: ['ignore-me-1', 'ignore-me-2'], - read: ['ignore-me-1', 'ignore-me-2'], + reserved: { + privilege: { + savedObject: { + all: ['ignore-me-1', 'ignore-me-2'], + read: ['ignore-me-1', 'ignore-me-2'], + }, + ui: ['ignore-me-1'], }, - ui: ['ignore-me-1'], + description: '', }, }, ]; @@ -758,12 +761,15 @@ describe('reserved', () => { foo: ['management-1', 'management-2'], }, privileges: {}, - reservedPrivilege: { - savedObject: { - all: [], - read: [], + reserved: { + privilege: { + savedObject: { + all: [], + read: [], + }, + ui: [], }, - ui: [], + description: '', }, }, ]; @@ -799,17 +805,20 @@ describe('reserved', () => { foo: ['ignore-me-1', 'ignore-me-2'], }, privileges: {}, - reservedPrivilege: { - app: ['app-1', 'app-2'], - catalogue: ['catalogue-1', 'catalogue-2'], - management: { - bar: ['management-1', 'management-2'], - }, - savedObject: { - all: [], - read: [], + reserved: { + privilege: { + app: ['app-1', 'app-2'], + catalogue: ['catalogue-1', 'catalogue-2'], + management: { + bar: ['management-1', 'management-2'], + }, + savedObject: { + all: [], + read: [], + }, + ui: [], }, - ui: [], + description: '', }, }, ]; @@ -840,12 +849,15 @@ describe('reserved', () => { icon: 'arrowDown', app: [], privileges: {}, - reservedPrivilege: { - savedObject: { - all: ['savedObject-all-1', 'savedObject-all-2'], - read: ['savedObject-read-1', 'savedObject-read-2'], + reserved: { + privilege: { + savedObject: { + all: ['savedObject-all-1', 'savedObject-all-2'], + read: ['savedObject-read-1', 'savedObject-read-2'], + }, + ui: ['ui-1', 'ui-2'], }, - ui: ['ui-1', 'ui-2'], + description: '', }, }, ]; diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts index 41afcbee96fb7..579d8688bd192 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.ts @@ -93,12 +93,12 @@ export function privilegesFactory(actions: Actions, xpackMainPlugin: XPackMainPl read: [actions.login, actions.version, ...readActions], }, reserved: features.reduce((acc: Record, feature: Feature) => { - if (feature.reservedPrivilege) { + if (feature.reserved) { acc[feature.id] = [ actions.version, ...flatten( featurePrivilegeBuilders.map(featurePrivilegeBuilder => - featurePrivilegeBuilder.getActions(feature.reservedPrivilege!, feature) + featurePrivilegeBuilder.getActions(feature.reserved!.privilege, feature) ) ), ]; diff --git a/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts b/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts index c87be92a31633..8b01a91a57335 100644 --- a/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts +++ b/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts @@ -55,18 +55,21 @@ describe('FeatureRegistry', () => { }, }, privilegesTooltip: 'some fancy tooltip', - reservedPrivilege: { - catalogue: ['foo'], - management: { - foo: ['bar'], - }, - app: ['app1'], - savedObject: { - all: ['config', 'space', 'etc'], - read: ['canvas'], + reserved: { + privilege: { + catalogue: ['foo'], + management: { + foo: ['bar'], + }, + app: ['app1'], + savedObject: { + all: ['config', 'space', 'etc'], + read: ['canvas'], + }, + api: ['someApiEndpointTag', 'anotherEndpointTag'], + ui: ['allowsFoo', 'showBar', 'showBaz'], }, - api: ['someApiEndpointTag', 'anotherEndpointTag'], - ui: ['allowsFoo', 'showBar', 'showBaz'], + description: 'some completely adequate description', }, }; diff --git a/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts b/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts index 25780d54c1e81..cc0b8731f8be7 100644 --- a/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts +++ b/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts @@ -44,7 +44,10 @@ export interface Feature = Privileges catalogue?: string[]; privileges: TPrivileges; privilegesTooltip?: string; - reservedPrivilege?: FeatureKibanaPrivileges; + reserved?: { + privilege: FeatureKibanaPrivileges; + description: string; + }; } // Each feature gets its own property on the UICapabilities object, @@ -100,7 +103,10 @@ const schema = Joi.object({ read: privilegeSchema, }).required(), privilegesTooltip: Joi.string(), - reservedPrivilege: privilegeSchema, + reserved: Joi.object({ + privilege: privilegeSchema.required(), + description: Joi.string().required(), + }), }); export class FeatureRegistry { From b7ac747c9e6b269eefe936b6cfa9b8d986b2855a Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 22 Mar 2019 09:58:49 -0700 Subject: [PATCH 53/62] Displaying features with reserved privileges in the feature table --- .../kibana_privileges/feature_privileges.ts | 7 ++- .../kibana_allowed_privileges_calculator.ts | 4 ++ .../kibana_privilege_calculator_types.ts | 12 ++-- .../kibana/feature_table/feature_table.tsx | 62 +++++++++++++++---- .../privilege_display.tsx | 9 ++- .../privilege_matrix.tsx | 13 ++-- .../privilege_space_form.tsx | 8 +-- .../space_aware_privilege_section.test.tsx | 21 +++++++ .../space_aware_privilege_section.tsx | 5 +- .../views/management/edit_role/index.js | 4 +- 10 files changed, 110 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/security/common/model/kibana_privileges/feature_privileges.ts b/x-pack/plugins/security/common/model/kibana_privileges/feature_privileges.ts index f7fa66813ee8c..fd4cdf33028eb 100644 --- a/x-pack/plugins/security/common/model/kibana_privileges/feature_privileges.ts +++ b/x-pack/plugins/security/common/model/kibana_privileges/feature_privileges.ts @@ -19,7 +19,12 @@ export class KibanaFeaturePrivileges { } public getPrivileges(featureId: string): string[] { - return Object.keys(this.featurePrivilegesMap[featureId]); + const featurePrivileges = this.featurePrivilegesMap[featureId]; + if (featurePrivileges == null) { + return []; + } + + return Object.keys(featurePrivileges); } public getActions(featureId: string, privilege: string): string[] { diff --git a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_allowed_privileges_calculator.ts b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_allowed_privileges_calculator.ts index cd719ac8430c5..89a9a75143aaf 100644 --- a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_allowed_privileges_calculator.ts +++ b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_allowed_privileges_calculator.ts @@ -93,6 +93,10 @@ export class KibanaAllowedPrivilegesCalculator { candidateFeaturePrivileges: string[] ): { privileges: string[]; canUnassign: boolean } { const effectiveFeaturePrivilegeExplanation = effectivePrivileges.feature[featureId]; + if (effectiveFeaturePrivilegeExplanation == null) { + throw new Error('To calculate allowed feature privileges, we need the effective privileges'); + } + const effectiveFeatureActions = this.getFeatureActions( featureId, effectiveFeaturePrivilegeExplanation.actualPrivilege diff --git a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator_types.ts b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator_types.ts index d56c8043b437a..50901f39fa328 100644 --- a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator_types.ts +++ b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator_types.ts @@ -32,7 +32,7 @@ export interface PrivilegeExplanation { export interface CalculatedPrivilege { base: PrivilegeExplanation; feature: { - [featureId: string]: PrivilegeExplanation; + [featureId: string]: PrivilegeExplanation | undefined; }; _reserved: undefined | string[]; } @@ -51,9 +51,11 @@ export interface AllowedPrivilege { canUnassign: boolean; }; feature: { - [featureId: string]: { - privileges: string[]; - canUnassign: boolean; - }; + [featureId: string]: + | { + privileges: string[]; + canUnassign: boolean; + } + | undefined; }; } diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx index c19ffb923f762..98c4bfb28c186 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/feature_table/feature_table.tsx @@ -61,14 +61,32 @@ export class FeatureTable extends Component { public render() { const { role, features, calculatedPrivileges, rankedFeaturePrivileges } = this.props; - const items: TableRow[] = features.map(feature => ({ - feature: { - ...feature, - hasAnyPrivilegeAssigned: - calculatedPrivileges.feature[feature.id].actualPrivilege !== NO_PRIVILEGE_VALUE, - }, - role, - })); + const items: TableRow[] = features + .sort((feature1, feature2) => { + if (feature1.reserved && !feature2.reserved) { + return 1; + } + + if (feature2.reserved && !feature1.reserved) { + return -1; + } + + return 0; + }) + .map(feature => { + const calculatedFeaturePrivileges = calculatedPrivileges.feature[feature.id]; + const hasAnyPrivilegeAssigned = Boolean( + calculatedFeaturePrivileges && + calculatedFeaturePrivileges.actualPrivilege !== NO_PRIVILEGE_VALUE + ); + return { + feature: { + ...feature, + hasAnyPrivilegeAssigned, + }, + role, + }; + }); // TODO: This simply grabs the available privileges from the first feature we encounter. // As of now, features can have 'all' and 'read' as available privileges. Once that assumption breaks, @@ -147,13 +165,17 @@ export class FeatureTable extends Component { ), render: (roleEntry: Role, record: TableRow) => { - const featureId = record.feature.id; + const { id: featureId, reserved } = record.feature; + + if (reserved) { + return {reserved.description}; + } const featurePrivileges = this.props.kibanaPrivileges .getFeaturePrivileges() .getPrivileges(featureId); - if (!featurePrivileges) { + if (featurePrivileges.length === 0) { return null; } @@ -213,18 +235,32 @@ export class FeatureTable extends Component { return featurePrivileges; } - return allowedPrivileges.feature[featureId].privileges; + const allowedFeaturePrivileges = allowedPrivileges.feature[featureId]; + if (allowedFeaturePrivileges == null) { + throw new Error('Unable to get enabled feature privileges for a feature without privileges'); + } + + return allowedFeaturePrivileges.privileges; }; private getPrivilegeExplanation = (featureId: string): PrivilegeExplanation => { const { calculatedPrivileges } = this.props; + const calculatedFeaturePrivileges = calculatedPrivileges.feature[featureId]; + if (calculatedFeaturePrivileges == null) { + throw new Error('Unable to get privilege explanation for a feature without privileges'); + } - return calculatedPrivileges.feature[featureId]; + return calculatedFeaturePrivileges; }; private allowsNoneForPrivilegeAssignment = (featureId: string): boolean => { const { allowedPrivileges } = this.props; - return allowedPrivileges.feature[featureId].canUnassign; + const allowedFeaturePrivileges = allowedPrivileges.feature[featureId]; + if (allowedFeaturePrivileges == null) { + throw new Error('Unable to determine if none is allowed for a feature without privileges'); + } + + return allowedFeaturePrivileges.canUnassign; }; private onChangeAllFeaturePrivileges = (privilege: string) => { diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.tsx index 5c2b8662d326c..d8bb01b01af82 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_display.tsx @@ -14,7 +14,7 @@ import { import { NO_PRIVILEGE_VALUE } from '../../../../lib/constants'; interface Props extends EuiTextProps { - privilege: string | string[]; + privilege: string | string[] | undefined; explanation?: PrivilegeExplanation; iconType?: IconType; tooltipContent?: ReactNode; @@ -90,7 +90,7 @@ PrivilegeDisplay.defaultProps = { privilege: [], }; -function getDisplayValue(privilege: string | string[]) { +function getDisplayValue(privilege: string | string[] | undefined) { const privileges = coerceToArray(privilege); let displayValue: string | ReactNode; @@ -125,7 +125,10 @@ function getIconTip(iconType?: IconType, tooltipContent?: ReactNode) { ); } -function coerceToArray(privilege: string | string[]): string[] { +function coerceToArray(privilege: string | string[] | undefined): string[] { + if (privilege === undefined) { + return []; + } if (Array.isArray(privilege)) { return privilege; } diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx index 2e40b8050eb91..d7ce157d4e071 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx @@ -288,11 +288,14 @@ export class PrivilegeMatrix extends Component { return ; } - const actualPrivileges = this.props.calculatedPrivileges[column.spacesIndex].feature[ - feature.id - ].actualPrivilege; + const featureCalculatedPrivilege = this.props.calculatedPrivileges[column.spacesIndex] + .feature[feature.id]; - return ; + return ( + + ); } else { // not global @@ -315,7 +318,7 @@ export class PrivilegeMatrix extends Component { return ( ); } diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx index e41c4f477f566..b2578953e1cb0 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_form.tsx @@ -508,7 +508,7 @@ export class PrivilegeSpaceForm extends Component { const featureEntries = Object.values(allowedPrivileges.feature); return featureEntries.some(entry => { - return entry.canUnassign || entry.privileges.length > 1; + return entry != null && (entry.canUnassign || entry.privileges.length > 1); }); }; @@ -538,9 +538,9 @@ export class PrivilegeSpaceForm extends Component { form.feature = {}; } else { this.props.features.forEach(feature => { - const canAssign = allowedPrivs[this.state.editingIndex].feature[ - feature.id - ].privileges.includes(privileges[0]); + const allowedPrivilegesFeature = allowedPrivs[this.state.editingIndex].feature[feature.id]; + const canAssign = + allowedPrivilegesFeature && allowedPrivilegesFeature.privileges.includes(privileges[0]); if (canAssign) { form.feature[feature.id] = [...privileges]; diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx index a2a53f191fafc..2756b1c447274 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.test.tsx @@ -9,6 +9,7 @@ import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { KibanaPrivileges } from '../../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; import { RoleValidator } from '../../../../lib/validate_role'; +import { PrivilegeMatrix } from './privilege_matrix'; import { PrivilegeSpaceForm } from './privilege_space_form'; import { PrivilegeSpaceTable } from './privilege_space_table'; import { SpaceAwarePrivilegeSection } from './space_aware_privilege_section'; @@ -118,6 +119,26 @@ describe('', () => { expect(wrapper.find(PrivilegeSpaceForm)).toHaveLength(1); }); + it('hides privilege matrix when the role is reserved', () => { + const props = buildProps({ + role: { + name: '', + metadata: { + _reserved: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + }, + }); + + const wrapper = mountWithIntl(); + expect(wrapper.find(PrivilegeMatrix)).toHaveLength(0); + }); + describe('with base privilege set to "read"', () => { it('allows space privileges to be customized', () => { const props = buildProps({ diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx index bea288c0f3416..435b56a01465d 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_aware_privilege_section.tsx @@ -19,6 +19,7 @@ import { Feature } from 'x-pack/plugins/xpack_main/types'; import { Space } from '../../../../../../../../../spaces/common/model/space'; import { KibanaPrivileges, Role } from '../../../../../../../../common/model'; import { KibanaPrivilegeCalculatorFactory } from '../../../../../../../lib/kibana_privilege_calculator'; +import { isReservedRole } from '../../../../../../../lib/role_utils'; import { RoleValidator } from '../../../../lib/validate_role'; import { PrivilegeMatrix } from './privilege_matrix'; import { PrivilegeSpaceForm } from './privilege_space_form'; @@ -219,7 +220,9 @@ class SpaceAwarePrivilegeSectionUI extends Component { return ( {addPrivilegeButton} - {hasPrivilegesAssigned && {viewMatrixButton}} + {hasPrivilegesAssigned && !isReservedRole(this.props.role) && ( + {viewMatrixButton} + )} ); }; diff --git a/x-pack/plugins/security/public/views/management/edit_role/index.js b/x-pack/plugins/security/public/views/management/edit_role/index.js index e1dcb5a2d9359..f110b591adcd1 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/index.js +++ b/x-pack/plugins/security/public/views/management/edit_role/index.js @@ -135,8 +135,6 @@ routes.when(`${EDIT_ROLES_PATH}/:name?`, { $scope.$$postDigest(async () => { const domNode = document.getElementById('editRoleReactRoot'); - // we filter out the features here which don't have any privileges to simplify the logic within - const featuresWithPrivileges = features.filter(feature => Object.keys(feature.privileges).length > 0); render( , domNode); From 7567c9a4c22f888af618b2a8120dc614c2901f9a Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 22 Mar 2019 12:06:37 -0700 Subject: [PATCH 54/62] Adjusting the reserved role privileges unit tests --- .../server/lib/authorization/privileges/privileges.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts index 7645f4bc021dd..6a7972334da19 100644 --- a/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/lib/authorization/privileges/privileges.test.ts @@ -746,6 +746,7 @@ describe('features', () => { actions.login, actions.version, ...(expectManageSpaces ? [actions.space.manage, actions.ui.get('spaces', 'manage')] : []), + actions.allHack, ]); expect(actual).toHaveProperty(`${group}.read`, [actions.login, actions.version]); }); From 61ed5b1834d52ad836139ece685c3f5ab2a94edc Mon Sep 17 00:00:00 2001 From: kobelb Date: Fri, 29 Mar 2019 08:19:07 -0700 Subject: [PATCH 55/62] Changing usages of expect.js to @kbn/expect --- .../apps/machine_learning/feature_controls/ml_security.ts | 2 +- .../apps/machine_learning/feature_controls/ml_spaces.ts | 2 +- .../apps/monitoring/feature_controls/monitoring_security.ts | 2 +- .../apps/monitoring/feature_controls/monitoring_spaces.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts index 068cceb05da34..ff1c913605051 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import expect from 'expect.js'; +import expect from '@kbn/expect'; import { SecurityService } from 'x-pack/test/common/services'; import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts index 9deb7eea1f147..7f0c0ff427a44 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import expect from 'expect.js'; +import expect from '@kbn/expect'; import { SpacesService } from 'x-pack/test/common/services'; import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts index bd90b5e867db8..94ba35e41183e 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import expect from 'expect.js'; +import expect from '@kbn/expect'; import { SecurityService } from 'x-pack/test/common/services'; import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts index 836e65ca69226..48085760ef000 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import expect from 'expect.js'; +import expect from '@kbn/expect'; import { SpacesService } from 'x-pack/test/common/services'; import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; From accf3e86d8d564d2d4605ca091e0090dffb6dff6 Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 1 Apr 2019 13:43:09 -0700 Subject: [PATCH 56/62] Changing the CalculatedPrivilege's _reserved property to reserved --- .../kibana_privilege_calculator.ts | 2 +- .../kibana_privilege_calculator_types.ts | 2 +- .../simple_privilege_section/simple_privilege_section.tsx | 6 +++--- .../space_aware_privilege_section/privilege_space_table.tsx | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator.ts b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator.ts index de8156a692765..58c371e80290b 100644 --- a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator.ts +++ b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator.ts @@ -76,7 +76,7 @@ export class KibanaPrivilegeCalculator { ignoreAssigned ), feature: {}, - _reserved: privilegeSpec._reserved, + reserved: privilegeSpec._reserved, }; // If calculations wish to ignoreAssigned, then we still need to know what the real effective base privilege is diff --git a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator_types.ts b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator_types.ts index 50901f39fa328..41f6012737a92 100644 --- a/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator_types.ts +++ b/x-pack/plugins/security/public/lib/kibana_privilege_calculator/kibana_privilege_calculator_types.ts @@ -34,7 +34,7 @@ export interface CalculatedPrivilege { feature: { [featureId: string]: PrivilegeExplanation | undefined; }; - _reserved: undefined | string[]; + reserved: undefined | string[]; } export interface PrivilegeScenario { diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx index 3b541f7c2798c..bc23171b269b3 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/simple_privilege_section/simple_privilege_section.tsx @@ -68,8 +68,8 @@ export class SimplePrivilegeSection extends Component { const hasReservedPrivileges = calculatedPrivileges && - calculatedPrivileges._reserved != null && - calculatedPrivileges._reserved.length > 0; + calculatedPrivileges.reserved != null && + calculatedPrivileges.reserved.length > 0; const description = (

@@ -97,7 +97,7 @@ export class SimplePrivilegeSection extends Component { {hasReservedPrivileges ? ( ({ + selectedOptions={calculatedPrivileges.reserved!.map(privilege => ({ label: privilege, }))} isDisabled diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx index 11847f7b828dc..1c8e5d3dcc536 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx @@ -192,8 +192,8 @@ export class PrivilegeSpaceTable extends Component { const showCustomize = hasCustomizations && isAllowedCustomizations; - if (effectivePrivilege._reserved != null && effectivePrivilege._reserved.length > 0) { - return ; + if (effectivePrivilege.reserved != null && effectivePrivilege.reserved.length > 0) { + return ; } else if (record.isGlobal) { return ( Date: Mon, 1 Apr 2019 15:59:24 -0700 Subject: [PATCH 57/62] Allowing reserved privileges to be assigned at kibana-* --- x-pack/plugins/security/common/constants.ts | 2 + .../server/lib/authorization/service.ts | 3 +- .../server/routes/api/public/roles/get.js | 27 +- .../routes/api/public/roles/get.test.js | 283 ++++++++++++++++++ 4 files changed, 309 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security/common/constants.ts b/x-pack/plugins/security/common/constants.ts index 5fb316b772508..bca0684209ba1 100644 --- a/x-pack/plugins/security/common/constants.ts +++ b/x-pack/plugins/security/common/constants.ts @@ -7,3 +7,5 @@ export const GLOBAL_RESOURCE = '*'; export const IGNORED_TYPES = ['space']; export const REALMS_ELIGIBLE_FOR_PASSWORD_CHANGE = ['reserved', 'native']; +export const APPLICATION_PREFIX = 'kibana-'; +export const RESERVED_PRIVILEGES_APPLICATION_WILDCARD = 'kibana-*'; diff --git a/x-pack/plugins/security/server/lib/authorization/service.ts b/x-pack/plugins/security/server/lib/authorization/service.ts index efc01d0754528..49a492aac419b 100644 --- a/x-pack/plugins/security/server/lib/authorization/service.ts +++ b/x-pack/plugins/security/server/lib/authorization/service.ts @@ -10,6 +10,7 @@ import { SpacesPlugin } from 'x-pack/plugins/spaces/types'; import { XPackFeature, XPackMainPlugin } from 'x-pack/plugins/xpack_main/xpack_main'; // @ts-ignore import { getClient } from '../../../../../server/lib/get_client_shield'; +import { APPLICATION_PREFIX } from '../../../common/constants'; import { OptionalPlugin } from '../optional_plugin'; import { Actions, actionsFactory } from './actions'; import { CheckPrivilegesWithRequest, checkPrivilegesWithRequestFactory } from './check_privileges'; @@ -39,7 +40,7 @@ export function createAuthorizationService( const config = server.config(); const actions = actionsFactory(config); - const application = `kibana-${config.get('kibana.index')}`; + const application = `${APPLICATION_PREFIX}${config.get('kibana.index')}`; const checkPrivilegesWithRequest = checkPrivilegesWithRequestFactory( actions, application, diff --git a/x-pack/plugins/security/server/routes/api/public/roles/get.js b/x-pack/plugins/security/server/routes/api/public/roles/get.js index 18aa7e1d79fb2..38b50885a76f8 100644 --- a/x-pack/plugins/security/server/routes/api/public/roles/get.js +++ b/x-pack/plugins/security/server/routes/api/public/roles/get.js @@ -5,7 +5,7 @@ */ import _ from 'lodash'; import Boom from 'boom'; -import { GLOBAL_RESOURCE } from '../../../../../common/constants'; +import { GLOBAL_RESOURCE, RESERVED_PRIVILEGES_APPLICATION_WILDCARD } from '../../../../../common/constants'; import { wrapError } from '../../../../lib/errors'; import { PrivilegeSerializer, ResourceSerializer } from '../../../../lib/authorization'; @@ -13,13 +13,27 @@ export function initGetRolesApi(server, callWithRequest, routePreCheckLicenseFn, const transformKibanaApplicationsFromEs = (roleApplications) => { const roleKibanaApplications = roleApplications - .filter(roleApplication => roleApplication.application === application); + .filter( + roleApplication => roleApplication.application === application || + roleApplication.application === RESERVED_PRIVILEGES_APPLICATION_WILDCARD + ); // if any application entry contains an empty resource, we throw an error if (roleKibanaApplications.some(entry => entry.resources.length === 0)) { throw new Error(`ES returned an application entry without resources, can't process this`); } + // if there is an entry with the reserved privileges application wildcard + // and there are privileges which aren't reserved, we won't transform these + if (roleKibanaApplications.some(entry => + entry.application === RESERVED_PRIVILEGES_APPLICATION_WILDCARD && + !entry.privileges.every(privilege => PrivilegeSerializer.isSerializedReservedPrivilege(privilege))) + ) { + return { + success: false + }; + } + // if space privilege assigned globally, we can't transform these if (roleKibanaApplications.some(entry => entry.resources.includes(GLOBAL_RESOURCE) && @@ -54,11 +68,11 @@ export function initGetRolesApi(server, callWithRequest, routePreCheckLicenseFn, } // if any application entry contains the '*' resource in addition to another resource, we can't transform these - {if (roleKibanaApplications.some(entry => entry.resources.includes(GLOBAL_RESOURCE) && entry.resources.length > 1)) { + if (roleKibanaApplications.some(entry => entry.resources.includes(GLOBAL_RESOURCE) && entry.resources.length > 1)) { return { success: false }; - }} + } const allResources = _.flatten(roleKibanaApplications.map(entry => entry.resources)); // if we have improperly formatted resource entries, we can't transform these @@ -125,7 +139,10 @@ export function initGetRolesApi(server, callWithRequest, routePreCheckLicenseFn, const transformUnrecognizedApplicationsFromEs = (roleApplications) => { return _.uniq(roleApplications - .filter(roleApplication => roleApplication.application !== application) + .filter(roleApplication => + roleApplication.application !== application && + roleApplication.application !== RESERVED_PRIVILEGES_APPLICATION_WILDCARD + ) .map(roleApplication => roleApplication.application)); }; diff --git a/x-pack/plugins/security/server/routes/api/public/roles/get.test.js b/x-pack/plugins/security/server/routes/api/public/roles/get.test.js index 8e5d3e0bdda5a..1199544de8693 100644 --- a/x-pack/plugins/security/server/routes/api/public/roles/get.test.js +++ b/x-pack/plugins/security/server/routes/api/public/roles/get.test.js @@ -8,6 +8,7 @@ import Boom from 'boom'; import { initGetRolesApi } from './get'; const application = 'kibana-.kibana'; +const reservedPrivilegesApplicationWildcard = 'kibana-*'; const createMockServer = () => { const mockServer = new Hapi.Server({ debug: false, port: 8080 }); @@ -295,6 +296,58 @@ describe('GET roles', () => { ], }, }); + + getRolesTest(`transforms applications with wildcard and * resource to kibana _reserved privileges`, { + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application: reservedPrivilegesApplicationWildcard, + privileges: ['reserved_customApplication1', 'reserved_customApplication2'], + resources: ['*'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: [ + { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + _reserved: ['customApplication1', 'customApplication2'], + base: [], + feature: {}, + spaces: ['*'] + } + ], + _transform_error: [], + _unrecognized_applications: [], + }, + ], + }, + }); }); describe('space', () => { @@ -697,6 +750,51 @@ describe('GET roles', () => { }, }); + getRolesTest(`space privilege with application wildcard returns empty kibana section with _transform_error set to ['kibana']`, { + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application: reservedPrivilegesApplicationWildcard, + privileges: ['space_read'], + resources: ['space:engineering'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: [ + { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + ], + }, + }); + getRolesTest(`global base privilege assigned at a space returns empty kibana section with _transform_error set to ['kibana']`, { callWithRequestImpl: async () => ({ first_role: { @@ -747,6 +845,51 @@ describe('GET roles', () => { }, }); + getRolesTest(`global base privilege with application wildcard returns empty kibana section with _transform_error set to ['kibana']`, { + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application: reservedPrivilegesApplicationWildcard, + privileges: ['all'], + resources: ['*'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: [ + { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + ], + }, + }); + getRolesTest(`reserved privilege assigned at a space returns empty kibana section with _transform_error set to ['kibana']`, { callWithRequestImpl: async () => ({ first_role: { @@ -1363,6 +1506,57 @@ describe('GET role', () => { }, }, }); + + getRoleTest(`transforms applications with wildcard and * resource to kibana _reserved privileges`, { + name: 'first_role', + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application: reservedPrivilegesApplicationWildcard, + privileges: ['reserved_customApplication1', 'reserved_customApplication2'], + resources: ['*'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [ + { + _reserved: ['customApplication1', 'customApplication2'], + base: [], + feature: {}, + spaces: ['*'] + } + ], + _transform_error: [], + _unrecognized_applications: [], + }, + }, + }); }); describe('space', () => { @@ -1678,6 +1872,50 @@ describe('GET role', () => { }, }); + getRoleTest(`space privilege with application wildcard returns empty kibana section with _transform_error set to ['kibana']`, { + name: 'first_role', + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application: reservedPrivilegesApplicationWildcard, + privileges: ['space_read'], + resources: ['space:engineering'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + }, + }); + getRoleTest(`global base privilege assigned at a space returns empty kibana section with _transform_error set to ['kibana']`, { name: 'first_role', callWithRequestImpl: async () => ({ @@ -1727,6 +1965,51 @@ describe('GET role', () => { }, }); + getRoleTest(`global base privilege with application wildcard returns empty kibana section with _transform_error set to ['kibana']`, { + name: 'first_role', + callWithRequestImpl: async () => ({ + first_role: { + cluster: [], + indices: [], + applications: [ + { + application: reservedPrivilegesApplicationWildcard, + privileges: ['all'], + resources: ['*'], + } + ], + run_as: [], + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + }, + }), + asserts: { + statusCode: 200, + result: { + name: 'first_role', + metadata: { + _reserved: true, + }, + transient_metadata: { + enabled: true, + }, + elasticsearch: { + cluster: [], + indices: [], + run_as: [], + }, + kibana: [], + _transform_error: ['kibana'], + _unrecognized_applications: [], + }, + }, + }); + + getRoleTest(`reserved privilege assigned at a space returns empty kibana section with _transform_error set to ['kibana']`, { name: 'first_role', callWithRequestImpl: async () => ({ From 560956316c7e56c4bed1403998cb363e04631ba3 Mon Sep 17 00:00:00 2001 From: kobelb Date: Mon, 1 Apr 2019 16:18:27 -0700 Subject: [PATCH 58/62] Updating forgotten snapshot --- .../__snapshots__/privilege_space_form.test.tsx.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap index 02431b500bad7..7bc819fdd23c3 100644 --- a/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap +++ b/x-pack/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap @@ -314,13 +314,13 @@ exports[` renders without crashing 1`] = ` } calculatedPrivileges={ Object { - "_reserved": undefined, "base": Object { "actualPrivilege": "none", "actualPrivilegeSource": 40, "isDirectlyAssigned": true, }, "feature": Object {}, + "reserved": undefined, } } disabled={false} From 1f3e7094a84c9badfd78b547136d3bbd2a105143 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 2 Apr 2019 09:35:18 -0700 Subject: [PATCH 59/62] Validating reserved privileges --- .../feature_registry/feature_registry.test.ts | 64 +++++++++++++++++-- .../lib/feature_registry/feature_registry.ts | 7 +- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts b/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts index c1af9f4a6e8c3..cfe6719489b9e 100644 --- a/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts +++ b/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.test.ts @@ -168,6 +168,32 @@ describe('FeatureRegistry', () => { ); }); + it(`prevents reserved privileges from specifying app entries that don't exist at the root level`, () => { + const feature: Feature = { + id: 'test-feature', + name: 'Test Feature', + app: ['bar'], + privileges: {}, + reserved: { + description: 'something', + privilege: { + savedObject: { + all: [], + read: [], + }, + ui: [], + app: ['foo', 'bar', 'baz'], + }, + }, + }; + + const featureRegistry = new FeatureRegistry(); + + expect(() => featureRegistry.register(feature)).toThrowErrorMatchingInlineSnapshot( + `"Feature privilege test-feature.reserved has unknown app entries: foo, baz"` + ); + }); + it(`prevents privileges from specifying catalogue entries that don't exist at the root level`, () => { const feature: Feature = { id: 'test-feature', @@ -194,6 +220,34 @@ describe('FeatureRegistry', () => { ); }); + it(`prevents reserved privileges from specifying catalogue entries that don't exist at the root level`, () => { + const feature: Feature = { + id: 'test-feature', + name: 'Test Feature', + app: [], + catalogue: ['bar'], + privileges: {}, + reserved: { + description: 'something', + privilege: { + catalogue: ['foo', 'bar', 'baz'], + savedObject: { + all: [], + read: [], + }, + ui: [], + app: [], + }, + }, + }; + + const featureRegistry = new FeatureRegistry(); + + expect(() => featureRegistry.register(feature)).toThrowErrorMatchingInlineSnapshot( + `"Feature privilege test-feature.reserved has unknown catalogue entries: foo, baz"` + ); + }); + it(`prevents privileges from specifying management sections that don't exist at the root level`, () => { const feature: Feature = { id: 'test-feature', @@ -226,7 +280,7 @@ describe('FeatureRegistry', () => { ); }); - it(`prevents privileges from specifying management entries that don't exist at the root level`, () => { + it(`prevents reserved privileges from specifying management entries that don't exist at the root level`, () => { const feature: Feature = { id: 'test-feature', name: 'Test Feature', @@ -235,8 +289,10 @@ describe('FeatureRegistry', () => { management: { kibana: ['hey'], }, - privileges: { - all: { + privileges: {}, + reserved: { + description: 'something', + privilege: { catalogue: ['bar'], management: { kibana: ['hey-there'], @@ -254,7 +310,7 @@ describe('FeatureRegistry', () => { const featureRegistry = new FeatureRegistry(); expect(() => featureRegistry.register(feature)).toThrowErrorMatchingInlineSnapshot( - `"Feature privilege test-feature.all has unknown management entries for section kibana: hey-there"` + `"Feature privilege test-feature.reserved has unknown management entries for section kibana: hey-there"` ); }); diff --git a/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts b/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts index 633074458e17e..e536529f7d6d2 100644 --- a/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts +++ b/x-pack/plugins/xpack_main/server/lib/feature_registry/feature_registry.ts @@ -141,7 +141,12 @@ function validateFeature(feature: FeatureWithAllOrReadPrivileges) { // 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; - Object.entries(feature.privileges).forEach(([privilegeId, privilegeDefinition]) => { + 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'); } From 2c5ab8f792f139685c83c439e6954529ffe18390 Mon Sep 17 00:00:00 2001 From: kobelb Date: Thu, 4 Apr 2019 16:02:14 -0700 Subject: [PATCH 60/62] Updating imports --- .../apps/machine_learning/feature_controls/ml_security.ts | 2 +- .../apps/machine_learning/feature_controls/ml_spaces.ts | 2 +- .../apps/monitoring/feature_controls/monitoring_security.ts | 2 +- .../apps/monitoring/feature_controls/monitoring_spaces.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts index ff1c913605051..ef29015ddc846 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; -import { SecurityService } from 'x-pack/test/common/services'; +import { SecurityService } from '../../../../common/services'; import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; // tslint:disable:no-default-export diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts index 7f0c0ff427a44..5a6d5bb5ab320 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; -import { SpacesService } from 'x-pack/test/common/services'; +import { SpacesService } from '../../../../common/services'; import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; // tslint:disable:no-default-export diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts index 94ba35e41183e..37d046769c471 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; -import { SecurityService } from 'x-pack/test/common/services'; +import { SecurityService } from '../../../../common/services'; import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; // tslint:disable:no-default-export diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts index 48085760ef000..da625df1fc6ad 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; -import { SpacesService } from 'x-pack/test/common/services'; +import { SpacesService } from '../../../../common/services'; import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; // tslint:disable:no-default-export From 951b4f5be1709f9c374fc28fbdb9af41b73e7af4 Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 9 Apr 2019 08:14:08 -0700 Subject: [PATCH 61/62] Removing --esFrom flag, we don't need it anymore --- test/scripts/jenkins_xpack_ci_group.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scripts/jenkins_xpack_ci_group.sh b/test/scripts/jenkins_xpack_ci_group.sh index 99d27b4c77ed0..297401fc24593 100755 --- a/test/scripts/jenkins_xpack_ci_group.sh +++ b/test/scripts/jenkins_xpack_ci_group.sh @@ -36,6 +36,6 @@ tar -xzf "$linuxBuild" -C "$installDir" --strip=1 echo " -> Running functional and api tests" cd "$XPACK_DIR" -node scripts/functional_tests --debug --bail --kibana-install-dir "$installDir" --include-tag "ciGroup$CI_GROUP" --esFrom "source" +node scripts/functional_tests --debug --bail --kibana-install-dir "$installDir" --include-tag "ciGroup$CI_GROUP" echo "" echo "" From 5cdc103aa01c2e42cfc4de94f74bd7ca7d3f5dbe Mon Sep 17 00:00:00 2001 From: kobelb Date: Tue, 9 Apr 2019 10:11:18 -0700 Subject: [PATCH 62/62] Switching from tslint's ignore to eslint's ignore --- .../functional/apps/machine_learning/feature_controls/index.ts | 2 +- .../apps/machine_learning/feature_controls/ml_security.ts | 2 +- .../apps/machine_learning/feature_controls/ml_spaces.ts | 2 +- x-pack/test/functional/apps/machine_learning/index.ts | 2 +- .../test/functional/apps/monitoring/feature_controls/index.ts | 2 +- .../apps/monitoring/feature_controls/monitoring_security.ts | 2 +- .../apps/monitoring/feature_controls/monitoring_spaces.ts | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/index.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/index.ts index 7f31aa6d3e497..5bef6cdb76559 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/index.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/index.ts @@ -5,7 +5,7 @@ */ import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; -// tslint:disable:no-default-export +// eslint-disable-next-line import/no-default-export export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) { describe('feature controls', () => { loadTestFile(require.resolve('./ml_security')); diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts index ef29015ddc846..a6a7a48e8f368 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { SecurityService } from '../../../../common/services'; import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; -// tslint:disable:no-default-export +// eslint-disable-next-line import/no-default-export export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { const esArchiver = getService('esArchiver'); const security: SecurityService = getService('security'); diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts index 5a6d5bb5ab320..36ae88c759e12 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { SpacesService } from '../../../../common/services'; import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; -// tslint:disable:no-default-export +// eslint-disable-next-line import/no-default-export export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { const esArchiver = getService('esArchiver'); const spacesService: SpacesService = getService('spaces'); diff --git a/x-pack/test/functional/apps/machine_learning/index.ts b/x-pack/test/functional/apps/machine_learning/index.ts index ba4dc4a818fef..14694cdf3fcbe 100644 --- a/x-pack/test/functional/apps/machine_learning/index.ts +++ b/x-pack/test/functional/apps/machine_learning/index.ts @@ -5,7 +5,7 @@ */ import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; -// tslint:disable:no-default-export +// eslint-disable-next-line import/no-default-export export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) { describe('machine learning', function() { this.tags('ciGroup3'); diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/index.ts b/x-pack/test/functional/apps/monitoring/feature_controls/index.ts index 0eb26f5e6bef1..35611f391846e 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/index.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/index.ts @@ -5,7 +5,7 @@ */ import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; -// tslint:disable:no-default-export +// eslint-disable-next-line import/no-default-export export default function({ loadTestFile }: KibanaFunctionalTestDefaultProviders) { describe('feature controls', () => { loadTestFile(require.resolve('./monitoring_security')); diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts index 37d046769c471..3e6e9cc67efae 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { SecurityService } from '../../../../common/services'; import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; -// tslint:disable:no-default-export +// eslint-disable-next-line import/no-default-export export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { const esArchiver = getService('esArchiver'); const security: SecurityService = getService('security'); diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts index da625df1fc6ad..58282269a5422 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { SpacesService } from '../../../../common/services'; import { KibanaFunctionalTestDefaultProviders } from '../../../../types/providers'; -// tslint:disable:no-default-export +// eslint-disable-next-line import/no-default-export export default function({ getPageObjects, getService }: KibanaFunctionalTestDefaultProviders) { const esArchiver = getService('esArchiver'); const spacesService: SpacesService = getService('spaces');