diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions_popopver.scss b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions_popopver.scss index b3cb695ecb44c..f776a67fabf89 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions_popopver.scss +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions_popopver.scss @@ -1,3 +1,3 @@ -button[data-test-subj='deleteRuleButton'] { +.ruleActionsPopover__deleteButton { color: $euiColorDangerText; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions_popover.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions_popover.tsx index 5f4457599bcb0..0862bf3bf3c1c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions_popover.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_actions_popover.tsx @@ -84,6 +84,7 @@ export const RuleActionsPopover: React.FunctionComponent { setIsPopoverOpen(false); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.scss b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.scss index bddabb86eb4a8..fe009998ff1c4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.scss +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.scss @@ -1,3 +1,3 @@ -button[data-test-subj='deleteRule'] { +.collapsedItemActions__deleteButton { color: $euiColorDangerText; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.tsx index 8dcc6fe16618b..7e65961ac80f0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/collapsed_item_actions.tsx @@ -148,6 +148,7 @@ export const CollapsedItemActions: React.FunctionComponent = ({ }, { disabled: !item.isEditable, + className: 'collapsedItemActions__deleteButton', 'data-test-subj': 'deleteRule', onClick: () => { setIsPopoverOpen(!isPopoverOpen); diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts index 837ca4adf217b..55fcff63e3d28 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts @@ -13,6 +13,7 @@ import { KibanaResponseFactory, IKibanaResponse, Logger, + SavedObject, } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; import { InvalidatePendingApiKey } from '@kbn/alerting-plugin/server/types'; @@ -364,4 +365,46 @@ export function defineRoutes( } } ); + + router.get( + { + path: '/api/alerting/rule/{id}/_get_api_key', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + const { id } = req.params; + const [, { encryptedSavedObjects, spaces }] = await core.getStartServices(); + + const spaceId = spaces ? spaces.spacesService.getSpaceId(req) : 'default'; + + let namespace: string | undefined; + if (spaces && spaceId) { + namespace = spaces.spacesService.spaceIdToNamespace(spaceId); + } + + try { + const { + attributes: { apiKey, apiKeyOwner }, + }: SavedObject = await encryptedSavedObjects + .getClient({ + includedHiddenTypes: ['alert'], + }) + .getDecryptedAsInternalUser('alert', id, { + namespace, + }); + + return res.ok({ body: { apiKey, apiKeyOwner } }); + } catch (err) { + return res.badRequest({ body: err }); + } + } + ); } diff --git a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts index 436a98d4cf3f8..cc4c730743d46 100644 --- a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts +++ b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts @@ -76,6 +76,16 @@ export class AlertUtils { return request; } + public getAPIKeyRequest(ruleId: string) { + const request = this.supertestWithoutAuth.get( + `${getUrlPrefix(this.space.id)}/api/alerting/rule/${ruleId}/_get_api_key` + ); + if (this.user) { + return request.auth(this.user.username, this.user.password); + } + return request; + } + public getDisableRequest(alertId: string) { const request = this.supertestWithoutAuth .post(`${getUrlPrefix(this.space.id)}/api/alerting/rule/${alertId}/_disable`) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts index a5c81a849d8f8..53b7e8e3fb2c0 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/index.ts @@ -31,6 +31,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./get_alert_summary')); loadTestFile(require.resolve('./rule_types')); loadTestFile(require.resolve('./bulk_edit')); + loadTestFile(require.resolve('./retain_api_key')); }); }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/retain_api_key.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/retain_api_key.ts new file mode 100644 index 0000000000000..d406bf0212383 --- /dev/null +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/retain_api_key.ts @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { UserAtSpaceScenarios } from '../../../scenarios'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { AlertUtils, getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; + +// eslint-disable-next-line import/no-default-export +export default function retainAPIKeyTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + describe('retain api key', () => { + const objectRemover = new ObjectRemover(supertest); + + after(() => objectRemover.removeAll()); + + for (const scenario of UserAtSpaceScenarios) { + const { user, space } = scenario; + const alertUtils = new AlertUtils({ user, space, supertestWithoutAuth }); + + describe(scenario.id, () => { + it('should retain the api key when a rule is disabled and then enabled', async () => { + const { body: createdConnector } = await supertest + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + const { body: createdRule } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + actions: [ + { + id: createdConnector.id, + group: 'default', + params: {}, + }, + ], + }) + ) + .expect(200); + objectRemover.add(space.id, createdRule.id, 'rule', 'alerting'); + + const { + body: { apiKey, apiKeyOwner }, + } = await alertUtils.getAPIKeyRequest(createdRule.id); + + await alertUtils.getDisableRequest(createdRule.id); + + const { + body: { apiKey: apiKeyAfterDisable, apiKeyOwner: apiKeyOwnerAfterDisable }, + } = await alertUtils.getAPIKeyRequest(createdRule.id); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'global_read at space1': + case 'space_1_all_alerts_none_actions at space1': + case 'superuser at space1': + case 'space_1_all at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(apiKey).to.be(apiKeyAfterDisable); + expect(apiKeyOwner).to.be(apiKeyOwnerAfterDisable); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + + await alertUtils.getEnableRequest(createdRule.id); + + const { + body: { apiKey: apiKeyAfterEnable, apiKeyOwner: apiKeyOwnerAfterEnable }, + } = await alertUtils.getAPIKeyRequest(createdRule.id); + + switch (scenario.id) { + case 'no_kibana_privileges at space1': + case 'space_1_all at space2': + case 'global_read at space1': + case 'space_1_all_alerts_none_actions at space1': + case 'superuser at space1': + case 'space_1_all at space1': + case 'space_1_all_with_restricted_fixture at space1': + expect(apiKey).to.be(apiKeyAfterEnable); + expect(apiKeyOwner).to.be(apiKeyOwnerAfterEnable); + break; + default: + throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); + } + }); + }); + } + }); +}