Skip to content

Commit

Permalink
Fix alerts unable to create / update when the name has trailing white…
Browse files Browse the repository at this point in the history
…pace(s) (#76079) (#76170)

* Trim alert name in API key name

* Add API integration tests
  • Loading branch information
mikecote committed Aug 27, 2020
1 parent 12579fb commit 57d8509
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 3 deletions.
128 changes: 127 additions & 1 deletion x-pack/plugins/alerts/server/alerts_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,70 @@ describe('create()', () => {
expect(taskManager.schedule).toHaveBeenCalledTimes(0);
});

test('should trim alert name when creating API key', async () => {
const data = getMockData({ name: ' my alert name ' });
unsecuredSavedObjectsClient.bulkGet.mockResolvedValueOnce({
saved_objects: [
{
id: '1',
type: 'action',
attributes: {
actions: [],
actionTypeId: 'test',
},
references: [],
},
],
});
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
enabled: false,
name: ' my alert name ',
alertTypeId: '123',
schedule: { interval: 10000 },
params: {
bar: true,
},
createdAt: new Date().toISOString(),
actions: [
{
group: 'default',
actionRef: 'action_0',
actionTypeId: 'test',
params: {
foo: true,
},
},
],
},
references: [
{
name: 'action_0',
type: 'action',
id: '1',
},
],
});
taskManager.schedule.mockResolvedValueOnce({
id: 'task-123',
taskType: 'alerting:123',
scheduledAt: new Date(),
attempts: 1,
status: TaskStatus.Idle,
runAt: new Date(),
startedAt: null,
retryAt: null,
state: {},
params: {},
ownerId: null,
});

await alertsClient.create({ data });
expect(alertsClientParams.createAPIKey).toHaveBeenCalledWith('Alerting: 123/my alert name');
});

test('should validate params', async () => {
const data = getMockData();
alertTypeRegistry.get.mockReturnValue({
Expand Down Expand Up @@ -2896,9 +2960,13 @@ describe('update()', () => {
type: 'alert',
attributes: {
enabled: true,
tags: ['foo'],
alertTypeId: 'myType',
schedule: { interval: '10s' },
consumer: 'myApp',
scheduledTaskId: 'task-123',
params: {},
throttle: null,
actions: [
{
group: 'default',
Expand Down Expand Up @@ -2927,7 +2995,7 @@ describe('update()', () => {
unsecuredSavedObjectsClient.get.mockResolvedValue(existingAlert);
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue(existingDecryptedAlert);
alertTypeRegistry.get.mockReturnValue({
id: '123',
id: 'myType',
name: 'Test',
actionGroups: [{ id: 'default', name: 'Default' }],
defaultActionGroupId: 'default',
Expand Down Expand Up @@ -3489,6 +3557,64 @@ describe('update()', () => {
);
});

it('should trim alert name in the API key name', async () => {
unsecuredSavedObjectsClient.bulkGet.mockResolvedValueOnce({
saved_objects: [
{
id: '1',
type: 'action',
attributes: {
actions: [],
actionTypeId: 'test',
},
references: [],
},
],
});
unsecuredSavedObjectsClient.update.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
enabled: false,
name: ' my alert name ',
schedule: { interval: '10s' },
params: {
bar: true,
},
createdAt: new Date().toISOString(),
actions: [
{
group: 'default',
actionRef: 'action_0',
actionTypeId: 'test',
params: {
foo: true,
},
},
],
scheduledTaskId: 'task-123',
apiKey: null,
},
updated_at: new Date().toISOString(),
references: [
{
name: 'action_0',
type: 'action',
id: '1',
},
],
});
await alertsClient.update({
id: '1',
data: {
...existingAlert.attributes,
name: ' my alert name ',
},
});

expect(alertsClientParams.createAPIKey).toHaveBeenCalledWith('Alerting: myType/my alert name');
});

it('swallows error when invalidate API key throws', async () => {
alertsClientParams.invalidateAPIKey.mockRejectedValueOnce(new Error('Fail'));
unsecuredSavedObjectsClient.bulkGet.mockResolvedValueOnce({
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/alerts/server/alerts_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import Boom from 'boom';
import { omit, isEqual, map, uniq, pick, truncate } from 'lodash';
import { omit, isEqual, map, uniq, pick, truncate, trim } from 'lodash';
import { i18n } from '@kbn/i18n';
import {
Logger,
Expand Down Expand Up @@ -940,7 +940,7 @@ export class AlertsClient {
}

private generateAPIKeyName(alertTypeId: string, alertName: string) {
return truncate(`Alerting: ${alertTypeId}/${alertName}`, { length: 256 });
return truncate(`Alerting: ${alertTypeId}/${trim(alertName)}`, { length: 256 });
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,45 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
}
});

it('should handle create alert request appropriately when alert name has leading and trailing whitespaces', async () => {
const response = await supertestWithoutAuth
.post(`${getUrlPrefix(space.id)}/api/alerts/alert`)
.set('kbn-xsrf', 'foo')
.auth(user.username, user.password)
.send(
getTestAlertData({
name: ' leading and trailing whitespace ',
})
);

switch (scenario.id) {
case 'no_kibana_privileges at space1':
case 'global_read at space1':
case 'space_1_all at space2':
expect(response.statusCode).to.eql(403);
expect(response.body).to.eql({
error: 'Forbidden',
message: getConsumerUnauthorizedErrorMessage(
'create',
'test.noop',
'alertsFixture'
),
statusCode: 403,
});
break;
case 'superuser at space1':
case 'space_1_all at space1':
case 'space_1_all_alerts_none_actions at space1':
case 'space_1_all_with_restricted_fixture at space1':
expect(response.statusCode).to.eql(200);
expect(response.body.name).to.eql(' leading and trailing whitespace ');
objectRemover.add(space.id, response.body.id, 'alert', 'alerts');
break;
default:
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
}
});

it('should handle create alert request appropriately when alert type is unregistered', async () => {
const response = await supertestWithoutAuth
.post(`${getUrlPrefix(space.id)}/api/alerts/alert`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,57 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
}
});

it('should handle update alert request appropriately when alert name has leading and trailing whitespaces', async () => {
const { body: createdAlert } = await supertest
.post(`${getUrlPrefix(space.id)}/api/alerts/alert`)
.set('kbn-xsrf', 'foo')
.send(getTestAlertData())
.expect(200);
objectRemover.add(space.id, createdAlert.id, 'alert', 'alerts');

const updatedData = {
name: ' leading and trailing whitespace ',
tags: ['bar'],
params: {
foo: true,
},
schedule: { interval: '12s' },
actions: [],
throttle: '1m',
};
const response = await supertestWithoutAuth
.put(`${getUrlPrefix(space.id)}/api/alerts/alert/${createdAlert.id}`)
.set('kbn-xsrf', 'foo')
.auth(user.username, user.password)
.send(updatedData);

switch (scenario.id) {
case 'no_kibana_privileges at space1':
case 'space_1_all at space2':
case 'global_read at space1':
expect(response.statusCode).to.eql(403);
expect(response.body).to.eql({
error: 'Forbidden',
message: getConsumerUnauthorizedErrorMessage(
'update',
'test.noop',
'alertsFixture'
),
statusCode: 403,
});
break;
case 'superuser at space1':
case 'space_1_all at space1':
case 'space_1_all_alerts_none_actions at space1':
case 'space_1_all_with_restricted_fixture at space1':
expect(response.statusCode).to.eql(200);
expect(response.body.name).to.eql(' leading and trailing whitespace ');
break;
default:
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
}
});

it(`shouldn't update alert from another space`, async () => {
const { body: createdAlert } = await supertest
.post(`${getUrlPrefix(space.id)}/api/alerts/alert`)
Expand Down

0 comments on commit 57d8509

Please sign in to comment.