Feature Controls - Add saved objects management feature (#35982)

* adds saved objects management feature

* document TODO

* testing UI Capbilities

* update SOM functional tests

* fixing privilege action mapping from merge

* remove describe.only

* add new feature to expected feature list

* prettier

* adds simple unit tests for OSS feature registration

* fix tests following merge
This commit is contained in:
Larry Gregory 2019-05-07 12:45:38 -04:00 committed by GitHub
parent 1cc0fa37f6
commit f37e04484a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1346 additions and 736 deletions

View file

@ -291,6 +291,7 @@ export default function (kibana) {
kibana: {
settings: true,
index_patterns: true,
objects: true,
},
}
};

View file

@ -87,7 +87,8 @@ function destroyObjectsTable() {
uiRoutes
.when('/management/kibana/objects', {
template: objectIndexHTML,
k7Breadcrumbs: getIndexBreadcrumbs
k7Breadcrumbs: getIndexBreadcrumbs,
requireUICapability: 'management.kibana.objects',
})
.when('/management/kibana/objects/:service', {
redirectTo: '/management/kibana/objects'

View file

@ -39,7 +39,8 @@ const location = 'SavedObject view';
uiRoutes
.when('/management/kibana/objects/:service/:id', {
template: objectViewHTML,
k7Breadcrumbs: getViewBreadcrumbs
k7Breadcrumbs: getViewBreadcrumbs,
requireUICapability: 'management.kibana.objects',
});
uiModules.get('apps/management', ['monospaced.elastic'])
@ -89,7 +90,7 @@ uiModules.get('apps/management', ['monospaced.elastic'])
field.type = 'boolean';
field.value = field.value;
} else if (_.isPlainObject(field.value)) {
// do something recursive
// do something recursive
return _.reduce(field.value, _.partialRight(createField, parents), memo);
}

View file

@ -13,6 +13,10 @@ import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder';
export class FeaturePrivilegeSavedObjectsManagementBuilder extends BaseFeaturePrivilegeBuilder {
public getActions(privilegeDefinition: FeatureKibanaPrivileges, feature: Feature): string[] {
// TODO: Revisit if/when savedObjectsManagement UI Capabilities are refactored
if (feature.id !== 'savedObjectsManagement') {
return [];
}
return uniq([
...flatten(
privilegeDefinition.savedObject.all.map(type => [

View file

@ -205,14 +205,6 @@ describe('features', () => {
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('savedObjectsManagement', 'all-savedObject-all-1', 'delete'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-all-1', 'edit'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-all-1', 'read'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-all-2', 'delete'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-all-2', 'edit'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-all-2', 'read'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-read-1', 'read'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-read-2', 'read'),
actions.ui.get('foo', 'all-ui-1'),
actions.ui.get('foo', 'all-ui-2'),
actions.allHack,
@ -240,14 +232,6 @@ describe('features', () => {
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('savedObjectsManagement', 'read-savedObject-all-1', 'delete'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-1', 'edit'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-1', 'read'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-2', 'delete'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-2', 'edit'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-2', 'read'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-read-1', 'read'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-read-2', 'read'),
actions.ui.get('foo', 'read-ui-1'),
actions.ui.get('foo', 'read-ui-2'),
],
@ -445,14 +429,6 @@ describe('features', () => {
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('savedObjectsManagement', 'bar-savedObject-all-1', 'delete'),
actions.ui.get('savedObjectsManagement', 'bar-savedObject-all-1', 'edit'),
actions.ui.get('savedObjectsManagement', 'bar-savedObject-all-1', 'read'),
actions.ui.get('savedObjectsManagement', 'bar-savedObject-all-2', 'delete'),
actions.ui.get('savedObjectsManagement', 'bar-savedObject-all-2', 'edit'),
actions.ui.get('savedObjectsManagement', 'bar-savedObject-all-2', 'read'),
actions.ui.get('savedObjectsManagement', 'bar-savedObject-read-1', 'read'),
actions.ui.get('savedObjectsManagement', 'bar-savedObject-read-2', 'read'),
actions.ui.get('foo', 'bar-ui-1'),
actions.ui.get('foo', 'bar-ui-2'),
actions.ui.get('catalogue', 'all-catalogue-1'),
@ -479,14 +455,6 @@ describe('features', () => {
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('savedObjectsManagement', 'all-savedObject-all-1', 'delete'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-all-1', 'edit'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-all-1', 'read'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-all-2', 'delete'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-all-2', 'edit'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-all-2', 'read'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-read-1', 'read'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-read-2', 'read'),
actions.ui.get('foo', 'all-ui-1'),
actions.ui.get('foo', 'all-ui-2'),
actions.ui.get('catalogue', 'read-catalogue-1'),
@ -513,14 +481,6 @@ describe('features', () => {
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('savedObjectsManagement', 'read-savedObject-all-1', 'delete'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-1', 'edit'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-1', 'read'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-2', 'delete'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-2', 'edit'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-2', 'read'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-read-1', 'read'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-read-2', 'read'),
actions.ui.get('foo', 'read-ui-1'),
actions.ui.get('foo', 'read-ui-2'),
actions.allHack,
@ -612,14 +572,6 @@ describe('features', () => {
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('savedObjectsManagement', 'read-savedObject-all-1', 'delete'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-1', 'edit'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-1', 'read'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-2', 'delete'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-2', 'edit'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-2', 'read'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-read-1', 'read'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-read-2', 'read'),
actions.ui.get('foo', 'read-ui-1'),
actions.ui.get('foo', 'read-ui-2'),
]);
@ -670,6 +622,90 @@ describe('features', () => {
});
});
describe('savedObjectsManagement feature', () => {
test(`saved objects privileges create 'savedObjectsManagement' actions`, () => {
const features: Feature[] = [
{
id: 'savedObjectsManagement',
name: 'Saved Objects Management',
icon: 'arrowDown',
app: [],
catalogue: [],
management: {},
privileges: {
all: {
ui: [],
savedObject: {
all: ['all-savedObject-all-1'],
read: ['all-savedObject-read-1'],
},
},
read: {
ui: [],
savedObject: {
all: ['read-savedObject-all-1'],
read: ['read-savedObject-read-1'],
},
},
},
},
];
const mockXPackMainPlugin = {
getFeatures: jest.fn().mockReturnValue(features),
};
const privileges = privilegesFactory(actions, mockXPackMainPlugin as any);
const actual = privileges.get();
expect(actual).toHaveProperty('features.savedObjectsManagement.all', [
actions.login,
actions.version,
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-read-1', 'bulk_get'),
actions.savedObject.get('all-savedObject-read-1', 'get'),
actions.savedObject.get('all-savedObject-read-1', 'find'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-all-1', 'delete'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-all-1', 'edit'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-all-1', 'read'),
actions.ui.get('savedObjectsManagement', 'all-savedObject-read-1', 'read'),
actions.allHack,
]);
expect(actual).toHaveProperty('features.savedObjectsManagement.read', [
actions.login,
actions.version,
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-read-1', 'bulk_get'),
actions.savedObject.get('read-savedObject-read-1', 'get'),
actions.savedObject.get('read-savedObject-read-1', 'find'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-1', 'delete'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-1', 'edit'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-all-1', 'read'),
actions.ui.get('savedObjectsManagement', 'read-savedObject-read-1', 'read'),
]);
});
});
describe('reserved', () => {
test('actions defined at the feature cascade to the privileges', () => {
const features: Feature[] = [
@ -814,14 +850,6 @@ describe('reserved', () => {
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'),
]);

View file

@ -60,6 +60,21 @@ function toggleDisabledFeatures(
});
});
// TODO: Revisit if/when savedObjectsManagement UI Capabilities are refactored
if (feature.id === 'savedObjectsManagement') {
const capability: Record<string, Record<string, boolean>> = uiCapabilities[
feature.id
] as Record<string, Record<string, boolean>>;
Object.keys(capability).forEach(savedObjectType => {
Object.keys(capability[savedObjectType]).forEach(typeCapability => {
capability[savedObjectType][typeCapability] = false;
});
});
continue;
}
// Disable "sub features" that match the disabled feature
if (uiCapabilities.hasOwnProperty(feature.id)) {
const capability = uiCapabilities[feature.id];

View file

@ -128,7 +128,8 @@ export const xpackMain = (kibana) => {
mirrorPluginStatus(server.plugins.elasticsearch, this, 'yellow', 'red');
setupXPackMain(server);
registerOssFeatures(server.plugins.xpack_main.registerFeature);
const { types: savedObjectTypes } = server.savedObjects;
registerOssFeatures(server.plugins.xpack_main.registerFeature, savedObjectTypes);
// register routes
xpackInfoRoute(server);

View file

@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { FeatureRegistry } from './feature_registry';
import { registerOssFeatures } from './register_oss_features';
describe('registerOssFeatures', () => {
it('allows features to be registered', () => {
const registry = new FeatureRegistry();
const savedObjectTypes = ['foo', 'bar'];
registerOssFeatures(feature => registry.register(feature), savedObjectTypes);
const features = registry.getAll();
expect(features.map(f => f.id)).toMatchInlineSnapshot(`
Array [
"discover",
"visualize",
"dashboard",
"dev_tools",
"advancedSettings",
"indexPatterns",
"savedObjectsManagement",
"timelion",
]
`);
});
});

View file

@ -6,189 +6,219 @@
import { i18n } from '@kbn/i18n';
import { Feature } from './feature_registry';
const kibanaFeatures: Feature[] = [
{
id: 'discover',
name: i18n.translate('xpack.main.featureRegistry.discoverFeatureName', {
defaultMessage: 'Discover',
}),
icon: 'discoverApp',
navLinkId: 'kibana:discover',
app: ['kibana'],
catalogue: ['discover'],
privileges: {
all: {
savedObject: {
all: ['search', 'url'],
read: ['index-pattern'],
const buildKibanaFeatures = (savedObjectTypes: string[]) => {
return [
{
id: 'discover',
name: i18n.translate('xpack.main.featureRegistry.discoverFeatureName', {
defaultMessage: 'Discover',
}),
icon: 'discoverApp',
navLinkId: 'kibana:discover',
app: ['kibana'],
catalogue: ['discover'],
privileges: {
all: {
savedObject: {
all: ['search', 'url'],
read: ['index-pattern'],
},
ui: ['show', 'createShortUrl', 'save'],
},
ui: ['show', 'createShortUrl', 'save'],
},
read: {
savedObject: {
all: [],
read: ['index-pattern', 'search', 'url'],
read: {
savedObject: {
all: [],
read: ['index-pattern', 'search', 'url'],
},
ui: ['show'],
},
ui: ['show'],
},
},
},
{
id: 'visualize',
name: i18n.translate('xpack.main.featureRegistry.visualizeFeatureName', {
defaultMessage: 'Visualize',
}),
icon: 'visualizeApp',
navLinkId: 'kibana:visualize',
app: ['kibana'],
catalogue: ['visualize'],
privileges: {
all: {
savedObject: {
all: ['visualization', 'url'],
read: ['index-pattern', 'search'],
{
id: 'visualize',
name: i18n.translate('xpack.main.featureRegistry.visualizeFeatureName', {
defaultMessage: 'Visualize',
}),
icon: 'visualizeApp',
navLinkId: 'kibana:visualize',
app: ['kibana'],
catalogue: ['visualize'],
privileges: {
all: {
savedObject: {
all: ['visualization', 'url'],
read: ['index-pattern', 'search'],
},
ui: ['show', 'createShortUrl', 'delete', 'save'],
},
ui: ['show', 'createShortUrl', 'delete', 'save'],
},
read: {
savedObject: {
all: [],
read: ['index-pattern', 'search', 'visualization'],
read: {
savedObject: {
all: [],
read: ['index-pattern', 'search', 'visualization'],
},
ui: ['show'],
},
ui: ['show'],
},
},
},
{
id: 'dashboard',
name: i18n.translate('xpack.main.featureRegistry.dashboardFeatureName', {
defaultMessage: 'Dashboard',
}),
icon: 'dashboardApp',
navLinkId: 'kibana:dashboard',
app: ['kibana'],
catalogue: ['dashboard'],
privileges: {
all: {
savedObject: {
all: ['dashboard', 'url'],
read: [
'index-pattern',
'search',
'visualization',
'timelion-sheet',
'canvas-workpad',
'map',
],
{
id: 'dashboard',
name: i18n.translate('xpack.main.featureRegistry.dashboardFeatureName', {
defaultMessage: 'Dashboard',
}),
icon: 'dashboardApp',
navLinkId: 'kibana:dashboard',
app: ['kibana'],
catalogue: ['dashboard'],
privileges: {
all: {
savedObject: {
all: ['dashboard', 'url'],
read: [
'index-pattern',
'search',
'visualization',
'timelion-sheet',
'canvas-workpad',
'map',
],
},
ui: ['createNew', 'show', 'showWriteControls'],
},
ui: ['createNew', 'show', 'showWriteControls'],
},
read: {
savedObject: {
all: [],
read: [
'index-pattern',
'search',
'visualization',
'timelion-sheet',
'canvas-workpad',
'map',
'dashboard',
],
read: {
savedObject: {
all: [],
read: [
'index-pattern',
'search',
'visualization',
'timelion-sheet',
'canvas-workpad',
'map',
'dashboard',
],
},
ui: ['show'],
},
ui: ['show'],
},
},
},
{
id: 'dev_tools',
name: i18n.translate('xpack.main.featureRegistry.devToolsFeatureName', {
defaultMessage: 'Dev Tools',
}),
icon: 'devToolsApp',
navLinkId: 'kibana:dev_tools',
app: ['kibana'],
catalogue: ['console', 'searchprofiler', 'grokdebugger'],
privileges: {
all: {
api: ['console'],
savedObject: {
all: [],
read: [],
{
id: 'dev_tools',
name: i18n.translate('xpack.main.featureRegistry.devToolsFeatureName', {
defaultMessage: 'Dev Tools',
}),
icon: 'devToolsApp',
navLinkId: 'kibana:dev_tools',
app: ['kibana'],
catalogue: ['console', 'searchprofiler', 'grokdebugger'],
privileges: {
all: {
api: ['console'],
savedObject: {
all: [],
read: [],
},
ui: ['show', 'save'],
},
read: {
api: ['console'],
savedObject: {
all: [],
read: [],
},
ui: ['show'],
},
ui: ['show', 'save'],
},
read: {
api: ['console'],
savedObject: {
all: [],
read: [],
privilegesTooltip: i18n.translate('xpack.main.featureRegistry.devToolsPrivilegesTooltip', {
defaultMessage:
'User should also be granted the appropriate Elasticsearch cluster and index privileges',
}),
},
{
id: 'advancedSettings',
name: i18n.translate('xpack.main.featureRegistry.advancedSettingsFeatureName', {
defaultMessage: 'Advanced Settings',
}),
icon: 'advancedSettingsApp',
app: ['kibana'],
catalogue: ['advanced_settings'],
management: {
kibana: ['settings'],
},
privileges: {
all: {
savedObject: {
all: ['config'],
read: [],
},
ui: ['save'],
},
read: {
savedObject: {
all: [],
read: [],
},
ui: [],
},
ui: ['show'],
},
},
privilegesTooltip: i18n.translate('xpack.main.featureRegistry.devToolsPrivilegesTooltip', {
defaultMessage:
'User should also be granted the appropriate Elasticsearch cluster and index privileges',
}),
},
{
id: 'advancedSettings',
name: i18n.translate('xpack.main.featureRegistry.advancedSettingsFeatureName', {
defaultMessage: 'Advanced Settings',
}),
icon: 'advancedSettingsApp',
app: ['kibana'],
catalogue: ['advanced_settings'],
management: {
kibana: ['settings'],
},
privileges: {
all: {
savedObject: {
all: ['config'],
read: [],
},
ui: ['save'],
{
id: 'indexPatterns',
name: i18n.translate('xpack.main.featureRegistry.indexPatternFeatureName', {
defaultMessage: 'Index Pattern Management',
}),
icon: 'indexPatternApp',
app: ['kibana'],
catalogue: ['index_patterns'],
management: {
kibana: ['index_patterns'],
},
read: {
savedObject: {
all: [],
read: [],
privileges: {
all: {
savedObject: {
all: ['index-pattern'],
read: [],
},
ui: ['save'],
},
read: {
savedObject: {
all: [],
read: ['index-pattern'],
},
ui: [],
},
ui: [],
},
},
},
{
id: 'indexPatterns',
name: i18n.translate('xpack.main.featureRegistry.indexPatternFeatureName', {
defaultMessage: 'Index Pattern Management',
}),
icon: 'indexPatternApp',
app: ['kibana'],
catalogue: ['index_patterns'],
management: {
kibana: ['index_patterns'],
},
privileges: {
all: {
savedObject: {
all: ['index-pattern'],
read: [],
},
ui: ['save'],
{
id: 'savedObjectsManagement',
name: i18n.translate('xpack.main.featureRegistry.savedObjectsManagementFeatureName', {
defaultMessage: 'Saved Objects Management',
}),
icon: 'savedObjectsApp',
app: ['kibana'],
catalogue: ['saved_objects'],
management: {
kibana: ['objects'],
},
read: {
savedObject: {
all: [],
read: ['index-pattern'],
privileges: {
all: {
savedObject: {
all: [...savedObjectTypes],
read: [],
},
ui: [],
},
read: {
savedObject: {
all: [],
read: [...savedObjectTypes],
},
ui: [],
},
ui: [],
},
},
},
];
];
};
const timelionFeatures: Feature[] = [
{
@ -217,7 +247,11 @@ const timelionFeatures: Feature[] = [
},
];
export function registerOssFeatures(registerFeature: (feature: Feature) => void) {
export function registerOssFeatures(
registerFeature: (feature: Feature) => void,
savedObjectTypes: string[]
) {
const kibanaFeatures = buildKibanaFeatures(savedObjectTypes);
for (const feature of [...kibanaFeatures, ...timelionFeatures]) {
registerFeature(feature);
}

File diff suppressed because it is too large Load diff

View file

@ -125,6 +125,7 @@ export default function({ getService }: KibanaFunctionalTestDefaultProviders) {
'timelion',
'graph',
'monitoring',
'savedObjectsManagement',
'ml',
'apm',
'canvas',

View file

@ -141,7 +141,126 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa
});
});
describe('global visualize read privileges', () => {
describe('global saved object management read privileges', () => {
before(async () => {
await security.role.create('global_som_read_role', {
elasticsearch: {
indices: [{ names: ['logstash-*'], privileges: ['read', 'view_index_metadata'] }],
},
kibana: [
{
feature: {
savedObjectsManagement: ['read'],
},
spaces: ['*'],
},
],
});
await security.user.create('global_som_read_user', {
password: 'global_som_read_user-password',
roles: ['global_som_read_role'],
full_name: 'test user',
});
await PageObjects.security.logout();
await PageObjects.security.login('global_som_read_user', 'global_som_read_user-password', {
expectSpaceSelector: false,
});
});
after(async () => {
await Promise.all([
security.role.delete('global_som_read_role'),
security.user.delete('global_som_read_user'),
PageObjects.security.logout(),
]);
});
describe('listing', () => {
before(async () => {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaSavedObjects();
});
it('shows all saved objects', async () => {
const objects = await PageObjects.settings.getSavedObjectsInTable();
expect(objects).to.eql([
'Advanced Settings [6.0.0]',
`Advanced Settings [${version}]`,
'A Dashboard',
'logstash-*',
'A Pie',
]);
});
it('cannot view any saved objects in applications', async () => {
const bools = await PageObjects.settings.getSavedObjectsTableSummary();
expect(bools).to.eql([
{
title: 'Advanced Settings [6.0.0]',
canViewInApp: false,
},
{
title: `Advanced Settings [${version}]`,
canViewInApp: false,
},
{
title: 'A Dashboard',
canViewInApp: false,
},
{
title: 'logstash-*',
canViewInApp: false,
},
{
title: 'A Pie',
canViewInApp: false,
},
]);
});
it(`can't delete all saved objects`, async () => {
await PageObjects.settings.clickSavedObjectsTableSelectAll();
const actual = await PageObjects.settings.canSavedObjectsBeDeleted();
expect(actual).to.be(false);
});
});
describe('edit visualization', () => {
before(async () => {
await PageObjects.common.navigateToActualUrl(
'kibana',
'/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed',
{
loginIfPrompted: false,
}
);
await testSubjects.existOrFail('savedObjectsEdit');
});
it('does not show delete button', async () => {
await testSubjects.missingOrFail('savedObjectEditDelete');
});
it('does not show save button', async () => {
await testSubjects.missingOrFail('savedObjectEditSave');
});
it('has inputs with only readonly attributes', async () => {
const form = await testSubjects.find('savedObjectEditForm');
const inputs = await form.findAllByCssSelector('input');
expect(inputs.length).to.be.greaterThan(0);
for (const input of inputs) {
const isEnabled = await input.isEnabled();
expect(isEnabled).to.be(false);
}
});
});
});
describe('global visualize all privileges', () => {
before(async () => {
await security.role.create('global_visualize_all_role', {
elasticsearch: {
@ -150,7 +269,7 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa
kibana: [
{
feature: {
visualize: ['read'],
visualize: ['all'],
},
spaces: ['*'],
},
@ -183,78 +302,26 @@ export default function({ getPageObjects, getService }: KibanaFunctionalTestDefa
});
describe('listing', () => {
before(async () => {
await PageObjects.settings.navigateTo();
await PageObjects.settings.clickKibanaSavedObjects();
});
it('shows two configs, a visualization and an index pattern', async () => {
const objects = await PageObjects.settings.getSavedObjectsInTable();
expect(objects).to.eql([
'Advanced Settings [6.0.0]',
`Advanced Settings [${version}]`,
'logstash-*',
'A Pie',
]);
});
it('can view only two configs and the visualization in application', async () => {
const bools = await PageObjects.settings.getSavedObjectsTableSummary();
expect(bools).to.eql([
{
title: 'Advanced Settings [6.0.0]',
canViewInApp: false,
},
{
title: `Advanced Settings [${version}]`,
canViewInApp: false,
},
{
title: 'logstash-*',
canViewInApp: false,
},
{
title: 'A Pie',
canViewInApp: true,
},
]);
});
it(`can't delete all saved objects`, async () => {
await PageObjects.settings.clickSavedObjectsTableSelectAll();
const actual = await PageObjects.settings.canSavedObjectsBeDeleted();
expect(actual).to.be(false);
it('redirects to Kibana home', async () => {
await PageObjects.common.navigateToActualUrl('kibana', 'management/kibana/objects', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
await testSubjects.existOrFail('homeApp');
});
});
describe('edit visualization', () => {
before(async () => {
it('redirects to Kibana home', async () => {
await PageObjects.common.navigateToActualUrl(
'kibana',
'/management/kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed',
{
loginIfPrompted: false,
ensureCurrentUrl: false,
}
);
await testSubjects.existOrFail('savedObjectsEdit');
});
it('shows delete button', async () => {
await testSubjects.missingOrFail('savedObjectEditDelete');
});
it('shows save button', async () => {
await testSubjects.missingOrFail('savedObjectEditSave');
});
it('has inputs without readonly attributes', async () => {
const form = await testSubjects.find('savedObjectEditForm');
const inputs = await form.findAllByCssSelector('input');
expect(inputs.length).to.be.greaterThan(0);
for (const input of inputs) {
const isEnabled = await input.isEnabled();
expect(isEnabled).to.be(false);
}
await testSubjects.existOrFail('homeApp');
});
});
});

View file

@ -7,7 +7,7 @@
import { KibanaFunctionalTestDefaultProviders } from '../../../types/providers';
// eslint-disable-next-line import/no-default-export
export default function advancedSettingsApp({
export default function savedObjectsManagementApp({
loadTestFile,
}: KibanaFunctionalTestDefaultProviders) {
describe('Saved objects management', function savedObjectsManagementAppTestSuite() {

View file

@ -8,6 +8,7 @@
class SavedObjectsTypeUICapabilitiesGroup {
public all = ['delete', 'edit', 'read'];
public read = ['read'];
public none = [] as string[];
}
const savedObjectsTypeUICapabilitiesGroup = new SavedObjectsTypeUICapabilitiesGroup();

View file

@ -32,65 +32,42 @@ export default function savedObjectsManagementTests({
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
switch (scenario.id) {
case 'superuser at everything_space':
case 'superuser at nothing_space':
case 'global_all at everything_space':
case 'dual_privileges_all at everything_space':
case 'everything_space_all at everything_space':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
const expected = mapValues(uiCapabilities.value!.savedObjectsManagement, () =>
savedObjectsManagementBuilder.uiCapabilities('all')
);
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(expected);
break;
case 'global_all at everything_space':
case 'dual_privileges_all at everything_space':
case 'everything_space_all at everything_space':
case 'global_all at nothing_space':
case 'dual_privileges_all at nothing_space':
case 'nothing_space_all at nothing_space':
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(
savedObjectsManagementBuilder.build({
all: [
'config',
'graph-workspace',
'map',
'canvas-workpad',
'canvas-element',
'index-pattern',
'visualization',
'search',
'dashboard',
'telemetry',
'timelion-sheet',
'url',
'infrastructure-ui-source',
],
})
);
break;
case 'dual_privileges_read at everything_space':
case 'global_read at everything_space':
case 'everything_space_read at everything_space':
case 'dual_privileges_read at nothing_space':
case 'global_read at nothing_space':
case 'nothing_space_read at nothing_space':
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(
savedObjectsManagementBuilder.build({
read: [
'config',
'graph-workspace',
'map',
'canvas-workpad',
'canvas-element',
'index-pattern',
'visualization',
'search',
'dashboard',
'timelion-sheet',
'url',
'infrastructure-ui-source',
],
})
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
const readExpected = mapValues(uiCapabilities.value!.savedObjectsManagement, () =>
savedObjectsManagementBuilder.uiCapabilities('read')
);
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(readExpected);
break;
case 'superuser at nothing_space':
case 'nothing_space_all at nothing_space':
case 'nothing_space_read at nothing_space':
case 'global_all at nothing_space':
case 'global_read at nothing_space':
case 'dual_privileges_all at nothing_space':
case 'dual_privileges_read at nothing_space':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
const noneExpected = mapValues(uiCapabilities.value!.savedObjectsManagement, () =>
savedObjectsManagementBuilder.uiCapabilities('none')
);
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(noneExpected);
break;
// if we don't have access at the space itself, all ui
// capabilities should be false
case 'no_kibana_privileges at everything_space':
case 'no_kibana_privileges at nothing_space':
case 'legacy_all at everything_space':
@ -101,6 +78,7 @@ export default function savedObjectsManagementTests({
case 'nothing_space_read at everything_space':
assertDeeplyFalse(uiCapabilities.value!.savedObjectsManagement);
break;
default:
throw new UnreachableError(scenario);
}

View file

@ -33,66 +33,30 @@ export default function savedObjectsManagementTests({
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
switch (scenario.username) {
case 'superuser':
case 'all':
case 'dual_privileges_all':
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
const expected = mapValues(uiCapabilities.value!.savedObjectsManagement, () =>
savedObjectsManagementBuilder.uiCapabilities('all')
);
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(expected);
break;
case 'all':
case 'dual_privileges_all':
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(
savedObjectsManagementBuilder.build({
all: [
'config',
'graph-workspace',
'map',
'canvas-workpad',
'canvas-element',
'index-pattern',
'visualization',
'search',
'dashboard',
'telemetry',
'timelion-sheet',
'url',
'infrastructure-ui-source',
],
})
);
break;
case 'read':
case 'dual_privileges_read':
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(
savedObjectsManagementBuilder.build({
read: [
'config',
'graph-workspace',
'map',
'canvas-workpad',
'canvas-element',
'index-pattern',
'visualization',
'search',
'dashboard',
'timelion-sheet',
'url',
'infrastructure-ui-source',
],
})
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
const expectedRead = mapValues(uiCapabilities.value!.savedObjectsManagement, () =>
savedObjectsManagementBuilder.uiCapabilities('read')
);
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(expectedRead);
break;
case 'foo_all':
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(
savedObjectsManagementBuilder.build({
all: ['foo', 'telemetry'],
read: ['index-pattern', 'config'],
})
);
break;
case 'foo_read':
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(
savedObjectsManagementBuilder.build({
read: ['foo', 'index-pattern', 'config'],
all: [],
read: [],
})
);
break;

View file

@ -22,14 +22,32 @@ export default function savedObjectsManagementTests({
describe('savedObjectsManagement', () => {
SpaceScenarios.forEach(scenario => {
it(`${scenario.name}`, async () => {
// spaces don't affect saved objects management, so we assert the same thing for every scenario
const uiCapabilities = await uiCapabilitiesService.get({ spaceId: scenario.id });
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
const expected = mapValues(uiCapabilities.value!.savedObjectsManagement, () =>
savedObjectsManagementBuilder.uiCapabilities('all')
);
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(expected);
switch (scenario.id) {
case 'nothing_space':
// Saved Objects Managment is not available when everything is disabled.
const nothingSpaceCapabilities = await uiCapabilitiesService.get({
spaceId: scenario.id,
});
expect(nothingSpaceCapabilities.success).to.be(true);
expect(nothingSpaceCapabilities.value).to.have.property('savedObjectsManagement');
const nothingSpaceExpected = mapValues(
nothingSpaceCapabilities.value!.savedObjectsManagement,
() => savedObjectsManagementBuilder.uiCapabilities('none')
);
expect(nothingSpaceCapabilities.value!.savedObjectsManagement).to.eql(
nothingSpaceExpected
);
break;
default:
// Otherwise it's available without restriction
const uiCapabilities = await uiCapabilitiesService.get({ spaceId: scenario.id });
expect(uiCapabilities.success).to.be(true);
expect(uiCapabilities.value).to.have.property('savedObjectsManagement');
const expected = mapValues(uiCapabilities.value!.savedObjectsManagement, () =>
savedObjectsManagementBuilder.uiCapabilities('all')
);
expect(uiCapabilities.value!.savedObjectsManagement).to.eql(expected);
}
});
});
});