Feature Controls - automatically grant access to short urls (#37532)

* automatically grant access to short urls

* adds API integration tests

* Update x-pack/test/api_integration/apis/short_urls/index.ts

Co-Authored-By: Brandon Kobel <brandon.kobel@gmail.com>
This commit is contained in:
Larry Gregory 2019-06-04 07:47:45 -04:00 committed by GitHub
parent 227feeb243
commit 54af64d416
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 14 deletions

View file

@ -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`, () => {

View file

@ -371,7 +371,7 @@ function applyAutomaticAllPrivilegeGrants(...allPrivileges: Array<FeatureKibanaP
allPrivileges.forEach(allPrivilege => {
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']);
}
});
}

View file

@ -28,7 +28,7 @@ const buildKibanaFeatures = (savedObjectTypes: string[]) => {
read: {
savedObject: {
all: [],
read: ['index-pattern', 'search', 'url'],
read: ['index-pattern', 'search'],
},
ui: ['show'],
},

View file

@ -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'));
});
}

View file

@ -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<string, any>) => {
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<string, any>) => {
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);
}
});
});
});
});
}

View file

@ -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'));
});
}