Commit 292c985b by Mitsuhiro Tanda Committed by GitHub

Alerting: Support storing sensitive notifier settings securely/encrypted (#25114)

Support storing sensitive notification settings securely/encrypted.
Move slack notifier url and api token to secure settings.
Migrating slack notifier to store token and url encrypted is currently 
a manual process by saving an existing slack alert notification channel.
saving an existing slack alert notification channel will reset the stored 
non-secure url and token.

Closes #25113
Ref #25967

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
parent b26ef1db
...@@ -281,6 +281,11 @@ func CreateAlertNotification(c *models.ReqContext, cmd models.CreateAlertNotific ...@@ -281,6 +281,11 @@ func CreateAlertNotification(c *models.ReqContext, cmd models.CreateAlertNotific
func UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotificationCommand) Response { func UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotificationCommand) Response {
cmd.OrgId = c.OrgId cmd.OrgId = c.OrgId
err := fillWithSecureSettingsData(&cmd)
if err != nil {
return Error(500, "Failed to update alert notification", err)
}
if err := bus.Dispatch(&cmd); err != nil { if err := bus.Dispatch(&cmd); err != nil {
return Error(500, "Failed to update alert notification", err) return Error(500, "Failed to update alert notification", err)
} }
...@@ -289,13 +294,27 @@ func UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotific ...@@ -289,13 +294,27 @@ func UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotific
return Error(404, "Alert notification not found", nil) return Error(404, "Alert notification not found", nil)
} }
return JSON(200, dtos.NewAlertNotification(cmd.Result)) query := models.GetAlertNotificationsQuery{
OrgId: c.OrgId,
Id: cmd.Id,
}
if err := bus.Dispatch(&query); err != nil {
return Error(500, "Failed to get alert notification", err)
}
return JSON(200, dtos.NewAlertNotification(query.Result))
} }
func UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNotificationWithUidCommand) Response { func UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNotificationWithUidCommand) Response {
cmd.OrgId = c.OrgId cmd.OrgId = c.OrgId
cmd.Uid = c.Params("uid") cmd.Uid = c.Params("uid")
err := fillWithSecureSettingsDataByUID(&cmd)
if err != nil {
return Error(500, "Failed to update alert notification", err)
}
if err := bus.Dispatch(&cmd); err != nil { if err := bus.Dispatch(&cmd); err != nil {
return Error(500, "Failed to update alert notification", err) return Error(500, "Failed to update alert notification", err)
} }
...@@ -304,7 +323,64 @@ func UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNo ...@@ -304,7 +323,64 @@ func UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNo
return Error(404, "Alert notification not found", nil) return Error(404, "Alert notification not found", nil)
} }
return JSON(200, dtos.NewAlertNotification(cmd.Result)) query := models.GetAlertNotificationsWithUidQuery{
OrgId: cmd.OrgId,
Uid: cmd.Uid,
}
if err := bus.Dispatch(&query); err != nil {
return Error(500, "Failed to get alert notification", err)
}
return JSON(200, dtos.NewAlertNotification(query.Result))
}
func fillWithSecureSettingsData(cmd *models.UpdateAlertNotificationCommand) error {
if len(cmd.SecureSettings) == 0 {
return nil
}
query := &models.GetAlertNotificationsQuery{
OrgId: cmd.OrgId,
Id: cmd.Id,
}
if err := bus.Dispatch(query); err != nil {
return err
}
secureSettings := query.Result.SecureSettings.Decrypt()
for k, v := range secureSettings {
if _, ok := cmd.SecureSettings[k]; !ok {
cmd.SecureSettings[k] = v
}
}
return nil
}
func fillWithSecureSettingsDataByUID(cmd *models.UpdateAlertNotificationWithUidCommand) error {
if len(cmd.SecureSettings) == 0 {
return nil
}
query := &models.GetAlertNotificationsWithUidQuery{
OrgId: cmd.OrgId,
Uid: cmd.Uid,
}
if err := bus.Dispatch(query); err != nil {
return err
}
secureSettings := query.Result.SecureSettings.Decrypt()
for k, v := range secureSettings {
if _, ok := cmd.SecureSettings[k]; !ok {
cmd.SecureSettings[k] = v
}
}
return nil
} }
func DeleteAlertNotification(c *models.ReqContext) Response { func DeleteAlertNotification(c *models.ReqContext) Response {
...@@ -336,9 +412,12 @@ func DeleteAlertNotificationByUID(c *models.ReqContext) Response { ...@@ -336,9 +412,12 @@ func DeleteAlertNotificationByUID(c *models.ReqContext) Response {
//POST /api/alert-notifications/test //POST /api/alert-notifications/test
func NotificationTest(c *models.ReqContext, dto dtos.NotificationTestCommand) Response { func NotificationTest(c *models.ReqContext, dto dtos.NotificationTestCommand) Response {
cmd := &alerting.NotificationTestCommand{ cmd := &alerting.NotificationTestCommand{
Name: dto.Name, OrgID: c.OrgId,
Type: dto.Type, ID: dto.ID,
Settings: dto.Settings, Name: dto.Name,
Type: dto.Type,
Settings: dto.Settings,
SecureSettings: dto.SecureSettings,
} }
if err := bus.Dispatch(cmd); err != nil { if err := bus.Dispatch(cmd); err != nil {
......
...@@ -48,7 +48,7 @@ func formatShort(interval time.Duration) string { ...@@ -48,7 +48,7 @@ func formatShort(interval time.Duration) string {
} }
func NewAlertNotification(notification *models.AlertNotification) *AlertNotification { func NewAlertNotification(notification *models.AlertNotification) *AlertNotification {
return &AlertNotification{ dto := &AlertNotification{
Id: notification.Id, Id: notification.Id,
Uid: notification.Uid, Uid: notification.Uid,
Name: notification.Name, Name: notification.Name,
...@@ -60,7 +60,16 @@ func NewAlertNotification(notification *models.AlertNotification) *AlertNotifica ...@@ -60,7 +60,16 @@ func NewAlertNotification(notification *models.AlertNotification) *AlertNotifica
SendReminder: notification.SendReminder, SendReminder: notification.SendReminder,
DisableResolveMessage: notification.DisableResolveMessage, DisableResolveMessage: notification.DisableResolveMessage,
Settings: notification.Settings, Settings: notification.Settings,
SecureFields: map[string]bool{},
} }
if notification.SecureSettings != nil {
for k := range notification.SecureSettings {
dto.SecureFields[k] = true
}
}
return dto
} }
type AlertNotification struct { type AlertNotification struct {
...@@ -75,6 +84,7 @@ type AlertNotification struct { ...@@ -75,6 +84,7 @@ type AlertNotification struct {
Created time.Time `json:"created"` Created time.Time `json:"created"`
Updated time.Time `json:"updated"` Updated time.Time `json:"updated"`
Settings *simplejson.Json `json:"settings"` Settings *simplejson.Json `json:"settings"`
SecureFields map[string]bool `json:"secureFields"`
} }
func NewAlertNotificationLookup(notification *models.AlertNotification) *AlertNotificationLookup { func NewAlertNotificationLookup(notification *models.AlertNotification) *AlertNotificationLookup {
...@@ -122,12 +132,14 @@ type EvalMatch struct { ...@@ -122,12 +132,14 @@ type EvalMatch struct {
} }
type NotificationTestCommand struct { type NotificationTestCommand struct {
Name string `json:"name"` ID int64 `json:"id,omitempty"`
Type string `json:"type"` Name string `json:"name"`
SendReminder bool `json:"sendReminder"` Type string `json:"type"`
DisableResolveMessage bool `json:"disableResolveMessage"` SendReminder bool `json:"sendReminder"`
Frequency string `json:"frequency"` DisableResolveMessage bool `json:"disableResolveMessage"`
Settings *simplejson.Json `json:"settings"` Frequency string `json:"frequency"`
Settings *simplejson.Json `json:"settings"`
SecureSettings map[string]string `json:"secureSettings"`
} }
type PauseAlertCommand struct { type PauseAlertCommand struct {
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"errors" "errors"
"time" "time"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
) )
...@@ -24,59 +25,63 @@ var ( ...@@ -24,59 +25,63 @@ var (
) )
type AlertNotification struct { type AlertNotification struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Uid string `json:"-"` Uid string `json:"-"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
SendReminder bool `json:"sendReminder"` SendReminder bool `json:"sendReminder"`
DisableResolveMessage bool `json:"disableResolveMessage"` DisableResolveMessage bool `json:"disableResolveMessage"`
Frequency time.Duration `json:"frequency"` Frequency time.Duration `json:"frequency"`
IsDefault bool `json:"isDefault"` IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings"` Settings *simplejson.Json `json:"settings"`
Created time.Time `json:"created"` SecureSettings securejsondata.SecureJsonData `json:"secureSettings"`
Updated time.Time `json:"updated"` Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
} }
type CreateAlertNotificationCommand struct { type CreateAlertNotificationCommand struct {
Uid string `json:"uid"` Uid string `json:"uid"`
Name string `json:"name" binding:"Required"` Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"` Type string `json:"type" binding:"Required"`
SendReminder bool `json:"sendReminder"` SendReminder bool `json:"sendReminder"`
DisableResolveMessage bool `json:"disableResolveMessage"` DisableResolveMessage bool `json:"disableResolveMessage"`
Frequency string `json:"frequency"` Frequency string `json:"frequency"`
IsDefault bool `json:"isDefault"` IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings"` Settings *simplejson.Json `json:"settings"`
SecureSettings map[string]string `json:"secureSettings"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
Result *AlertNotification Result *AlertNotification
} }
type UpdateAlertNotificationCommand struct { type UpdateAlertNotificationCommand struct {
Id int64 `json:"id" binding:"Required"` Id int64 `json:"id" binding:"Required"`
Uid string `json:"uid"` Uid string `json:"uid"`
Name string `json:"name" binding:"Required"` Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"` Type string `json:"type" binding:"Required"`
SendReminder bool `json:"sendReminder"` SendReminder bool `json:"sendReminder"`
DisableResolveMessage bool `json:"disableResolveMessage"` DisableResolveMessage bool `json:"disableResolveMessage"`
Frequency string `json:"frequency"` Frequency string `json:"frequency"`
IsDefault bool `json:"isDefault"` IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings" binding:"Required"` Settings *simplejson.Json `json:"settings" binding:"Required"`
SecureSettings map[string]string `json:"secureSettings"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
Result *AlertNotification Result *AlertNotification
} }
type UpdateAlertNotificationWithUidCommand struct { type UpdateAlertNotificationWithUidCommand struct {
Uid string `json:"-"` Uid string `json:"-"`
NewUid string `json:"uid"` NewUid string `json:"uid"`
Name string `json:"name" binding:"Required"` Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"` Type string `json:"type" binding:"Required"`
SendReminder bool `json:"sendReminder"` SendReminder bool `json:"sendReminder"`
DisableResolveMessage bool `json:"disableResolveMessage"` DisableResolveMessage bool `json:"disableResolveMessage"`
Frequency string `json:"frequency"` Frequency string `json:"frequency"`
IsDefault bool `json:"isDefault"` IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings" binding:"Required"` Settings *simplejson.Json `json:"settings" binding:"Required"`
SecureSettings map[string]string `json:"secureSettings"`
OrgId int64 OrgId int64
Result *AlertNotification Result *AlertNotification
...@@ -157,3 +162,11 @@ type GetOrCreateNotificationStateQuery struct { ...@@ -157,3 +162,11 @@ type GetOrCreateNotificationStateQuery struct {
Result *AlertNotificationState Result *AlertNotificationState
} }
// decryptedValue returns decrypted value from secureSettings
func (an *AlertNotification) DecryptedValue(field string, fallback string) string {
if value, ok := an.SecureSettings.DecryptedValue(field); ok {
return value
}
return fallback
}
...@@ -31,7 +31,19 @@ func init() { ...@@ -31,7 +31,19 @@ func init() {
<h3 class="page-heading">Slack settings</h3> <h3 class="page-heading">Slack settings</h3>
<div class="gf-form max-width-30"> <div class="gf-form max-width-30">
<span class="gf-form-label width-8">Url</span> <span class="gf-form-label width-8">Url</span>
<input type="text" required class="gf-form-input max-width-30" ng-model="ctrl.model.settings.url" placeholder="Slack incoming webhook url"></input> <div class="gf-form gf-form--grow" ng-if="!ctrl.model.secureFields.url">
<input type="text"
required
class="gf-form-input max-width-30"
ng-init="ctrl.model.secureSettings.url = ctrl.model.settings.url || null; ctrl.model.settings.url = null;"
ng-model="ctrl.model.secureSettings.url"
placeholder="Slack incoming webhook url">
</input>
</div>
<div class="gf-form" ng-if="ctrl.model.secureFields.url">
<input type="text" class="gf-form-input max-width-18" disabled="disabled" value="configured" />
<a class="btn btn-secondary gf-form-btn" href="#" ng-click="ctrl.model.secureFields.url = false">reset</a>
</div>
</div> </div>
<div class="gf-form max-width-30"> <div class="gf-form max-width-30">
<span class="gf-form-label width-8">Recipient</span> <span class="gf-form-label width-8">Recipient</span>
...@@ -114,15 +126,22 @@ func init() { ...@@ -114,15 +126,22 @@ func init() {
</info-popover> </info-popover>
</div> </div>
<div class="gf-form max-width-30"> <div class="gf-form max-width-30">
<span class="gf-form-label width-8">Token</span> <div class="gf-form gf-form--v-stretch"><label class="gf-form-label width-8">Token</label></div>
<input type="text" <div class="gf-form gf-form--grow" ng-if="!ctrl.model.secureFields.token">
class="gf-form-input max-width-30" <input type="text"
ng-model="ctrl.model.settings.token" class="gf-form-input max-width-30"
data-placement="right"> ng-init="ctrl.model.secureSettings.token = ctrl.model.settings.token || null; ctrl.model.settings.token = null;"
</input> ng-model="ctrl.model.secureSettings.token"
<info-popover mode="right-absolute"> data-placement="right">
Provide a bot token to use the Slack file.upload API (starts with "xoxb"). Specify Recipient for this to work </input>
</info-popover> <info-popover mode="right-absolute">
Provide a bot token to use the Slack file.upload API (starts with "xoxb"). Specify Recipient for this to work
</info-popover>
</div>
<div class="gf-form" ng-if="ctrl.model.secureFields.token">
<input type="text" class="gf-form-input max-width-18" disabled="disabled" value="configured" />
<a class="btn btn-secondary gf-form-btn" href="#" ng-click="ctrl.model.secureFields.token = false">reset</a>
</div>
</div> </div>
`, `,
Options: []alerting.NotifierOption{ Options: []alerting.NotifierOption{
...@@ -211,7 +230,7 @@ var reRecipient *regexp.Regexp = regexp.MustCompile("^((@[a-z0-9][a-zA-Z0-9._-]* ...@@ -211,7 +230,7 @@ var reRecipient *regexp.Regexp = regexp.MustCompile("^((@[a-z0-9][a-zA-Z0-9._-]*
// NewSlackNotifier is the constructor for the Slack notifier // NewSlackNotifier is the constructor for the Slack notifier
func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString() url := model.DecryptedValue("url", model.Settings.Get("url").MustString())
if url == "" { if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
} }
...@@ -226,7 +245,8 @@ func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error ...@@ -226,7 +245,8 @@ func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error
mentionUsersStr := model.Settings.Get("mentionUsers").MustString() mentionUsersStr := model.Settings.Get("mentionUsers").MustString()
mentionGroupsStr := model.Settings.Get("mentionGroups").MustString() mentionGroupsStr := model.Settings.Get("mentionGroups").MustString()
mentionChannel := model.Settings.Get("mentionChannel").MustString() mentionChannel := model.Settings.Get("mentionChannel").MustString()
token := model.Settings.Get("token").MustString() token := model.DecryptedValue("token", model.Settings.Get("token").MustString())
uploadImage := model.Settings.Get("uploadImage").MustBool(true) uploadImage := model.Settings.Get("uploadImage").MustBool(true)
if mentionChannel != "" && mentionChannel != "here" && mentionChannel != "channel" { if mentionChannel != "" && mentionChannel != "here" && mentionChannel != "channel" {
......
...@@ -3,6 +3,7 @@ package notifiers ...@@ -3,6 +3,7 @@ package notifiers
import ( import (
"testing" "testing"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
...@@ -96,6 +97,49 @@ func TestSlackNotifier(t *testing.T) { ...@@ -96,6 +97,49 @@ func TestSlackNotifier(t *testing.T) {
So(slackNotifier.Token, ShouldEqual, "xoxb-XXXXXXXX-XXXXXXXX-XXXXXXXXXX") So(slackNotifier.Token, ShouldEqual, "xoxb-XXXXXXXX-XXXXXXXX-XXXXXXXXXX")
}) })
Convey("from settings with Recipient, Username, IconEmoji, IconUrl, MentionUsers, MentionGroups, MentionChannel, and Secured Token", func() {
json := `
{
"url": "http://google.com",
"recipient": "#ds-opentsdb",
"username": "Grafana Alerts",
"icon_emoji": ":smile:",
"icon_url": "https://grafana.com/img/fav32.png",
"mentionUsers": "user1, user2",
"mentionGroups": "group1, group2",
"mentionChannel": "here",
"token": "uenc-XXXXXXXX-XXXXXXXX-XXXXXXXXXX"
}`
settingsJSON, err := simplejson.NewJson([]byte(json))
securedSettingsJSON := securejsondata.GetEncryptedJsonData(map[string]string{
"token": "xenc-XXXXXXXX-XXXXXXXX-XXXXXXXXXX",
})
So(err, ShouldBeNil)
model := &models.AlertNotification{
Name: "ops",
Type: "slack",
Settings: settingsJSON,
SecureSettings: securedSettingsJSON,
}
not, err := NewSlackNotifier(model)
slackNotifier := not.(*SlackNotifier)
So(err, ShouldBeNil)
So(slackNotifier.Name, ShouldEqual, "ops")
So(slackNotifier.Type, ShouldEqual, "slack")
So(slackNotifier.URL, ShouldEqual, "http://google.com")
So(slackNotifier.Recipient, ShouldEqual, "#ds-opentsdb")
So(slackNotifier.Username, ShouldEqual, "Grafana Alerts")
So(slackNotifier.IconEmoji, ShouldEqual, ":smile:")
So(slackNotifier.IconURL, ShouldEqual, "https://grafana.com/img/fav32.png")
So(slackNotifier.MentionUsers, ShouldResemble, []string{"user1", "user2"})
So(slackNotifier.MentionGroups, ShouldResemble, []string{"group1", "group2"})
So(slackNotifier.MentionChannel, ShouldEqual, "here")
So(slackNotifier.Token, ShouldEqual, "xenc-XXXXXXXX-XXXXXXXX-XXXXXXXXXX")
})
Convey("with channel recipient with spaces should return an error", func() { Convey("with channel recipient with spaces should return an error", func() {
json := ` json := `
{ {
......
...@@ -4,6 +4,8 @@ import ( ...@@ -4,6 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
...@@ -14,10 +16,13 @@ import ( ...@@ -14,10 +16,13 @@ import (
// NotificationTestCommand initiates an test // NotificationTestCommand initiates an test
// execution of an alert notification. // execution of an alert notification.
type NotificationTestCommand struct { type NotificationTestCommand struct {
State models.AlertStateType OrgID int64
Name string ID int64
Type string State models.AlertStateType
Settings *simplejson.Json Name string
Type string
Settings *simplejson.Json
SecureSettings map[string]string
} }
var ( var (
...@@ -37,6 +42,28 @@ func handleNotificationTestCommand(cmd *NotificationTestCommand) error { ...@@ -37,6 +42,28 @@ func handleNotificationTestCommand(cmd *NotificationTestCommand) error {
Settings: cmd.Settings, Settings: cmd.Settings,
} }
secureSettingsMap := map[string]string{}
if cmd.ID > 0 {
query := &models.GetAlertNotificationsQuery{
OrgId: cmd.OrgID,
Id: cmd.ID,
}
if err := bus.Dispatch(query); err != nil {
return err
}
if query.Result.SecureSettings != nil {
secureSettingsMap = query.Result.SecureSettings.Decrypt()
}
}
for k, v := range cmd.SecureSettings {
secureSettingsMap[k] = v
}
model.SecureSettings = securejsondata.GetEncryptedJsonData(secureSettingsMap)
notifiers, err := InitNotifier(model) notifiers, err := InitNotifier(model)
if err != nil { if err != nil {
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
...@@ -120,6 +121,7 @@ func GetAlertNotificationsWithUidToSend(query *models.GetAlertNotificationsWithU ...@@ -120,6 +121,7 @@ func GetAlertNotificationsWithUidToSend(query *models.GetAlertNotificationsWithU
alert_notification.created, alert_notification.created,
alert_notification.updated, alert_notification.updated,
alert_notification.settings, alert_notification.settings,
alert_notification.secure_settings,
alert_notification.is_default, alert_notification.is_default,
alert_notification.disable_resolve_message, alert_notification.disable_resolve_message,
alert_notification.send_reminder, alert_notification.send_reminder,
...@@ -192,6 +194,7 @@ func getAlertNotificationInternal(query *models.GetAlertNotificationsQuery, sess ...@@ -192,6 +194,7 @@ func getAlertNotificationInternal(query *models.GetAlertNotificationsQuery, sess
alert_notification.created, alert_notification.created,
alert_notification.updated, alert_notification.updated,
alert_notification.settings, alert_notification.settings,
alert_notification.secure_settings,
alert_notification.is_default, alert_notification.is_default,
alert_notification.disable_resolve_message, alert_notification.disable_resolve_message,
alert_notification.send_reminder, alert_notification.send_reminder,
...@@ -241,6 +244,7 @@ func getAlertNotificationWithUidInternal(query *models.GetAlertNotificationsWith ...@@ -241,6 +244,7 @@ func getAlertNotificationWithUidInternal(query *models.GetAlertNotificationsWith
alert_notification.created, alert_notification.created,
alert_notification.updated, alert_notification.updated,
alert_notification.settings, alert_notification.settings,
alert_notification.secure_settings,
alert_notification.is_default, alert_notification.is_default,
alert_notification.disable_resolve_message, alert_notification.disable_resolve_message,
alert_notification.send_reminder, alert_notification.send_reminder,
...@@ -308,12 +312,20 @@ func CreateAlertNotificationCommand(cmd *models.CreateAlertNotificationCommand) ...@@ -308,12 +312,20 @@ func CreateAlertNotificationCommand(cmd *models.CreateAlertNotificationCommand)
} }
} }
// delete empty keys
for k, v := range cmd.SecureSettings {
if v == "" {
delete(cmd.SecureSettings, k)
}
}
alertNotification := &models.AlertNotification{ alertNotification := &models.AlertNotification{
Uid: cmd.Uid, Uid: cmd.Uid,
OrgId: cmd.OrgId, OrgId: cmd.OrgId,
Name: cmd.Name, Name: cmd.Name,
Type: cmd.Type, Type: cmd.Type,
Settings: cmd.Settings, Settings: cmd.Settings,
SecureSettings: securejsondata.GetEncryptedJsonData(cmd.SecureSettings),
SendReminder: cmd.SendReminder, SendReminder: cmd.SendReminder,
DisableResolveMessage: cmd.DisableResolveMessage, DisableResolveMessage: cmd.DisableResolveMessage,
Frequency: frequency, Frequency: frequency,
...@@ -365,8 +377,16 @@ func UpdateAlertNotification(cmd *models.UpdateAlertNotificationCommand) error { ...@@ -365,8 +377,16 @@ func UpdateAlertNotification(cmd *models.UpdateAlertNotificationCommand) error {
return fmt.Errorf("Alert notification name %s already exists", cmd.Name) return fmt.Errorf("Alert notification name %s already exists", cmd.Name)
} }
// delete empty keys
for k, v := range cmd.SecureSettings {
if v == "" {
delete(cmd.SecureSettings, k)
}
}
current.Updated = time.Now() current.Updated = time.Now()
current.Settings = cmd.Settings current.Settings = cmd.Settings
current.SecureSettings = securejsondata.GetEncryptedJsonData(cmd.SecureSettings)
current.Name = cmd.Name current.Name = cmd.Name
current.Type = cmd.Type current.Type = cmd.Type
current.IsDefault = cmd.IsDefault current.IsDefault = cmd.IsDefault
...@@ -430,6 +450,7 @@ func UpdateAlertNotificationWithUid(cmd *models.UpdateAlertNotificationWithUidCo ...@@ -430,6 +450,7 @@ func UpdateAlertNotificationWithUid(cmd *models.UpdateAlertNotificationWithUidCo
Frequency: cmd.Frequency, Frequency: cmd.Frequency,
IsDefault: cmd.IsDefault, IsDefault: cmd.IsDefault,
Settings: cmd.Settings, Settings: cmd.Settings,
SecureSettings: cmd.SecureSettings,
OrgId: cmd.OrgId, OrgId: cmd.OrgId,
} }
......
...@@ -167,4 +167,8 @@ func addAlertMigrations(mg *Migrator) { ...@@ -167,4 +167,8 @@ func addAlertMigrations(mg *Migrator) {
mg.AddMigration("Remove unique index org_id_name", NewDropIndexMigration(alert_notification, &Index{ mg.AddMigration("Remove unique index org_id_name", NewDropIndexMigration(alert_notification, &Index{
Cols: []string{"org_id", "name"}, Type: UniqueIndex, Cols: []string{"org_id", "name"}, Type: UniqueIndex,
})) }))
mg.AddMigration("Add column secure_settings in alert_notification", NewAddColumnMigration(alert_notification, &Column{
Name: "secure_settings", Type: DB_Text, Nullable: true,
}))
} }
...@@ -26,6 +26,7 @@ export class AlertNotificationEditCtrl { ...@@ -26,6 +26,7 @@ export class AlertNotificationEditCtrl {
severity: 'critical', severity: 'critical',
uploadImage: true, uploadImage: true,
}, },
secureSettings: {},
isDefault: false, isDefault: false,
}; };
getFrequencySuggestion: any; getFrequencySuggestion: any;
...@@ -72,6 +73,7 @@ export class AlertNotificationEditCtrl { ...@@ -72,6 +73,7 @@ export class AlertNotificationEditCtrl {
this.navModel.breadcrumbs.push({ text: result.name }); this.navModel.breadcrumbs.push({ text: result.name });
this.navModel.node = { text: result.name }; this.navModel.node = { text: result.name };
result.settings = _.defaults(result.settings, this.defaults.settings); result.settings = _.defaults(result.settings, this.defaults.settings);
result.secureSettings = _.defaults(result.secureSettings, this.defaults.secureSettings);
return result; return result;
}); });
}) })
...@@ -149,6 +151,7 @@ export class AlertNotificationEditCtrl { ...@@ -149,6 +151,7 @@ export class AlertNotificationEditCtrl {
typeChanged() { typeChanged() {
this.model.settings = _.defaults({}, this.defaults.settings); this.model.settings = _.defaults({}, this.defaults.settings);
this.model.secureSettings = _.defaults({}, this.defaults.secureSettings);
this.notifierTemplateId = this.getNotifierTemplateId(this.model.type); this.notifierTemplateId = this.getNotifierTemplateId(this.model.type);
} }
...@@ -157,13 +160,18 @@ export class AlertNotificationEditCtrl { ...@@ -157,13 +160,18 @@ export class AlertNotificationEditCtrl {
return; return;
} }
const payload = { const payload: any = {
name: this.model.name, name: this.model.name,
type: this.model.type, type: this.model.type,
frequency: this.model.frequency, frequency: this.model.frequency,
settings: this.model.settings, settings: this.model.settings,
secureSettings: this.model.secureSettings,
}; };
if (this.model.id) {
payload.id = this.model.id;
}
promiseToDigest(this.$scope)(getBackendSrv().post(`/api/alert-notifications/test`, payload)); promiseToDigest(this.$scope)(getBackendSrv().post(`/api/alert-notifications/test`, payload));
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment