Skip to content

Commit 6d1c010

Browse files
authored
[Alerting] Fixing broken Alerts view when no Global All Kibana privilege (#88727)
* Making kibanaFeatures an optional parameter and catching error on plugin start * Gracefully handle 404 errors when no access to features endpoint * Adding functional test
1 parent e3063f6 commit 6d1c010

File tree

3 files changed

+100
-55
lines changed

3 files changed

+100
-55
lines changed

x-pack/plugins/triggers_actions_ui/public/plugin.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { CoreSetup, CoreStart, Plugin as CorePlugin } from 'src/core/public';
99
import { i18n } from '@kbn/i18n';
1010
import { ReactElement } from 'react';
1111
import { FeaturesPluginStart } from '../../features/public';
12+
import { KibanaFeature } from '../../features/common';
1213
import { registerBuiltInActionTypes } from './application/components/builtin_action_types';
1314
import { ActionTypeModel, AlertTypeModel } from './types';
1415
import { TypeRegistry } from './application/type_registry';
@@ -122,7 +123,17 @@ export class Plugin
122123
];
123124

124125
const { renderApp } = await import('./application/app');
125-
const kibanaFeatures = await pluginsStart.features.getFeatures();
126+
127+
// The `/api/features` endpoint requires the "Global All" Kibana privilege. Users with a
128+
// subset of this privilege are not authorized to access this endpoint and will receive a 404
129+
// error that causes the Alerting view to fail to load.
130+
let kibanaFeatures: KibanaFeature[];
131+
try {
132+
kibanaFeatures = await pluginsStart.features.getFeatures();
133+
} catch (err) {
134+
kibanaFeatures = [];
135+
}
136+
126137
return renderApp({
127138
...coreStart,
128139
data: pluginsStart.data,

x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts

Lines changed: 72 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -11,87 +11,105 @@ import { getTestAlertData, getTestActionData } from '../../lib/get_test_data';
1111

1212
export default ({ getPageObjects, getService }: FtrProviderContext) => {
1313
const testSubjects = getService('testSubjects');
14+
const security = getService('security');
1415
const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']);
1516
const log = getService('log');
1617
const browser = getService('browser');
1718
const supertest = getService('supertest');
1819
const objectRemover = new ObjectRemover(supertest);
1920

2021
describe('Home page', function () {
21-
before(async () => {
22-
await pageObjects.common.navigateToApp('triggersActions');
23-
});
22+
describe('Loads the app with limited privileges', () => {
23+
before(async () => {
24+
await security.testUser.setRoles(['alerts_and_actions_role'], true);
25+
});
26+
after(async () => {
27+
await security.testUser.restoreDefaults();
28+
});
2429

25-
after(async () => {
26-
await objectRemover.removeAll();
30+
it('Loads the Alerts page', async () => {
31+
await pageObjects.common.navigateToApp('triggersActions');
32+
const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText();
33+
expect(headingText).to.be('Alerts and Actions');
34+
});
2735
});
2836

29-
it('Loads the app', async () => {
30-
await log.debug('Checking for section heading to say Triggers and Actions.');
37+
describe('Loads the app', () => {
38+
before(async () => {
39+
await pageObjects.common.navigateToApp('triggersActions');
40+
});
3141

32-
const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText();
33-
expect(headingText).to.be('Alerts and Actions');
34-
});
42+
after(async () => {
43+
await objectRemover.removeAll();
44+
});
45+
46+
it('Loads the Alerts page', async () => {
47+
await log.debug('Checking for section heading to say Triggers and Actions.');
48+
49+
const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText();
50+
expect(headingText).to.be('Alerts and Actions');
51+
});
3552

36-
describe('Connectors tab', () => {
37-
it('renders the connectors tab', async () => {
38-
// Navigate to the connectors tab
39-
await pageObjects.triggersActionsUI.changeTabs('connectorsTab');
53+
describe('Connectors tab', () => {
54+
it('renders the connectors tab', async () => {
55+
// Navigate to the connectors tab
56+
await pageObjects.triggersActionsUI.changeTabs('connectorsTab');
4057

41-
await pageObjects.header.waitUntilLoadingHasFinished();
58+
await pageObjects.header.waitUntilLoadingHasFinished();
4259

43-
// Verify url
44-
const url = await browser.getCurrentUrl();
45-
expect(url).to.contain(`/connectors`);
60+
// Verify url
61+
const url = await browser.getCurrentUrl();
62+
expect(url).to.contain(`/connectors`);
4663

47-
// Verify content
48-
await testSubjects.existOrFail('actionsList');
64+
// Verify content
65+
await testSubjects.existOrFail('actionsList');
66+
});
4967
});
50-
});
5168

52-
describe('Alerts tab', () => {
53-
it('renders the alerts tab', async () => {
54-
// Navigate to the alerts tab
55-
await pageObjects.triggersActionsUI.changeTabs('alertsTab');
69+
describe('Alerts tab', () => {
70+
it('renders the alerts tab', async () => {
71+
// Navigate to the alerts tab
72+
await pageObjects.triggersActionsUI.changeTabs('alertsTab');
5673

57-
await pageObjects.header.waitUntilLoadingHasFinished();
74+
await pageObjects.header.waitUntilLoadingHasFinished();
5875

59-
// Verify url
60-
const url = await browser.getCurrentUrl();
61-
expect(url).to.contain(`/alerts`);
76+
// Verify url
77+
const url = await browser.getCurrentUrl();
78+
expect(url).to.contain(`/alerts`);
6279

63-
// Verify content
64-
await testSubjects.existOrFail('alertsList');
65-
});
80+
// Verify content
81+
await testSubjects.existOrFail('alertsList');
82+
});
6683

67-
it('navigates to an alert details page', async () => {
68-
const { body: createdAction } = await supertest
69-
.post(`/api/actions/action`)
70-
.set('kbn-xsrf', 'foo')
71-
.send(getTestActionData())
72-
.expect(200);
73-
objectRemover.add(createdAction.id, 'action', 'actions');
84+
it('navigates to an alert details page', async () => {
85+
const { body: createdAction } = await supertest
86+
.post(`/api/actions/action`)
87+
.set('kbn-xsrf', 'foo')
88+
.send(getTestActionData())
89+
.expect(200);
90+
objectRemover.add(createdAction.id, 'action', 'actions');
7491

75-
const { body: createdAlert } = await supertest
76-
.post(`/api/alerts/alert`)
77-
.set('kbn-xsrf', 'foo')
78-
.send(getTestAlertData())
79-
.expect(200);
80-
objectRemover.add(createdAlert.id, 'alert', 'alerts');
92+
const { body: createdAlert } = await supertest
93+
.post(`/api/alerts/alert`)
94+
.set('kbn-xsrf', 'foo')
95+
.send(getTestAlertData())
96+
.expect(200);
97+
objectRemover.add(createdAlert.id, 'alert', 'alerts');
8198

82-
// refresh to see alert
83-
await browser.refresh();
99+
// refresh to see alert
100+
await browser.refresh();
84101

85-
await pageObjects.header.waitUntilLoadingHasFinished();
102+
await pageObjects.header.waitUntilLoadingHasFinished();
86103

87-
// Verify content
88-
await testSubjects.existOrFail('alertsList');
104+
// Verify content
105+
await testSubjects.existOrFail('alertsList');
89106

90-
// click on first alert
91-
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(createdAlert.name);
107+
// click on first alert
108+
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(createdAlert.name);
92109

93-
// Verify url
94-
expect(await browser.getCurrentUrl()).to.contain(`/alert/${createdAlert.id}`);
110+
// Verify url
111+
expect(await browser.getCurrentUrl()).to.contain(`/alert/${createdAlert.id}`);
112+
});
95113
});
96114
});
97115
});

x-pack/test/functional_with_es_ssl/config.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,22 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
8686
})}`,
8787
],
8888
},
89+
security: {
90+
roles: {
91+
alerts_and_actions_role: {
92+
kibana: [
93+
{
94+
feature: {
95+
actions: ['all'],
96+
stackAlerts: ['all'],
97+
},
98+
spaces: ['*'],
99+
},
100+
],
101+
},
102+
},
103+
defaultRoles: ['superuser'],
104+
},
89105
};
90106

91107
return returnedObject;

0 commit comments

Comments
 (0)