Consolidate capabilities check for Stack Management (#69437)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Larry Gregory 2020-06-29 11:43:43 -04:00 committed by GitHub
parent fe1c508d8d
commit 28b70923df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 119 additions and 136 deletions

View file

@ -17,8 +17,8 @@
* under the License.
*/
import { i18n } from '@kbn/i18n';
import { CoreSetup, CoreStart, Plugin } from 'kibana/public';
import { ManagementApp, ManagementSectionId } from '../../management/public';
import { CoreSetup, Plugin } from 'kibana/public';
import { ManagementSectionId } from '../../management/public';
import { ComponentRegistry } from './component_registry';
import { AdvancedSettingsSetup, AdvancedSettingsStart, AdvancedSettingsPluginSetup } from './types';
@ -30,11 +30,10 @@ const title = i18n.translate('advancedSettings.advancedSettingsLabel', {
export class AdvancedSettingsPlugin
implements Plugin<AdvancedSettingsSetup, AdvancedSettingsStart, AdvancedSettingsPluginSetup> {
private managementApp?: ManagementApp;
public setup(core: CoreSetup, { management }: AdvancedSettingsPluginSetup) {
const kibanaSection = management.sections.getSection(ManagementSectionId.Kibana);
this.managementApp = kibanaSection.registerApp({
kibanaSection.registerApp({
id: 'settings',
title,
order: 3,
@ -51,11 +50,7 @@ export class AdvancedSettingsPlugin
};
}
public start(core: CoreStart) {
if (!core.application.capabilities.management.kibana.settings) {
this.managementApp!.disable();
}
public start() {
return {
component: component.start,
};

View file

@ -30,7 +30,7 @@ export const onRedirectNoIndexPattern = (
navigateToApp: CoreStart['application']['navigateToApp'],
overlays: CoreStart['overlays']
) => () => {
const canManageIndexPatterns = capabilities.management.kibana.index_patterns;
const canManageIndexPatterns = capabilities.management.kibana.indexPatterns;
const redirectTarget = canManageIndexPatterns ? '/management/kibana/indexPatterns' : '/home';
let timeoutId: NodeJS.Timeout | undefined;

View file

@ -37,7 +37,7 @@ export const indexPatternSavedObjectType: SavedObjectsType = {
getInAppUrl(obj) {
return {
path: `/app/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`,
uiCapabilitiesPath: 'management.kibana.index_patterns',
uiCapabilitiesPath: 'management.kibana.indexPatterns',
};
},
},

View file

@ -24,6 +24,6 @@ export const capabilitiesProvider = () => ({
visualize: true,
console: true,
advanced_settings: true,
index_patterns: true,
indexPatterns: true,
},
});

View file

@ -27,7 +27,7 @@ import {
IndexPatternManagementServiceStart,
} from './service';
import { ManagementSetup, ManagementApp, ManagementSectionId } from '../../management/public';
import { ManagementSetup, ManagementSectionId } from '../../management/public';
export interface IndexPatternManagementSetupDependencies {
management: ManagementSetup;
@ -57,7 +57,6 @@ export class IndexPatternManagementPlugin
IndexPatternManagementStartDependencies
> {
private readonly indexPatternManagementService = new IndexPatternManagementService();
private managementApp?: ManagementApp;
constructor(initializerContext: PluginInitializerContext) {}
@ -80,7 +79,7 @@ export class IndexPatternManagementPlugin
return pathInApp && `/patterns${pathInApp}`;
});
this.managementApp = kibanaSection.registerApp({
kibanaSection.registerApp({
id: IPM_APP_ID,
title: sectionsHeader,
order: 0,
@ -95,10 +94,6 @@ export class IndexPatternManagementPlugin
}
public start(core: CoreStart, plugins: IndexPatternManagementStartDependencies) {
if (!core.application.capabilities.management.kibana.index_patterns) {
this.managementApp!.disable();
}
return this.indexPatternManagementService.start();
}

View file

@ -27,9 +27,15 @@ describe('ManagementService', () => {
managementService = new ManagementSectionsService();
});
const capabilities = {
navLinks: {},
catalogue: {},
management: {},
};
test('Provides default sections', () => {
managementService.setup();
const start = managementService.start();
const start = managementService.start({ capabilities });
expect(start.getAllSections().length).toEqual(6);
expect(start.getSection(ManagementSectionId.Ingest)).toBeDefined();
@ -48,7 +54,7 @@ describe('ManagementService', () => {
expect(setup.getSection('test-section')).not.toBeUndefined();
// Start phase:
const start = managementService.start();
const start = managementService.start({ capabilities });
expect(start.getSectionsEnabled().length).toEqual(7);
@ -56,4 +62,38 @@ describe('ManagementService', () => {
expect(start.getSectionsEnabled().length).toEqual(6);
});
test('Disables items that are not allowed by Capabilities', () => {
// Setup phase:
const setup = managementService.setup();
const testSection = setup.register({ id: 'test-section', title: 'Test Section' });
testSection.registerApp({ id: 'test-app-1', title: 'Test App 1', mount: jest.fn() });
testSection.registerApp({ id: 'test-app-2', title: 'Test App 2', mount: jest.fn() });
testSection.registerApp({ id: 'test-app-3', title: 'Test App 3', mount: jest.fn() });
expect(setup.getSection('test-section')).not.toBeUndefined();
// Start phase:
managementService.start({
capabilities: {
navLinks: {},
catalogue: {},
management: {
['test-section']: {
'test-app-1': true,
'test-app-2': false,
// test-app-3 intentionally left undefined. Should be enabled by default
},
},
},
});
expect(testSection.apps).toHaveLength(3);
expect(testSection.getAppsEnabled().map((app) => app.id)).toMatchInlineSnapshot(`
Array [
"test-app-1",
"test-app-3",
]
`);
});
});

View file

@ -21,7 +21,12 @@ import { ReactElement } from 'react';
import { ManagementSection, RegisterManagementSectionArgs } from './utils';
import { managementSections } from './components/management_sections';
import { ManagementSectionId, SectionsServiceSetup, SectionsServiceStart } from './types';
import {
ManagementSectionId,
SectionsServiceSetup,
SectionsServiceStart,
SectionsServiceStartDeps,
} from './types';
export class ManagementSectionsService {
private sections: Map<ManagementSectionId | string, ManagementSection> = new Map();
@ -55,7 +60,18 @@ export class ManagementSectionsService {
};
}
start(): SectionsServiceStart {
start({ capabilities }: SectionsServiceStartDeps): SectionsServiceStart {
this.getAllSections().forEach((section) => {
if (capabilities.management.hasOwnProperty(section.id)) {
const sectionCapabilities = capabilities.management[section.id];
section.apps.forEach((app) => {
if (sectionCapabilities.hasOwnProperty(app.id) && sectionCapabilities[app.id] !== true) {
app.disable();
}
});
}
});
return {
getSection: this.getSection,
getAllSections: this.getAllSections,

View file

@ -82,7 +82,7 @@ export class ManagementPlugin implements Plugin<ManagementSetup, ManagementStart
public start(core: CoreStart) {
return {
sections: this.managementSections.start(),
sections: this.managementSections.start({ capabilities: core.application.capabilities }),
};
}
}

View file

@ -18,7 +18,7 @@
*/
import { ReactElement } from 'react';
import { ScopedHistory } from 'kibana/public';
import { ScopedHistory, Capabilities } from 'kibana/public';
import { ManagementSection, RegisterManagementSectionArgs } from './utils';
import { ChromeBreadcrumb } from '../../../core/public/';
@ -30,8 +30,12 @@ export interface ManagementStart {
sections: SectionsServiceStart;
}
export interface SectionsServiceStartDeps {
capabilities: Capabilities;
}
export interface SectionsServiceSetup {
register: (args: RegisterManagementSectionArgs) => ManagementSection;
register: (args: Omit<RegisterManagementSectionArgs, 'capabilities'>) => ManagementSection;
getSection: (sectionId: ManagementSectionId | string) => ManagementSection;
}

View file

@ -25,7 +25,7 @@ export const capabilitiesProvider = () => ({
*/
kibana: {
settings: true,
index_patterns: true,
indexPatterns: true,
objects: true,
},
},

View file

@ -70,7 +70,7 @@ describe('canViewInApp', () => {
let uiCapabilities = createCapabilities({
management: {
kibana: {
index_patterns: true,
indexPatterns: true,
},
},
});
@ -81,7 +81,7 @@ describe('canViewInApp', () => {
uiCapabilities = createCapabilities({
management: {
kibana: {
index_patterns: false,
indexPatterns: false,
},
},
});

View file

@ -30,7 +30,7 @@ export function canViewInApp(uiCapabilities: Capabilities, type: string): boolea
case 'index-pattern':
case 'index-patterns':
case 'indexPatterns':
return uiCapabilities.management.kibana.index_patterns as boolean;
return uiCapabilities.management.kibana.indexPatterns as boolean;
case 'dashboard':
case 'dashboards':
return uiCapabilities.dashboard.show as boolean;

View file

@ -304,7 +304,7 @@ exports[`SavedObjectsTable should render normally 1`] = `
"icon": "indexPatternApp",
"inAppUrl": Object {
"path": "/management/kibana/indexPatterns/patterns/1",
"uiCapabilitiesPath": "management.kibana.index_patterns",
"uiCapabilitiesPath": "management.kibana.indexPatterns",
},
"title": "MyIndexPattern*",
},

View file

@ -456,7 +456,7 @@ exports[`Relationships should render searches normally 1`] = `
"icon": "indexPatternApp",
"inAppUrl": Object {
"path": "/app/management/kibana/indexPatterns/patterns/1",
"uiCapabilitiesPath": "management.kibana.index_patterns",
"uiCapabilitiesPath": "management.kibana.indexPatterns",
},
"title": "My Index Pattern",
},

View file

@ -178,7 +178,7 @@ exports[`Table prevents saved objects from being deleted 1`] = `
"icon": "indexPatternApp",
"inAppUrl": Object {
"path": "/management/kibana/indexPatterns/patterns/1",
"uiCapabilitiesPath": "management.kibana.index_patterns",
"uiCapabilitiesPath": "management.kibana.indexPatterns",
},
"title": "MyIndexPattern*",
},
@ -393,7 +393,7 @@ exports[`Table should render normally 1`] = `
"icon": "indexPatternApp",
"inAppUrl": Object {
"path": "/management/kibana/indexPatterns/patterns/1",
"uiCapabilitiesPath": "management.kibana.index_patterns",
"uiCapabilitiesPath": "management.kibana.indexPatterns",
},
"title": "MyIndexPattern*",
},

View file

@ -77,7 +77,7 @@ describe('Relationships', () => {
editUrl: '#/management/kibana/indexPatterns/patterns/1',
inAppUrl: {
path: '/management/kibana/indexPatterns/patterns/1',
uiCapabilitiesPath: 'management.kibana.index_patterns',
uiCapabilitiesPath: 'management.kibana.indexPatterns',
},
},
},
@ -113,7 +113,7 @@ describe('Relationships', () => {
icon: 'indexPatternApp',
inAppUrl: {
path: '/app/management/kibana/indexPatterns/patterns/1',
uiCapabilitiesPath: 'management.kibana.index_patterns',
uiCapabilitiesPath: 'management.kibana.indexPatterns',
},
title: 'My Index Pattern',
},

View file

@ -41,7 +41,7 @@ const defaultProps: TableProps = {
editUrl: '#/management/kibana/indexPatterns/patterns/1',
inAppUrl: {
path: '/management/kibana/indexPatterns/patterns/1',
uiCapabilitiesPath: 'management.kibana.index_patterns',
uiCapabilitiesPath: 'management.kibana.indexPatterns',
},
},
},
@ -68,7 +68,7 @@ const defaultProps: TableProps = {
editUrl: '#/management/kibana/indexPatterns/patterns/1',
inAppUrl: {
path: '/management/kibana/indexPatterns/patterns/1',
uiCapabilitiesPath: 'management.kibana.index_patterns',
uiCapabilitiesPath: 'management.kibana.indexPatterns',
},
},
},

View file

@ -158,7 +158,7 @@ describe('SavedObjectsTable', () => {
editUrl: '#/management/kibana/indexPatterns/patterns/1',
inAppUrl: {
path: '/management/kibana/indexPatterns/patterns/1',
uiCapabilitiesPath: 'management.kibana.index_patterns',
uiCapabilitiesPath: 'management.kibana.indexPatterns',
},
},
},

View file

@ -287,7 +287,7 @@ export default function ({ getService }: FtrProviderContext) {
inAppUrl: {
path:
'/app/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'management.kibana.index_patterns',
uiCapabilitiesPath: 'management.kibana.indexPatterns',
},
});
}));

View file

@ -87,7 +87,7 @@ export default function ({ getService }: FtrProviderContext) {
inAppUrl: {
path:
'/app/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'management.kibana.index_patterns',
uiCapabilitiesPath: 'management.kibana.indexPatterns',
},
},
},
@ -128,7 +128,7 @@ export default function ({ getService }: FtrProviderContext) {
inAppUrl: {
path:
'/app/management/kibana/indexPatterns/patterns/8963ca30-3224-11e8-a572-ffca06da1357',
uiCapabilitiesPath: 'management.kibana.index_patterns',
uiCapabilitiesPath: 'management.kibana.indexPatterns',
},
},
relationship: 'child',

View file

@ -240,11 +240,11 @@ Array [
"kibana",
],
"catalogue": Array [
"index_patterns",
"indexPatterns",
],
"management": Object {
"kibana": Array [
"index_patterns",
"indexPatterns",
],
},
"savedObject": Object {
@ -265,11 +265,11 @@ Array [
"kibana",
],
"catalogue": Array [
"index_patterns",
"indexPatterns",
],
"management": Object {
"kibana": Array [
"index_patterns",
"indexPatterns",
],
},
"savedObject": Object {

View file

@ -294,16 +294,16 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS
order: 1600,
icon: 'indexPatternApp',
app: ['kibana'],
catalogue: ['index_patterns'],
catalogue: ['indexPatterns'],
management: {
kibana: ['index_patterns'],
kibana: ['indexPatterns'],
},
privileges: {
all: {
app: ['kibana'],
catalogue: ['index_patterns'],
catalogue: ['indexPatterns'],
management: {
kibana: ['index_patterns'],
kibana: ['indexPatterns'],
},
savedObject: {
all: ['index-pattern'],
@ -313,9 +313,9 @@ export const buildOSSFeatures = ({ savedObjectTypes, includeTimelion }: BuildOSS
},
read: {
app: ['kibana'],
catalogue: ['index_patterns'],
catalogue: ['indexPatterns'],
management: {
kibana: ['index_patterns'],
kibana: ['indexPatterns'],
},
savedObject: {
all: [],

View file

@ -9,7 +9,6 @@ import { coreMock } from 'src/core/public/mocks';
import { spacesManagerMock } from '../spaces_manager/mocks';
import { managementPluginMock } from '../../../../../src/plugins/management/public/mocks';
import { ManagementSection } from 'src/plugins/management/public';
import { Capabilities } from 'kibana/public';
import { PluginsStart } from '../plugin';
import { CoreSetup } from 'src/core/public';
@ -58,66 +57,6 @@ describe('ManagementService', () => {
});
});
describe('#start', () => {
it('disables the spaces management page if the user is not authorized', () => {
const mockSpacesManagementPage = { disable: jest.fn() };
const mockKibanaSection = ({
registerApp: jest.fn().mockReturnValue(mockSpacesManagementPage),
} as unknown) as ManagementSection;
const deps = {
management: managementPluginMock.createSetupContract(),
getStartServices: coreMock.createSetup().getStartServices as CoreSetup<
PluginsStart
>['getStartServices'],
spacesManager: spacesManagerMock.create(),
};
deps.management.sections.getSection.mockImplementation((id) => {
if (id === 'kibana') return mockKibanaSection;
throw new Error(`unexpected getSection call: ${id}`);
});
const service = new ManagementService();
service.setup(deps);
const capabilities = ({ spaces: { manage: false } } as unknown) as Capabilities;
service.start({ capabilities });
expect(mockKibanaSection.registerApp).toHaveBeenCalledTimes(1);
expect(mockSpacesManagementPage.disable).toHaveBeenCalledTimes(1);
});
it('does not disable the spaces management page if the user is authorized', () => {
const mockSpacesManagementPage = { disable: jest.fn() };
const mockKibanaSection = ({
registerApp: jest.fn().mockReturnValue(mockSpacesManagementPage),
} as unknown) as ManagementSection;
const deps = {
management: managementPluginMock.createSetupContract(),
getStartServices: coreMock.createSetup().getStartServices as CoreSetup<
PluginsStart
>['getStartServices'],
spacesManager: spacesManagerMock.create(),
};
deps.management.sections.getSection.mockImplementation((id) => {
if (id === 'kibana') return mockKibanaSection;
throw new Error(`unexpected getSection call: ${id}`);
});
const service = new ManagementService();
service.setup(deps);
const capabilities = ({ spaces: { manage: true } } as unknown) as Capabilities;
service.start({ capabilities });
expect(mockKibanaSection.registerApp).toHaveBeenCalledTimes(1);
expect(mockSpacesManagementPage.disable).toHaveBeenCalledTimes(0);
});
});
describe('#stop', () => {
it('disables the spaces management page', () => {
const mockSpacesManagementPage = { disable: jest.fn() };

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { StartServicesAccessor, Capabilities } from 'src/core/public';
import { StartServicesAccessor } from 'src/core/public';
import {
ManagementSetup,
ManagementApp,
@ -22,9 +22,6 @@ interface SetupDeps {
securityLicense?: SecurityLicense;
}
interface StartDeps {
capabilities: Capabilities;
}
export class ManagementService {
private registeredSpacesManagementApp?: ManagementApp;
@ -36,12 +33,6 @@ export class ManagementService {
);
}
public start({ capabilities }: StartDeps) {
if (!capabilities.spaces.manage) {
this.disableSpacesApp();
}
}
public stop() {
this.disableSpacesApp();
}

View file

@ -87,10 +87,6 @@ export class SpacesPlugin implements Plugin<SpacesPluginSetup, SpacesPluginStart
public start(core: CoreStart, plugins: PluginsStart) {
initSpacesNavControl(this.spacesManager, core);
if (this.managementService) {
this.managementService.start({ capabilities: core.application.capabilities });
}
return {
activeSpace$: this.spacesManager.onActiveSpaceChange$,
getActiveSpace: () => this.spacesManager.getActiveSpace(),

View file

@ -138,8 +138,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
});
// FLAKY: https://github.com/elastic/kibana/issues/57377
describe.skip('no advanced_settings privileges', function () {
describe('no advanced_settings privileges', function () {
before(async () => {
await security.role.create('no_advanced_settings_privileges_role', {
elasticsearch: {

View file

@ -182,8 +182,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
it(`doesn't show Index Patterns in management side-nav`, async () => {
await PageObjects.settings.navigateTo();
await testSubjects.existOrFail('kibana');
await testSubjects.missingOrFail('index_patterns');
await testSubjects.existOrFail('managementHome', {
timeout: config.get('timeouts.waitFor'),
});
await testSubjects.missingOrFail('indexPatterns');
});
it(`does not allow navigation to Index Patterns; redirects to management home`, async () => {

View file

@ -310,19 +310,25 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
});
describe('listing', () => {
it('redirects to Kibana home', async () => {
it(`doesn't display management section`, async () => {
await PageObjects.settings.navigateTo();
await testSubjects.existOrFail('managementHome'); // this ensures we've gotten to the management page
await testSubjects.missingOrFail('objects');
});
it(`can't navigate to listing page`, async () => {
await PageObjects.common.navigateToUrl('management', 'kibana/objects', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
shouldUseHashForSubUrl: false,
});
await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.existOrFail('homeApp');
await testSubjects.existOrFail('managementHome');
});
});
describe('edit visualization', () => {
it('redirects to Kibana home', async () => {
it('redirects to management home', async () => {
await PageObjects.common.navigateToUrl(
'management',
'kibana/objects/savedVisualizations/75c3e060-1e7c-11e9-8488-65449e65d0ed',
@ -333,7 +339,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
}
);
await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.existOrFail('homeApp');
await testSubjects.existOrFail('managementHome');
});
});
});