diff --git a/api/controller/contact.go b/api/controller/contact.go index 8e460dbe5..41482cf94 100644 --- a/api/controller/contact.go +++ b/api/controller/contact.go @@ -47,14 +47,20 @@ func GetContactById(database moira.Database, contactID string) (*dto.Contact, *a } // CreateContact creates new notification contact for current user. -func CreateContact(dataBase moira.Database, contact *dto.Contact, userLogin, teamID string) *api.ErrorResponse { +func CreateContact(dataBase moira.Database, auth *api.Authorization, contact *dto.Contact, userLogin, teamID string) *api.ErrorResponse { if userLogin != "" && teamID != "" { return api.ErrorInternalServer(fmt.Errorf("CreateContact: cannot create contact when both userLogin and teamID specified")) } + + // Only admins are allowed to create contacts for other users + if !auth.IsAdmin(userLogin) || contact.User == "" { + contact.User = userLogin + } + contactData := moira.ContactData{ ID: contact.ID, Name: contact.Name, - User: userLogin, + User: contact.User, Team: teamID, Type: contact.Type, Value: contact.Value, @@ -78,7 +84,7 @@ func CreateContact(dataBase moira.Database, contact *dto.Contact, userLogin, tea if err := dataBase.SaveContact(&contactData); err != nil { return api.ErrorInternalServer(err) } - contact.User = userLogin + contact.User = contactData.User contact.ID = contactData.ID contact.TeamID = contactData.Team return nil diff --git a/api/controller/contact_test.go b/api/controller/contact_test.go index b8b597d31..06c0087da 100644 --- a/api/controller/contact_test.go +++ b/api/controller/contact_test.go @@ -113,6 +113,7 @@ func TestCreateContact(t *testing.T) { defer mockCtrl.Finish() const userLogin = "user" const teamID = "team" + auth := &api.Authorization{Enabled: false} Convey("Create for user", t, func() { Convey("Success", func() { @@ -121,7 +122,7 @@ func TestCreateContact(t *testing.T) { Type: "mail", } dataBase.EXPECT().SaveContact(gomock.Any()).Return(nil) - err := CreateContact(dataBase, contact, userLogin, "") + err := CreateContact(dataBase, auth, contact, userLogin, "") So(err, ShouldBeNil) So(contact.User, ShouldResemble, userLogin) }) @@ -140,7 +141,7 @@ func TestCreateContact(t *testing.T) { } dataBase.EXPECT().GetContact(contact.ID).Return(moira.ContactData{}, database.ErrNil) dataBase.EXPECT().SaveContact(&expectedContact).Return(nil) - err := CreateContact(dataBase, &contact, userLogin, "") + err := CreateContact(dataBase, auth, &contact, userLogin, "") So(err, ShouldBeNil) So(contact.User, ShouldResemble, userLogin) So(contact.ID, ShouldResemble, contact.ID) @@ -153,7 +154,7 @@ func TestCreateContact(t *testing.T) { Type: "mail", } dataBase.EXPECT().GetContact(contact.ID).Return(moira.ContactData{}, nil) - err := CreateContact(dataBase, contact, userLogin, "") + err := CreateContact(dataBase, auth, contact, userLogin, "") So(err, ShouldResemble, api.ErrorInvalidRequest(fmt.Errorf("contact with this ID already exists"))) }) @@ -165,7 +166,7 @@ func TestCreateContact(t *testing.T) { } err := fmt.Errorf("oooops! Can not write contact") dataBase.EXPECT().GetContact(contact.ID).Return(moira.ContactData{}, err) - expected := CreateContact(dataBase, contact, userLogin, "") + expected := CreateContact(dataBase, auth, contact, userLogin, "") So(expected, ShouldResemble, api.ErrorInternalServer(err)) }) @@ -176,7 +177,7 @@ func TestCreateContact(t *testing.T) { } err := fmt.Errorf("oooops! Can not write contact") dataBase.EXPECT().SaveContact(gomock.Any()).Return(err) - expected := CreateContact(dataBase, contact, userLogin, "") + expected := CreateContact(dataBase, auth, contact, userLogin, "") So(expected, ShouldResemble, &api.ErrorResponse{ ErrorText: err.Error(), HTTPStatusCode: http.StatusInternalServerError, @@ -193,7 +194,7 @@ func TestCreateContact(t *testing.T) { Type: "mail", } dataBase.EXPECT().SaveContact(gomock.Any()).Return(nil) - err := CreateContact(dataBase, contact, "", teamID) + err := CreateContact(dataBase, auth, contact, "", teamID) So(err, ShouldBeNil) So(contact.TeamID, ShouldResemble, teamID) }) @@ -212,7 +213,7 @@ func TestCreateContact(t *testing.T) { } dataBase.EXPECT().GetContact(contact.ID).Return(moira.ContactData{}, database.ErrNil) dataBase.EXPECT().SaveContact(&expectedContact).Return(nil) - err := CreateContact(dataBase, &contact, "", teamID) + err := CreateContact(dataBase, auth, &contact, "", teamID) So(err, ShouldBeNil) So(contact.TeamID, ShouldResemble, teamID) So(contact.ID, ShouldResemble, contact.ID) @@ -234,7 +235,7 @@ func TestCreateContact(t *testing.T) { } dataBase.EXPECT().GetContact(contact.ID).Return(moira.ContactData{}, database.ErrNil) dataBase.EXPECT().SaveContact(&expectedContact).Return(nil) - err := CreateContact(dataBase, &contact, "", teamID) + err := CreateContact(dataBase, auth, &contact, "", teamID) So(err, ShouldBeNil) So(contact.TeamID, ShouldResemble, teamID) So(contact.Name, ShouldResemble, expectedContact.Name) @@ -247,7 +248,7 @@ func TestCreateContact(t *testing.T) { Type: "mail", } dataBase.EXPECT().GetContact(contact.ID).Return(moira.ContactData{}, nil) - err := CreateContact(dataBase, contact, "", teamID) + err := CreateContact(dataBase, auth, contact, "", teamID) So(err, ShouldResemble, api.ErrorInvalidRequest(fmt.Errorf("contact with this ID already exists"))) }) @@ -259,7 +260,7 @@ func TestCreateContact(t *testing.T) { } err := fmt.Errorf("oooops! Can not write contact") dataBase.EXPECT().GetContact(contact.ID).Return(moira.ContactData{}, err) - expected := CreateContact(dataBase, contact, "", teamID) + expected := CreateContact(dataBase, auth, contact, "", teamID) So(expected, ShouldResemble, api.ErrorInternalServer(err)) }) @@ -270,7 +271,7 @@ func TestCreateContact(t *testing.T) { } err := fmt.Errorf("oooops! Can not write contact") dataBase.EXPECT().SaveContact(gomock.Any()).Return(err) - expected := CreateContact(dataBase, contact, "", teamID) + expected := CreateContact(dataBase, auth, contact, "", teamID) So(expected, ShouldResemble, &api.ErrorResponse{ ErrorText: err.Error(), HTTPStatusCode: http.StatusInternalServerError, @@ -284,11 +285,73 @@ func TestCreateContact(t *testing.T) { Value: "some@mail.com", Type: "mail", } - err := CreateContact(dataBase, contact, userLogin, teamID) + err := CreateContact(dataBase, auth, contact, userLogin, teamID) So(err, ShouldResemble, api.ErrorInternalServer(fmt.Errorf("CreateContact: cannot create contact when both userLogin and teamID specified"))) }) } +func TestAdminsCreatesContact(t *testing.T) { + mockCtrl := gomock.NewController(t) + dataBase := mock_moira_alert.NewMockDatabase(mockCtrl) + defer mockCtrl.Finish() + const userLogin = "user" + const adminLogin = "admin" + auth := &api.Authorization{ + Enabled: true, + AdminList: map[string]struct{}{adminLogin: {}}, + } + + Convey("Create for user", t, func() { + Convey("The same user", func() { + contact := &dto.Contact{ + Value: "some@mail.com", + Type: "mail", + User: userLogin, + } + dataBase.EXPECT().SaveContact(gomock.Any()).Return(nil) + err := CreateContact(dataBase, auth, contact, userLogin, "") + So(err, ShouldBeNil) + So(contact.User, ShouldResemble, userLogin) + }) + + Convey("The same user by admin", func() { + contact := &dto.Contact{ + Value: "some@mail.com", + Type: "mail", + User: adminLogin, + } + dataBase.EXPECT().SaveContact(gomock.Any()).Return(nil) + err := CreateContact(dataBase, auth, contact, adminLogin, "") + So(err, ShouldBeNil) + So(contact.User, ShouldResemble, adminLogin) + }) + + Convey("Non admin can not create contact for other user", func() { + contact := &dto.Contact{ + Value: "some@mail.com", + Type: "mail", + User: adminLogin, + } + dataBase.EXPECT().SaveContact(gomock.Any()).Return(nil) + err := CreateContact(dataBase, auth, contact, userLogin, "") + So(err, ShouldBeNil) + So(contact.User, ShouldResemble, userLogin) + }) + + Convey("Admin can create contact for other user", func() { + contact := &dto.Contact{ + Value: "some@mail.com", + Type: "mail", + User: userLogin, + } + dataBase.EXPECT().SaveContact(gomock.Any()).Return(nil) + err := CreateContact(dataBase, auth, contact, adminLogin, "") + So(err, ShouldBeNil) + So(contact.User, ShouldResemble, userLogin) + }) + }) +} + func TestUpdateContact(t *testing.T) { mockCtrl := gomock.NewController(t) dataBase := mock_moira_alert.NewMockDatabase(mockCtrl) diff --git a/api/controller/subscription.go b/api/controller/subscription.go index 1e725669c..be6754307 100644 --- a/api/controller/subscription.go +++ b/api/controller/subscription.go @@ -36,7 +36,7 @@ func GetUserSubscriptions(database moira.Database, userLogin string) (*dto.Subsc } // CreateSubscription create or update subscription. -func CreateSubscription(dataBase moira.Database, userLogin, teamID string, subscription *dto.Subscription) *api.ErrorResponse { +func CreateSubscription(dataBase moira.Database, auth *api.Authorization, userLogin, teamID string, subscription *dto.Subscription) *api.ErrorResponse { if userLogin != "" && teamID != "" { return api.ErrorInternalServer(fmt.Errorf("CreateSubscription: cannot create subscription when both userLogin and teamID specified")) } @@ -56,7 +56,10 @@ func CreateSubscription(dataBase moira.Database, userLogin, teamID string, subsc } } - subscription.User = userLogin + // Only admins are allowed to create subscriptions for other users + if !auth.IsAdmin(userLogin) || subscription.User == "" { + subscription.User = userLogin + } subscription.TeamID = teamID data := moira.SubscriptionData(*subscription) if err := dataBase.SaveSubscription(&data); err != nil { diff --git a/api/controller/subscription_test.go b/api/controller/subscription_test.go index 0d275c40a..9bfe4072c 100644 --- a/api/controller/subscription_test.go +++ b/api/controller/subscription_test.go @@ -176,12 +176,13 @@ func TestCreateSubscription(t *testing.T) { dataBase := mock_moira_alert.NewMockDatabase(mockCtrl) const login = "user" const teamID = "testTeam" + auth := &api.Authorization{Enabled: false} Convey("Create for user", t, func() { Convey("Success create", func() { subscription := dto.Subscription{ID: ""} dataBase.EXPECT().SaveSubscription(gomock.Any()).Return(nil) - err := CreateSubscription(dataBase, login, "", &subscription) + err := CreateSubscription(dataBase, auth, login, "", &subscription) So(err, ShouldBeNil) }) @@ -191,7 +192,7 @@ func TestCreateSubscription(t *testing.T) { } dataBase.EXPECT().GetSubscription(sub.ID).Return(moira.SubscriptionData{}, database.ErrNil) dataBase.EXPECT().SaveSubscription(gomock.Any()).Return(nil) - err := CreateSubscription(dataBase, login, "", sub) + err := CreateSubscription(dataBase, auth, login, "", sub) So(err, ShouldBeNil) So(sub.User, ShouldResemble, login) So(sub.ID, ShouldResemble, sub.ID) @@ -202,7 +203,7 @@ func TestCreateSubscription(t *testing.T) { ID: uuid.Must(uuid.NewV4()).String(), } dataBase.EXPECT().GetSubscription(subscription.ID).Return(moira.SubscriptionData{}, nil) - err := CreateSubscription(dataBase, login, "", subscription) + err := CreateSubscription(dataBase, auth, login, "", subscription) So(err, ShouldResemble, api.ErrorInvalidRequest(fmt.Errorf("subscription with this ID already exists"))) }) @@ -212,7 +213,7 @@ func TestCreateSubscription(t *testing.T) { } err := fmt.Errorf("oooops! Can not write contact") dataBase.EXPECT().GetSubscription(subscription.ID).Return(moira.SubscriptionData{}, err) - expected := CreateSubscription(dataBase, login, "", subscription) + expected := CreateSubscription(dataBase, auth, login, "", subscription) So(expected, ShouldResemble, api.ErrorInternalServer(err)) }) @@ -220,7 +221,7 @@ func TestCreateSubscription(t *testing.T) { subscription := dto.Subscription{ID: ""} expected := fmt.Errorf("oooops! Can not create subscription") dataBase.EXPECT().SaveSubscription(gomock.Any()).Return(expected) - err := CreateSubscription(dataBase, login, "", &subscription) + err := CreateSubscription(dataBase, auth, login, "", &subscription) So(err, ShouldResemble, api.ErrorInternalServer(expected)) }) }) @@ -228,7 +229,7 @@ func TestCreateSubscription(t *testing.T) { Convey("Success create", func() { subscription := dto.Subscription{ID: ""} dataBase.EXPECT().SaveSubscription(gomock.Any()).Return(nil) - err := CreateSubscription(dataBase, "", teamID, &subscription) + err := CreateSubscription(dataBase, auth, "", teamID, &subscription) So(err, ShouldBeNil) }) @@ -238,7 +239,7 @@ func TestCreateSubscription(t *testing.T) { } dataBase.EXPECT().GetSubscription(sub.ID).Return(moira.SubscriptionData{}, database.ErrNil) dataBase.EXPECT().SaveSubscription(gomock.Any()).Return(nil) - err := CreateSubscription(dataBase, "", teamID, sub) + err := CreateSubscription(dataBase, auth, "", teamID, sub) So(err, ShouldBeNil) So(sub.TeamID, ShouldResemble, teamID) So(sub.ID, ShouldResemble, sub.ID) @@ -249,7 +250,7 @@ func TestCreateSubscription(t *testing.T) { ID: uuid.Must(uuid.NewV4()).String(), } dataBase.EXPECT().GetSubscription(subscription.ID).Return(moira.SubscriptionData{}, nil) - err := CreateSubscription(dataBase, "", teamID, subscription) + err := CreateSubscription(dataBase, auth, "", teamID, subscription) So(err, ShouldResemble, api.ErrorInvalidRequest(fmt.Errorf("subscription with this ID already exists"))) }) @@ -259,7 +260,7 @@ func TestCreateSubscription(t *testing.T) { } err := fmt.Errorf("oooops! Can not write contact") dataBase.EXPECT().GetSubscription(subscription.ID).Return(moira.SubscriptionData{}, err) - expected := CreateSubscription(dataBase, "", teamID, subscription) + expected := CreateSubscription(dataBase, auth, "", teamID, subscription) So(expected, ShouldResemble, api.ErrorInternalServer(err)) }) @@ -267,17 +268,71 @@ func TestCreateSubscription(t *testing.T) { subscription := dto.Subscription{ID: ""} expected := fmt.Errorf("oooops! Can not create subscription") dataBase.EXPECT().SaveSubscription(gomock.Any()).Return(expected) - err := CreateSubscription(dataBase, "", teamID, &subscription) + err := CreateSubscription(dataBase, auth, "", teamID, &subscription) So(err, ShouldResemble, api.ErrorInternalServer(expected)) }) }) Convey("Error on create with both: userLogin and teamID specified", t, func() { subscription := &dto.Subscription{} - err := CreateSubscription(dataBase, login, teamID, subscription) + err := CreateSubscription(dataBase, auth, login, teamID, subscription) So(err, ShouldResemble, api.ErrorInternalServer(fmt.Errorf("CreateSubscription: cannot create subscription when both userLogin and teamID specified"))) }) } +func TestAdminsCreatesSubscription(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + dataBase := mock_moira_alert.NewMockDatabase(mockCtrl) + const userLogin = "user" + const adminLogin = "admin" + auth := &api.Authorization{ + Enabled: true, + AdminList: map[string]struct{}{adminLogin: {}}, + } + + Convey("Create for user", t, func() { + Convey("For same user", func() { + subscription := dto.Subscription{ + User: userLogin, + } + dataBase.EXPECT().SaveSubscription(gomock.Any()).Return(nil) + err := CreateSubscription(dataBase, auth, userLogin, "", &subscription) + So(err, ShouldBeNil) + So(subscription.User, ShouldEqual, userLogin) + }) + + Convey("For same admin", func() { + subscription := dto.Subscription{ + User: adminLogin, + } + dataBase.EXPECT().SaveSubscription(gomock.Any()).Return(nil) + err := CreateSubscription(dataBase, auth, adminLogin, "", &subscription) + So(err, ShouldBeNil) + So(subscription.User, ShouldEqual, adminLogin) + }) + + Convey("User can not create subscription for other user", func() { + subscription := dto.Subscription{ + User: adminLogin, + } + dataBase.EXPECT().SaveSubscription(gomock.Any()).Return(nil) + err := CreateSubscription(dataBase, auth, userLogin, "", &subscription) + So(err, ShouldBeNil) + So(subscription.User, ShouldEqual, userLogin) + }) + + Convey("Admin can create subscription for other user", func() { + subscription := dto.Subscription{ + User: userLogin, + } + dataBase.EXPECT().SaveSubscription(gomock.Any()).Return(nil) + err := CreateSubscription(dataBase, auth, adminLogin, "", &subscription) + So(err, ShouldBeNil) + So(subscription.User, ShouldEqual, userLogin) + }) + }) +} + func TestCheckUserPermissionsForSubscription(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() diff --git a/api/dto/subscription.go b/api/dto/subscription.go index a725e3460..38178db3a 100644 --- a/api/dto/subscription.go +++ b/api/dto/subscription.go @@ -70,9 +70,17 @@ func (subscription *Subscription) checkContacts(request *http.Request) error { database := middleware.GetDatabase(request) userLogin := middleware.GetLogin(request) teamID := middleware.GetTeamID(request) + auth := middleware.GetAuth(request) + if teamID == "" && subscription.TeamID != "" { teamID = subscription.TeamID } + + // Only admins are allowed to create subscriptions for other users + if auth.IsAdmin(userLogin) && subscription.User != "" { + userLogin = subscription.User + } + if subscription.User != "" && teamID != "" { return ErrSubscriptionContainsTeamAndUser{} } diff --git a/api/dto/subscription_test.go b/api/dto/subscription_test.go index 69a1e556c..79f1ce30e 100644 --- a/api/dto/subscription_test.go +++ b/api/dto/subscription_test.go @@ -10,6 +10,7 @@ import ( "github.com/golang/mock/gomock" "github.com/moira-alert/moira" + "github.com/moira-alert/moira/api" "github.com/moira-alert/moira/api/middleware" mock "github.com/moira-alert/moira/mock/moira-alert" . "github.com/smartystreets/goconvey/convey" @@ -21,6 +22,8 @@ func TestSubscription_checkContacts(t *testing.T) { defer mockCtrl.Finish() dataBase := mock.NewMockDatabase(mockCtrl) + auth := &api.Authorization{Enabled: false} + subscription := Subscription{} const userID = "userID" const teamID = "teamID" @@ -30,6 +33,7 @@ func TestSubscription_checkContacts(t *testing.T) { Convey("For user", func() { request := httptest.NewRequest(http.MethodPost, "/api/subscriptions", strings.NewReader("")) + request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "auth", auth)) middleware.DatabaseContext(dataBase)(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { request = req })).ServeHTTP(responseWriter, request) @@ -56,6 +60,7 @@ func TestSubscription_checkContacts(t *testing.T) { Convey("For team", func() { request := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/teams/%s/subscriptions", teamID), strings.NewReader("")) + request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "auth", auth)) middleware.DatabaseContext(dataBase)(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { request = req })).ServeHTTP(responseWriter, request) @@ -82,6 +87,7 @@ func TestSubscription_checkContacts(t *testing.T) { Convey("Error bot teamID and userID specified in JSON", func() { request := httptest.NewRequest(http.MethodPost, fmt.Sprintf("/api/teams/%s/subscriptions", teamID), strings.NewReader("")) + request = request.WithContext(middleware.SetContextValueForTest(request.Context(), "auth", auth)) middleware.DatabaseContext(dataBase)(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { request = req })).ServeHTTP(responseWriter, request) diff --git a/api/handler/contact.go b/api/handler/contact.go index 5138c6772..2b41d7ac6 100644 --- a/api/handler/contact.go +++ b/api/handler/contact.go @@ -99,8 +99,9 @@ func createNewContact(writer http.ResponseWriter, request *http.Request) { return } userLogin := middleware.GetLogin(request) + auth := middleware.GetAuth(request) - if err := controller.CreateContact(database, contact, userLogin, contact.TeamID); err != nil { + if err := controller.CreateContact(database, auth, contact, userLogin, contact.TeamID); err != nil { render.Render(writer, request, err) //nolint return } diff --git a/api/handler/contact_test.go b/api/handler/contact_test.go index 969bb5448..2801f9cef 100644 --- a/api/handler/contact_test.go +++ b/api/handler/contact_test.go @@ -195,6 +195,8 @@ func TestCreateNewContact(t *testing.T) { login := defaultLogin testErr := errors.New("test error") + auth := &api.Authorization{Enabled: false} + newContactDto := &dto.Contact{ ID: defaultContact, Name: "Mail Alerts", @@ -221,6 +223,7 @@ func TestCreateNewContact(t *testing.T) { testRequest := httptest.NewRequest(http.MethodPut, "/contact", bytes.NewBuffer(jsonContact)) testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), LoginKey, login)) + testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), "auth", auth)) testRequest.Header.Add("content-type", "application/json") createNewContact(responseWriter, testRequest) @@ -252,6 +255,7 @@ func TestCreateNewContact(t *testing.T) { testRequest := httptest.NewRequest(http.MethodPut, "/contact", bytes.NewBuffer(jsonContact)) testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), LoginKey, login)) + testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), "auth", auth)) testRequest.Header.Add("content-type", "application/json") createNewContact(responseWriter, testRequest) @@ -292,6 +296,7 @@ func TestCreateNewContact(t *testing.T) { testRequest := httptest.NewRequest(http.MethodPut, "/contact", bytes.NewBuffer(jsonContact)) testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), LoginKey, login)) + testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), "auth", auth)) testRequest.Header.Add("content-type", "application/json") createNewContact(responseWriter, testRequest) @@ -322,6 +327,7 @@ func TestCreateNewContact(t *testing.T) { testRequest := httptest.NewRequest(http.MethodPut, "/contact", bytes.NewBuffer(jsonContact)) testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), LoginKey, login)) + testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), "auth", auth)) testRequest.Header.Add("content-type", "application/json") createNewContact(responseWriter, testRequest) @@ -354,6 +360,7 @@ func TestCreateNewContact(t *testing.T) { testRequest := httptest.NewRequest(http.MethodPut, "/contact", bytes.NewBuffer(jsonContact)) testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), LoginKey, login)) + testRequest = testRequest.WithContext(middleware.SetContextValueForTest(testRequest.Context(), "auth", auth)) testRequest.Header.Add("content-type", "application/json") createNewContact(responseWriter, testRequest) diff --git a/api/handler/subscription.go b/api/handler/subscription.go index 8d1b17075..efca95735 100644 --- a/api/handler/subscription.go +++ b/api/handler/subscription.go @@ -71,6 +71,7 @@ func createSubscription(writer http.ResponseWriter, request *http.Request) { return } userLogin := middleware.GetLogin(request) + auth := middleware.GetAuth(request) if subscription.AnyTags && len(subscription.Tags) > 0 { writer.WriteHeader(http.StatusBadRequest) @@ -78,7 +79,7 @@ func createSubscription(writer http.ResponseWriter, request *http.Request) { errors.New("if any_tags is true, then the tags must be empty"))) return } - if err := controller.CreateSubscription(database, userLogin, "", subscription); err != nil { + if err := controller.CreateSubscription(database, auth, userLogin, "", subscription); err != nil { render.Render(writer, request, err) //nolint return } diff --git a/api/handler/team_contact.go b/api/handler/team_contact.go index 1ed5874ab..e7dd5bcd7 100644 --- a/api/handler/team_contact.go +++ b/api/handler/team_contact.go @@ -38,8 +38,9 @@ func createNewTeamContact(writer http.ResponseWriter, request *http.Request) { return } teamID := middleware.GetTeamID(request) + auth := middleware.GetAuth(request) - if err := controller.CreateContact(database, contact, "", teamID); err != nil { + if err := controller.CreateContact(database, auth, contact, "", teamID); err != nil { render.Render(writer, request, err) //nolint:errcheck return } diff --git a/api/handler/team_subscription.go b/api/handler/team_subscription.go index c913fff79..99f6c6666 100644 --- a/api/handler/team_subscription.go +++ b/api/handler/team_subscription.go @@ -39,6 +39,7 @@ func createTeamSubscription(writer http.ResponseWriter, request *http.Request) { return } teamID := middleware.GetTeamID(request) + auth := middleware.GetAuth(request) if subscription.AnyTags && len(subscription.Tags) > 0 { writer.WriteHeader(http.StatusBadRequest) @@ -46,7 +47,7 @@ func createTeamSubscription(writer http.ResponseWriter, request *http.Request) { errors.New("if any_tags is true, then the tags must be empty"))) return } - if err := controller.CreateSubscription(database, "", teamID, subscription); err != nil { + if err := controller.CreateSubscription(database, auth, "", teamID, subscription); err != nil { render.Render(writer, request, err) //nolint:errcheck return } diff --git a/cmd/config.go b/cmd/config.go index 15e5d63fe..0d34d7da0 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -47,20 +47,31 @@ type RedisConfig struct { WriteTimeout string `yaml:"write_timeout"` // MaxRetries count of retries. MaxRetries int `yaml:"max_retries"` + // Enables read-only commands on slave nodes. + ReadOnly bool `yaml:"read_only"` + // Allows routing read-only commands to the **closest** master or slave node. + // It automatically enables ReadOnly. + RouteByLatency bool `yaml:"route_by_latency"` + // Allows routing read-only commands to the **random** master or slave node. + // It automatically enables ReadOnly. + RouteRandomly bool `yaml:"route_randomly"` } // GetSettings returns redis config parsed from moira config files. func (config *RedisConfig) GetSettings() redis.DatabaseConfig { return redis.DatabaseConfig{ - MasterName: config.MasterName, - Addrs: strings.Split(config.Addrs, ","), - Username: config.Username, - Password: config.Password, - MaxRetries: config.MaxRetries, - MetricsTTL: to.Duration(config.MetricsTTL), - DialTimeout: to.Duration(config.DialTimeout), - ReadTimeout: to.Duration(config.ReadTimeout), - WriteTimeout: to.Duration(config.WriteTimeout), + MasterName: config.MasterName, + Addrs: strings.Split(config.Addrs, ","), + Username: config.Username, + Password: config.Password, + MaxRetries: config.MaxRetries, + MetricsTTL: to.Duration(config.MetricsTTL), + DialTimeout: to.Duration(config.DialTimeout), + ReadTimeout: to.Duration(config.ReadTimeout), + WriteTimeout: to.Duration(config.WriteTimeout), + ReadOnly: config.ReadOnly, + RouteByLatency: config.RouteByLatency, + RouteRandomly: config.RouteRandomly, } } diff --git a/database/redis/config.go b/database/redis/config.go index 791363458..7c840c15b 100644 --- a/database/redis/config.go +++ b/database/redis/config.go @@ -15,6 +15,9 @@ type DatabaseConfig struct { ReadTimeout time.Duration WriteTimeout time.Duration MaxRetries int + ReadOnly bool + RouteByLatency bool + RouteRandomly bool } type NotificationHistoryConfig struct { diff --git a/database/redis/database.go b/database/redis/database.go index 22b891ca0..0633b3098 100644 --- a/database/redis/database.go +++ b/database/redis/database.go @@ -62,6 +62,9 @@ func NewDatabase(logger moira.Logger, config DatabaseConfig, nh NotificationHist ReadTimeout: config.ReadTimeout, WriteTimeout: config.WriteTimeout, MaxRetries: config.MaxRetries, + ReadOnly: config.ReadOnly, + RouteByLatency: config.RouteByLatency, + RouteRandomly: config.RouteRandomly, }) ctx := context.Background()