Skip to content

Commit

Permalink
add allowed emergency contact types validation
Browse files Browse the repository at this point in the history
  • Loading branch information
almostinf committed Nov 7, 2024
1 parent 82fcf1c commit 6e1350e
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 20 deletions.
9 changes: 6 additions & 3 deletions api/authorization.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package api

import "github.com/moira-alert/moira/datatypes"

// Authorization contains authorization configuration.
type Authorization struct {
AdminList map[string]struct{}
Enabled bool
AllowedContactTypes map[string]struct{}
AdminList map[string]struct{}
Enabled bool
AllowedContactTypes map[string]struct{}
AllowedEmergencyContactTypes map[datatypes.HeartbeatType]struct{}
}

// IsEnabled returns true if auth is enabled and false otherwise.
Expand Down
14 changes: 8 additions & 6 deletions api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"

"github.com/moira-alert/moira"
"github.com/moira-alert/moira/datatypes"
)

// WebContact is container for web ui contact validation.
Expand Down Expand Up @@ -43,12 +44,13 @@ type Config struct {

// WebConfig is container for web ui configuration parameters.
type WebConfig struct {
SupportEmail string `json:"supportEmail,omitempty" example:"[email protected]"`
RemoteAllowed bool `json:"remoteAllowed" example:"true"`
MetricSourceClusters []MetricSourceCluster `json:"metric_source_clusters"`
Contacts []WebContact `json:"contacts"`
FeatureFlags FeatureFlags `json:"featureFlags"`
Sentry Sentry `json:"sentry"`
SupportEmail string `json:"supportEmail,omitempty" example:"[email protected]"`
RemoteAllowed bool `json:"remoteAllowed" example:"true"`
MetricSourceClusters []MetricSourceCluster `json:"metric_source_clusters"`
Contacts []WebContact `json:"contacts"`
EmergencyContactTypes []datatypes.HeartbeatType `json:"emergency_contact_types"`
FeatureFlags FeatureFlags `json:"featureFlags"`
Sentry Sentry `json:"sentry"`
}

// MetricSourceCluster contains data about supported metric source cluster.
Expand Down
9 changes: 9 additions & 0 deletions api/dto/emergency_contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/http"

"github.com/moira-alert/moira/api/middleware"
"github.com/moira-alert/moira/datatypes"
)

Expand All @@ -28,10 +29,18 @@ func (emergencyContact *EmergencyContact) Bind(r *http.Request) error {
return ErrEmptyHeartbeatTypes
}

auth := middleware.GetAuth(r)
userLogin := middleware.GetLogin(r)
isAdmin := auth.IsAdmin(userLogin)

for _, emergencyType := range emergencyContact.HeartbeatTypes {
if !emergencyType.IsValid() {
return fmt.Errorf("'%s' heartbeat type doesn't exist", emergencyType)
}

if _, ok := auth.AllowedEmergencyContactTypes[emergencyType]; !ok && !isAdmin {
return fmt.Errorf("'%s' heartbeat type is not allowed", emergencyType)
}
}

return nil
Expand Down
95 changes: 95 additions & 0 deletions api/dto/emergency_contact_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package dto

import (
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/moira-alert/moira/api"
"github.com/moira-alert/moira/api/middleware"
"github.com/moira-alert/moira/datatypes"
. "github.com/smartystreets/goconvey/convey"
)
Expand All @@ -16,6 +21,96 @@ var (
}
)

func TestEmergencyContactBind(t *testing.T) {
auth := &api.Authorization{
Enabled: true,
AllowedEmergencyContactTypes: map[datatypes.HeartbeatType]struct{}{
datatypes.HeartbeatNotifier: {},
datatypes.HeartbeatDatabase: {},
},
}

userLogin := "test"
testLoginKey := "login"
testAuthKey := "auth"
testContactID := "test-contact-id"

Convey("Test Bind", t, func() {
Convey("With empty emergency types", func() {
testRequest := httptest.NewRequest(http.MethodPut, "/contact", http.NoBody)
testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), testLoginKey, userLogin))
testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), testAuthKey, auth))

emergencyContact := &EmergencyContact{
ContactID: testContactID,
}

err := emergencyContact.Bind(testRequest)
So(err, ShouldEqual, ErrEmptyHeartbeatTypes)
})

Convey("With invalid heartbeat type", func() {
testRequest := httptest.NewRequest(http.MethodPut, "/contact", http.NoBody)
testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), testLoginKey, userLogin))
testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), testAuthKey, auth))

emergencyContact := &EmergencyContact{
ContactID: testContactID,
HeartbeatTypes: []datatypes.HeartbeatType{"invalid-heartbeat-type"},
}

err := emergencyContact.Bind(testRequest)
So(err, ShouldResemble, fmt.Errorf("'invalid-heartbeat-type' heartbeat type doesn't exist"))
})

Convey("With not allowed heartbeat type", func() {
testRequest := httptest.NewRequest(http.MethodPut, "/contact", http.NoBody)
testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), testLoginKey, userLogin))
testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), testAuthKey, auth))

emergencyContact := &EmergencyContact{
ContactID: testContactID,
HeartbeatTypes: []datatypes.HeartbeatType{datatypes.HeartbeatFilter},
}

err := emergencyContact.Bind(testRequest)
So(err, ShouldResemble, fmt.Errorf("'%s' heartbeat type is not allowed", datatypes.HeartbeatFilter))
})

Convey("With allowed heartbeat types", func() {
testRequest := httptest.NewRequest(http.MethodPut, "/contact", http.NoBody)
testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), testLoginKey, userLogin))
testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), testAuthKey, auth))

emergencyContact := &EmergencyContact{
ContactID: testContactID,
HeartbeatTypes: []datatypes.HeartbeatType{datatypes.HeartbeatDatabase, datatypes.HeartbeatNotifier},
}

err := emergencyContact.Bind(testRequest)
So(err, ShouldBeNil)
})

Convey("With admin who's allowed everything", func() {
testRequest := httptest.NewRequest(http.MethodPut, "/contact", http.NoBody)
testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), testLoginKey, userLogin))
testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), testAuthKey, auth))

auth.AdminList = map[string]struct{}{
userLogin: {},
}

emergencyContact := &EmergencyContact{
ContactID: testContactID,
HeartbeatTypes: []datatypes.HeartbeatType{datatypes.HeartbeatFilter, datatypes.HeartbeatNotifier},
}

err := emergencyContact.Bind(testRequest)
So(err, ShouldBeNil)
})
})
}

func TestFromEmergencyContacts(t *testing.T) {
Convey("Test FromEmergencyContacts", t, func() {
Convey("With nil emergency contacts", func() {
Expand Down
Loading

0 comments on commit 6e1350e

Please sign in to comment.