diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels.go b/pkg/services/ngalert/notifier/channels_config/available_channels.go index da42cb0d803..2aae696be1f 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels.go @@ -997,6 +997,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { Placeholder: "Discord webhook URL", PropertyName: "url", Required: true, + Secure: true, }, { Label: "Avatar URL", diff --git a/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx b/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx index 9cdfc9747ea..e4b15afdec3 100644 --- a/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/ChannelOptions.tsx @@ -68,6 +68,7 @@ export function ChannelOptions({ )?.[option.propertyName]; const defaultValue = defaultValues?.settings?.[option.propertyName]; + const hasSecureProperty = defaultValues.secureSettings?.[option.propertyName]; return ( ({ key={key} error={error} pathPrefix={pathPrefix} - pathSuffix={option.secure ? 'secureSettings.' : 'settings.'} + pathSuffix={option.secure && hasSecureProperty ? 'secureSettings.' : 'settings.'} option={option} /> ); diff --git a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx index 684a090ebfd..a0de4b6bfe8 100644 --- a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.tsx @@ -68,7 +68,9 @@ export const GrafanaReceiverForm = ({ existing, alertManagerSourceName, config } }, [existing, grafanaNotifiers.result]); const onSubmit = (values: ReceiverFormValues) => { - const newReceiver = formValuesToGrafanaReceiver(values, id2original, defaultChannelValues); + const notifiers = grafanaNotifiers.result; + + const newReceiver = formValuesToGrafanaReceiver(values, id2original, defaultChannelValues, notifiers ?? []); dispatch( updateAlertManagerConfigAction({ newConfig: updateConfigWithReceiver(config, newReceiver, existing?.name), diff --git a/public/app/features/alerting/unified/utils/__snapshots__/receiver-form.test.ts.snap b/public/app/features/alerting/unified/utils/__snapshots__/receiver-form.test.ts.snap new file mode 100644 index 00000000000..368e0ba1ba0 --- /dev/null +++ b/public/app/features/alerting/unified/utils/__snapshots__/receiver-form.test.ts.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`formValuesToGrafanaReceiver should migrate regular settings to secure settings if the field is defined as secure 1`] = ` +{ + "grafana_managed_receiver_configs": [ + { + "disableResolveMessage": false, + "name": "my-receiver", + "secureSettings": { + "url": "https://foo.bar/", + }, + "settings": {}, + "type": "discord", + "uid": "abc123", + }, + ], + "name": "my-receiver", +} +`; diff --git a/public/app/features/alerting/unified/utils/receiver-form.test.ts b/public/app/features/alerting/unified/utils/receiver-form.test.ts index 7f5e460aab8..7db175181df 100644 --- a/public/app/features/alerting/unified/utils/receiver-form.test.ts +++ b/public/app/features/alerting/unified/utils/receiver-form.test.ts @@ -1,4 +1,8 @@ -import { omitEmptyValues, omitEmptyUnlessExisting } from './receiver-form'; +import { NotifierDTO } from 'app/types'; + +import { GrafanaChannelValues, ReceiverFormValues } from '../types/receiver-form'; + +import { formValuesToGrafanaReceiver, omitEmptyValues, omitEmptyUnlessExisting } from './receiver-form'; describe('Receiver form utils', () => { describe('omitEmptyStringValues', () => { @@ -64,3 +68,46 @@ describe('Receiver form utils', () => { }); }); }); + +describe('formValuesToGrafanaReceiver', () => { + it('should migrate regular settings to secure settings if the field is defined as secure', () => { + const formValues: ReceiverFormValues = { + name: 'my-receiver', + items: [ + { + __id: '1', + secureSettings: {}, + secureFields: {}, + type: 'discord', + settings: { + url: 'https://foo.bar/', + }, + disableResolveMessage: false, + }, + ], + }; + + const channelMap = { + '1': { + uid: 'abc123', + secureSettings: {}, + secureFields: {}, + type: 'discord', + settings: { + url: 'https://foo.bar/', + }, + disableResolveMessage: false, + }, + }; + + const notifiers = [ + { + type: 'discord', + options: [{ propertyName: 'url', secure: true }], + }, + ] as NotifierDTO[]; + + // @ts-expect-error + expect(formValuesToGrafanaReceiver(formValues, channelMap, {}, notifiers)).toMatchSnapshot(); + }); +}); diff --git a/public/app/features/alerting/unified/utils/receiver-form.ts b/public/app/features/alerting/unified/utils/receiver-form.ts index a80459e354b..b023b29ff71 100644 --- a/public/app/features/alerting/unified/utils/receiver-form.ts +++ b/public/app/features/alerting/unified/utils/receiver-form.ts @@ -1,4 +1,4 @@ -import { isArray, isNil, omitBy } from 'lodash'; +import { isArray, omit, pick, isNil, omitBy } from 'lodash'; import { AlertManagerCortexConfig, @@ -79,13 +79,22 @@ export function cloudReceiverToFormValues( export function formValuesToGrafanaReceiver( values: ReceiverFormValues, channelMap: GrafanaChannelMap, - defaultChannelValues: GrafanaChannelValues + defaultChannelValues: GrafanaChannelValues, + notifiers: NotifierDTO[] ): Receiver { return { name: values.name, grafana_managed_receiver_configs: (values.items ?? []).map((channelValues) => { const existing: GrafanaManagedReceiverConfig | undefined = channelMap[channelValues.__id]; - return formChannelValuesToGrafanaChannelConfig(channelValues, defaultChannelValues, values.name, existing); + const notifier = notifiers.find((notifier) => notifier.type === channelValues.type); + + return formChannelValuesToGrafanaChannelConfig( + channelValues, + defaultChannelValues, + values.name, + existing, + notifier + ); }), }; } @@ -202,7 +211,7 @@ function grafanaChannelConfigToFormChannelValues( // work around https://github.com/grafana/alerting-squad/issues/100 notifier?.options.forEach((option) => { - if (option.secure && values.settings[option.propertyName]) { + if (option.secure && values.secureSettings[option.propertyName]) { delete values.settings[option.propertyName]; values.secureFields[option.propertyName] = true; } @@ -215,7 +224,8 @@ export function formChannelValuesToGrafanaChannelConfig( values: GrafanaChannelValues, defaults: GrafanaChannelValues, name: string, - existing?: GrafanaManagedReceiverConfig + existing?: GrafanaManagedReceiverConfig, + notifier?: NotifierDTO ): GrafanaManagedReceiverConfig { const channel: GrafanaManagedReceiverConfig = { settings: omitEmptyValues({ @@ -228,9 +238,25 @@ export function formChannelValuesToGrafanaChannelConfig( disableResolveMessage: values.disableResolveMessage ?? existing?.disableResolveMessage ?? defaults.disableResolveMessage, }; + + // find all secure field definitions + const secureFieldNames: string[] = + notifier?.options.filter((option) => option.secure).map((option) => option.propertyName) ?? []; + + // we make sure all fields that are marked as "secure" will be moved to "SecureSettings" instead of "settings" + const shouldBeSecure = pick(channel.settings, secureFieldNames); + channel.secureSettings = { + ...shouldBeSecure, + ...channel.secureSettings, + }; + + // remove the secure ones from the regular settings + channel.settings = omit(channel.settings, secureFieldNames); + if (existing) { channel.uid = existing.uid; } + return channel; }