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 fc9585424d00..5fceeac98d85 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 @@ -46,7 +46,7 @@ describe('FeatureRegistry', () => { app: ['app1'], savedObject: { all: ['space', 'etc', 'telemetry'], - read: ['canvas', 'config'], + read: ['canvas', 'config', 'url'], }, api: ['someApiEndpointTag', 'anotherEndpointTag'], ui: ['allowsFoo', 'showBar', 'showBaz'], @@ -62,7 +62,7 @@ describe('FeatureRegistry', () => { app: ['app1'], savedObject: { all: ['space', 'etc', 'telemetry'], - read: ['canvas', 'config'], + read: ['canvas', 'config', 'url'], }, api: ['someApiEndpointTag', 'anotherEndpointTag'], ui: ['allowsFoo', 'showBar', 'showBaz'], @@ -105,7 +105,7 @@ describe('FeatureRegistry', () => { expect(allPrivilege.savedObject.all).toEqual(['telemetry']); }); - it(`automatically grants 'read' access to config saved objects for both privileges`, () => { + it(`automatically grants 'read' access to config and url saved objects for both privileges`, () => { const feature: Feature = { id: 'test-feature', name: 'Test Feature', @@ -134,11 +134,11 @@ describe('FeatureRegistry', () => { const allPrivilege = result[0].privileges.all; const readPrivilege = result[0].privileges.read; - expect(allPrivilege.savedObject.read).toEqual(['config']); - expect(readPrivilege.savedObject.read).toEqual(['config']); + expect(allPrivilege.savedObject.read).toEqual(['config', 'url']); + expect(readPrivilege.savedObject.read).toEqual(['config', 'url']); }); - it(`automatically grants 'all' access to telemetry and 'read' to config saved objects for the reserved privilege`, () => { + it(`automatically grants 'all' access to telemetry and 'read' to [config, url] saved objects for the reserved privilege`, () => { const feature: Feature = { id: 'test-feature', name: 'Test Feature', @@ -162,7 +162,7 @@ describe('FeatureRegistry', () => { const reservedPrivilege = result[0]!.reserved!.privilege; expect(reservedPrivilege.savedObject.all).toEqual(['telemetry']); - expect(reservedPrivilege.savedObject.read).toEqual(['config']); + expect(reservedPrivilege.savedObject.read).toEqual(['config', 'url']); }); it(`does not duplicate the automatic grants if specified on the incoming feature`, () => { @@ -175,14 +175,14 @@ describe('FeatureRegistry', () => { ui: [], savedObject: { all: ['telemetry'], - read: ['config'], + read: ['config', 'url'], }, }, read: { ui: [], savedObject: { all: [], - read: ['config'], + read: ['config', 'url'], }, }, }, @@ -195,8 +195,8 @@ describe('FeatureRegistry', () => { const allPrivilege = result[0].privileges.all; const readPrivilege = result[0].privileges.read; expect(allPrivilege.savedObject.all).toEqual(['telemetry']); - expect(allPrivilege.savedObject.read).toEqual(['config']); - expect(readPrivilege.savedObject.read).toEqual(['config']); + expect(allPrivilege.savedObject.read).toEqual(['config', 'url']); + expect(readPrivilege.savedObject.read).toEqual(['config', 'url']); }); it(`does not allow duplicate features to be registered`, () => { 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 a09a17384bd2..48397a87b8d2 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 @@ -371,7 +371,7 @@ function applyAutomaticAllPrivilegeGrants(...allPrivileges: Array { if (allPrivilege) { allPrivilege.savedObject.all = uniq([...allPrivilege.savedObject.all, 'telemetry']); - allPrivilege.savedObject.read = uniq([...allPrivilege.savedObject.read, 'config']); + allPrivilege.savedObject.read = uniq([...allPrivilege.savedObject.read, 'config', 'url']); } }); } @@ -381,7 +381,7 @@ function applyAutomaticReadPrivilegeGrants( ) { readPrivileges.forEach(readPrivilege => { if (readPrivilege) { - readPrivilege.savedObject.read = uniq([...readPrivilege.savedObject.read, 'config']); + readPrivilege.savedObject.read = uniq([...readPrivilege.savedObject.read, 'config', 'url']); } }); } 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 cb9bbfeb6dfb..151732e4b1d5 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 @@ -28,7 +28,7 @@ const buildKibanaFeatures = (savedObjectTypes: string[]) => { read: { savedObject: { all: [], - read: ['index-pattern', 'search', 'url'], + read: ['index-pattern', 'search'], }, ui: ['show'], }, diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index 3bde650b8359..e4de3b44c070 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -23,5 +23,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./apm')); loadTestFile(require.resolve('./siem')); loadTestFile(require.resolve('./code')); + loadTestFile(require.resolve('./short_urls')); }); } diff --git a/x-pack/test/api_integration/apis/short_urls/feature_controls.ts b/x-pack/test/api_integration/apis/short_urls/feature_controls.ts new file mode 100644 index 000000000000..bac06e875a5e --- /dev/null +++ b/x-pack/test/api_integration/apis/short_urls/feature_controls.ts @@ -0,0 +1,119 @@ +/* + * 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 '@kbn/expect'; +import { SecurityService } from '../../../common/services'; +import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; + +// eslint-disable-next-line import/no-default-export +export default function featureControlsTests({ getService }: KibanaFunctionalTestDefaultProviders) { + const supertest = getService('supertestWithoutAuth'); + const security: SecurityService = getService('security'); + + describe('feature controls', () => { + const kibanaUsername = 'kibana_user'; + const kibanaUserRoleName = 'kibana_user'; + + const kibanaUserPassword = `${kibanaUsername}-password`; + + let urlId: string; + + // a sampling of features to test against + const features = [ + { + featureId: 'discover', + canAccess: true, + }, + { + featureId: 'dashboard', + canAccess: true, + }, + { + featureId: 'visualize', + canAccess: true, + }, + { + featureId: 'infrastructure', + canAccess: true, + }, + { + featureId: 'canvas', + canAccess: true, + }, + { + featureId: 'maps', + canAccess: true, + }, + { + featureId: 'unknown-feature', + canAccess: false, + }, + ]; + + before(async () => { + for (const feature of features) { + await security.role.create(`${feature.featureId}-role`, { + kibana: [ + { + base: [], + feature: { + [feature.featureId]: ['read'], + }, + spaces: ['*'], + }, + ], + }); + + await security.user.create(`${feature.featureId}-user`, { + password: kibanaUserPassword, + roles: [`${feature.featureId}-role`], + full_name: 'a kibana user', + }); + } + + await security.user.create(kibanaUsername, { + password: kibanaUserPassword, + roles: [kibanaUserRoleName], + full_name: 'a kibana user', + }); + + await supertest + .post(`/api/shorten_url`) + .auth(kibanaUsername, kibanaUserPassword) + .set('kbn-xsrf', 'foo') + .send({ url: '/app/kibana#foo/bar/baz' }) + .then((resp: Record) => { + urlId = resp.body.urlId; + }); + }); + + after(async () => { + const users = features.map(feature => security.user.delete(`${feature.featureId}-user`)); + const roles = features.map(feature => security.role.delete(`${feature.featureId}-role`)); + await Promise.all([...users, ...roles]); + await security.user.delete(kibanaUsername); + }); + + features.forEach(feature => { + it(`users with "read" access to ${feature.featureId} ${ + feature.canAccess ? 'should' : 'should not' + } be able to access short-urls`, async () => { + await supertest + .get(`/goto/${urlId}`) + .auth(`${feature.featureId}-user`, kibanaUserPassword) + .then((resp: Record) => { + if (feature.canAccess) { + expect(resp.status).to.eql(302); + expect(resp.headers.location).to.eql('/app/kibana#foo/bar/baz'); + } else { + expect(resp.status).to.eql(500); + expect(resp.headers.location).to.eql(undefined); + } + }); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/short_urls/index.ts b/x-pack/test/api_integration/apis/short_urls/index.ts new file mode 100644 index 000000000000..8f12c6f2953b --- /dev/null +++ b/x-pack/test/api_integration/apis/short_urls/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers'; + +// eslint-disable-next-line import/no-default-export +export default function shortUrlsApiIntegrationTests({ + loadTestFile, +}: KibanaFunctionalTestDefaultProviders) { + describe('Short URLs', () => { + loadTestFile(require.resolve('./feature_controls')); + }); +}