Skip to content

Commit

Permalink
Add admin permissions via list of admins in api (#996)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tetrergeru authored Mar 13, 2024
1 parent 19d0a7b commit da72678
Show file tree
Hide file tree
Showing 20 changed files with 338 additions and 37 deletions.
29 changes: 25 additions & 4 deletions api/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,31 @@ type Sentry struct {

// Config for api configuration variables.
type Config struct {
EnableCORS bool
Listen string
MetricsTTL map[moira.ClusterKey]time.Duration
Flags FeatureFlags
EnableCORS bool
Listen string
MetricsTTL map[moira.ClusterKey]time.Duration
Flags FeatureFlags
Authorization Authorization
}

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

// IsEnabled returns true if auth is enabled and false otherwise.
func (auth *Authorization) IsEnabled() bool {
return auth.Enabled
}

// IsAdmin checks whether given user is considered an administrator.
func (auth *Authorization) IsAdmin(login string) bool {
if !auth.IsEnabled() {
return false
}
_, ok := auth.AdminList[login]
return ok
}

// WebConfig is container for web ui configuration parameters.
Expand Down
10 changes: 9 additions & 1 deletion api/controller/contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,14 +177,22 @@ func SendTestContactNotification(dataBase moira.Database, contactID string) *api
}

// CheckUserPermissionsForContact checks contact for existence and permissions for given user.
func CheckUserPermissionsForContact(dataBase moira.Database, contactID string, userLogin string) (moira.ContactData, *api.ErrorResponse) {
func CheckUserPermissionsForContact(
dataBase moira.Database,
contactID string,
userLogin string,
auth *api.Authorization,
) (moira.ContactData, *api.ErrorResponse) {
contactData, err := dataBase.GetContact(contactID)
if err != nil {
if errors.Is(err, database.ErrNil) {
return moira.ContactData{}, api.ErrorNotFound(fmt.Sprintf("contact with ID '%s' does not exists", contactID))
}
return moira.ContactData{}, api.ErrorInternalServer(err)
}
if auth.IsAdmin(userLogin) {
return contactData, nil
}
if contactData.Team != "" {
teamContainsUser, err := dataBase.IsTeamContainUser(contactData.Team, userLogin)
if err != nil {
Expand Down
50 changes: 43 additions & 7 deletions api/controller/contact_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,33 +497,34 @@ func TestCheckUserPermissionsForContact(t *testing.T) {
userLogin := uuid.Must(uuid.NewV4()).String()
teamID := uuid.Must(uuid.NewV4()).String()
id := uuid.Must(uuid.NewV4()).String()
auth := &api.Authorization{}

Convey("No contact", t, func() {
dataBase.EXPECT().GetContact(id).Return(moira.ContactData{}, database.ErrNil)
expectedContact, expected := CheckUserPermissionsForContact(dataBase, id, userLogin)
expectedContact, expected := CheckUserPermissionsForContact(dataBase, id, userLogin, auth)
So(expected, ShouldResemble, api.ErrorNotFound(fmt.Sprintf("contact with ID '%s' does not exists", id)))
So(expectedContact, ShouldResemble, moira.ContactData{})
})

Convey("Different user", t, func() {
dataBase.EXPECT().GetContact(id).Return(moira.ContactData{User: "diffUser"}, nil)
expectedContact, expected := CheckUserPermissionsForContact(dataBase, id, userLogin)
expectedContact, expected := CheckUserPermissionsForContact(dataBase, id, userLogin, auth)
So(expected, ShouldResemble, api.ErrorForbidden("you are not permitted"))
So(expectedContact, ShouldResemble, moira.ContactData{})
})

Convey("Has contact", t, func() {
actualContact := moira.ContactData{ID: id, User: userLogin}
dataBase.EXPECT().GetContact(id).Return(actualContact, nil)
expectedContact, expected := CheckUserPermissionsForContact(dataBase, id, userLogin)
expectedContact, expected := CheckUserPermissionsForContact(dataBase, id, userLogin, auth)
So(expected, ShouldBeNil)
So(expectedContact, ShouldResemble, actualContact)
})

Convey("Error get contact", t, func() {
err := fmt.Errorf("oooops! Can not read contact")
dataBase.EXPECT().GetContact(id).Return(moira.ContactData{User: userLogin}, err)
expectedContact, expected := CheckUserPermissionsForContact(dataBase, id, userLogin)
expectedContact, expected := CheckUserPermissionsForContact(dataBase, id, userLogin, auth)
So(expected, ShouldResemble, api.ErrorInternalServer(err))
So(expectedContact, ShouldResemble, moira.ContactData{})
})
Expand All @@ -533,28 +534,63 @@ func TestCheckUserPermissionsForContact(t *testing.T) {
expectedSub := moira.ContactData{ID: id, Team: teamID}
dataBase.EXPECT().GetContact(id).Return(expectedSub, nil)
dataBase.EXPECT().IsTeamContainUser(teamID, userLogin).Return(true, nil)
actual, err := CheckUserPermissionsForContact(dataBase, id, userLogin)
actual, err := CheckUserPermissionsForContact(dataBase, id, userLogin, auth)
So(err, ShouldBeNil)
So(actual, ShouldResemble, expectedSub)
})
Convey("User is not in team", func() {
dataBase.EXPECT().GetContact(id).Return(moira.ContactData{ID: id, Team: teamID}, nil)
dataBase.EXPECT().IsTeamContainUser(teamID, userLogin).Return(false, nil)
actual, err := CheckUserPermissionsForContact(dataBase, id, userLogin)
actual, err := CheckUserPermissionsForContact(dataBase, id, userLogin, auth)
So(err, ShouldResemble, api.ErrorForbidden("you are not permitted"))
So(actual, ShouldResemble, moira.ContactData{})
})
Convey("Error while checking user team", func() {
errReturned := errors.New("test error")
dataBase.EXPECT().GetContact(id).Return(moira.ContactData{ID: id, Team: teamID}, nil)
dataBase.EXPECT().IsTeamContainUser(teamID, userLogin).Return(false, errReturned)
actual, err := CheckUserPermissionsForContact(dataBase, id, userLogin)
actual, err := CheckUserPermissionsForContact(dataBase, id, userLogin, auth)
So(err, ShouldResemble, api.ErrorInternalServer(errReturned))
So(actual, ShouldResemble, moira.ContactData{})
})
})
}

func TestCheckAdminPermissionsForContact(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

dataBase := mock_moira_alert.NewMockDatabase(mockCtrl)
teamID := uuid.Must(uuid.NewV4()).String()
id := uuid.Must(uuid.NewV4()).String()
adminLogin := "admin_login"
auth := &api.Authorization{Enabled: true, AdminList: map[string]struct{}{adminLogin: {}}}

Convey("Same user", t, func() {
expectedContact := moira.ContactData{ID: id, User: adminLogin}
dataBase.EXPECT().GetContact(id).Return(expectedContact, nil)
actualContact, errorResponse := CheckUserPermissionsForContact(dataBase, id, adminLogin, auth)
So(errorResponse, ShouldBeNil)
So(actualContact, ShouldResemble, expectedContact)
})

Convey("Different user", t, func() {
expectedContact := moira.ContactData{ID: id, User: "diffUser"}
dataBase.EXPECT().GetContact(id).Return(expectedContact, nil)
actualContact, errorResponse := CheckUserPermissionsForContact(dataBase, id, adminLogin, auth)
So(errorResponse, ShouldBeNil)
So(actualContact, ShouldResemble, expectedContact)
})

Convey("Team contact", t, func() {
expectedContact := moira.ContactData{ID: id, Team: teamID}
dataBase.EXPECT().GetContact(id).Return(expectedContact, nil)
actualContact, errorResponse := CheckUserPermissionsForContact(dataBase, id, adminLogin, auth)
So(errorResponse, ShouldBeNil)
So(actualContact, ShouldResemble, expectedContact)
})
}

func Test_isContactExists(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
Expand Down
10 changes: 9 additions & 1 deletion api/controller/subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,22 @@ func SendTestNotification(database moira.Database, subscriptionID string) *api.E
}

// CheckUserPermissionsForSubscription checks subscription for existence and permissions for given user.
func CheckUserPermissionsForSubscription(dataBase moira.Database, subscriptionID string, userLogin string) (moira.SubscriptionData, *api.ErrorResponse) {
func CheckUserPermissionsForSubscription(
dataBase moira.Database,
subscriptionID string,
userLogin string,
auth *api.Authorization,
) (moira.SubscriptionData, *api.ErrorResponse) {
subscription, err := dataBase.GetSubscription(subscriptionID)
if err != nil {
if errors.Is(err, database.ErrNil) {
return moira.SubscriptionData{}, api.ErrorNotFound(fmt.Sprintf("subscription with ID '%s' does not exists", subscriptionID))
}
return moira.SubscriptionData{}, api.ErrorInternalServer(err)
}
if auth.IsAdmin(userLogin) {
return subscription, nil
}
if subscription.TeamID != "" {
teamContainsUser, err := dataBase.IsTeamContainUser(subscription.TeamID, userLogin)
if err != nil {
Expand Down
50 changes: 43 additions & 7 deletions api/controller/subscription_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,34 +285,35 @@ func TestCheckUserPermissionsForSubscription(t *testing.T) {
userLogin := uuid.Must(uuid.NewV4()).String()
teamID := uuid.Must(uuid.NewV4()).String()
id := uuid.Must(uuid.NewV4()).String()
auth := &api.Authorization{}

Convey("No subscription", t, func() {
dataBase.EXPECT().GetSubscription(id).Return(moira.SubscriptionData{}, database.ErrNil)
expectedSub, expected := CheckUserPermissionsForSubscription(dataBase, id, userLogin)
expectedSub, expected := CheckUserPermissionsForSubscription(dataBase, id, userLogin, auth)
So(expected, ShouldResemble, api.ErrorNotFound(fmt.Sprintf("subscription with ID '%s' does not exists", id)))
So(expectedSub, ShouldResemble, moira.SubscriptionData{})
})

Convey("Different user", t, func() {
actualSub := moira.SubscriptionData{User: "diffUser"}
dataBase.EXPECT().GetSubscription(id).Return(actualSub, nil)
expectedSub, expected := CheckUserPermissionsForSubscription(dataBase, id, userLogin)
expectedSub, expected := CheckUserPermissionsForSubscription(dataBase, id, userLogin, auth)
So(expected, ShouldResemble, api.ErrorForbidden("you are not permitted"))
So(expectedSub, ShouldResemble, moira.SubscriptionData{})
})

Convey("Has subscription", t, func() {
actualSub := moira.SubscriptionData{ID: id, User: userLogin}
dataBase.EXPECT().GetSubscription(id).Return(actualSub, nil)
expectedSub, expected := CheckUserPermissionsForSubscription(dataBase, id, userLogin)
expectedSub, expected := CheckUserPermissionsForSubscription(dataBase, id, userLogin, auth)
So(expected, ShouldBeNil)
So(expectedSub, ShouldResemble, actualSub)
})

Convey("Error get contact", t, func() {
err := fmt.Errorf("oooops! Can not read contact")
dataBase.EXPECT().GetSubscription(id).Return(moira.SubscriptionData{}, err)
expectedSub, expected := CheckUserPermissionsForSubscription(dataBase, id, userLogin)
expectedSub, expected := CheckUserPermissionsForSubscription(dataBase, id, userLogin, auth)
So(expected, ShouldResemble, api.ErrorInternalServer(err))
So(expectedSub, ShouldResemble, moira.SubscriptionData{})
})
Expand All @@ -322,28 +323,63 @@ func TestCheckUserPermissionsForSubscription(t *testing.T) {
expectedSub := moira.SubscriptionData{ID: id, TeamID: teamID}
dataBase.EXPECT().GetSubscription(id).Return(expectedSub, nil)
dataBase.EXPECT().IsTeamContainUser(teamID, userLogin).Return(true, nil)
actual, err := CheckUserPermissionsForSubscription(dataBase, id, userLogin)
actual, err := CheckUserPermissionsForSubscription(dataBase, id, userLogin, auth)
So(err, ShouldBeNil)
So(actual, ShouldResemble, expectedSub)
})
Convey("User is not in team", func() {
dataBase.EXPECT().GetSubscription(id).Return(moira.SubscriptionData{ID: id, TeamID: teamID}, nil)
dataBase.EXPECT().IsTeamContainUser(teamID, userLogin).Return(false, nil)
actual, err := CheckUserPermissionsForSubscription(dataBase, id, userLogin)
actual, err := CheckUserPermissionsForSubscription(dataBase, id, userLogin, auth)
So(err, ShouldResemble, api.ErrorForbidden("you are not permitted"))
So(actual, ShouldResemble, moira.SubscriptionData{})
})
Convey("Error while checking user team", func() {
errReturned := errors.New("test error")
dataBase.EXPECT().GetSubscription(id).Return(moira.SubscriptionData{ID: id, TeamID: teamID}, nil)
dataBase.EXPECT().IsTeamContainUser(teamID, userLogin).Return(false, errReturned)
actual, err := CheckUserPermissionsForSubscription(dataBase, id, userLogin)
actual, err := CheckUserPermissionsForSubscription(dataBase, id, userLogin, auth)
So(err, ShouldResemble, api.ErrorInternalServer(errReturned))
So(actual, ShouldResemble, moira.SubscriptionData{})
})
})
}

func TestCheckAdminPermissionsForSubscription(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()

dataBase := mock_moira_alert.NewMockDatabase(mockCtrl)
teamID := uuid.Must(uuid.NewV4()).String()
id := uuid.Must(uuid.NewV4()).String()
adminLogin := "admin_login"
auth := &api.Authorization{Enabled: true, AdminList: map[string]struct{}{adminLogin: {}}}

Convey("Same user", t, func() {
expectedSub := moira.SubscriptionData{ID: id, User: adminLogin}
dataBase.EXPECT().GetSubscription(id).Return(expectedSub, nil)
actualContact, errorResponse := CheckUserPermissionsForSubscription(dataBase, id, adminLogin, auth)
So(errorResponse, ShouldBeNil)
So(actualContact, ShouldResemble, expectedSub)
})

Convey("Different user", t, func() {
expectedSub := moira.SubscriptionData{ID: id, User: "diffUser"}
dataBase.EXPECT().GetSubscription(id).Return(expectedSub, nil)
actualContact, errorResponse := CheckUserPermissionsForSubscription(dataBase, id, adminLogin, auth)
So(errorResponse, ShouldBeNil)
So(actualContact, ShouldResemble, expectedSub)
})

Convey("Team contact", t, func() {
expectedSub := moira.SubscriptionData{ID: id, TeamID: teamID}
dataBase.EXPECT().GetSubscription(id).Return(expectedSub, nil)
actualContact, errorResponse := CheckUserPermissionsForSubscription(dataBase, id, adminLogin, auth)
So(errorResponse, ShouldBeNil)
So(actualContact, ShouldResemble, expectedSub)
})
}

func Test_isSubscriptionExists(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
Expand Down
10 changes: 9 additions & 1 deletion api/controller/team.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,15 @@ func removeUserTeam(teams []string, teamID string) ([]string, error) {
return []string{}, fmt.Errorf("cannot find team in user teams: %s", teamID)
}

func CheckUserPermissionsForTeam(dataBase moira.Database, teamID, userID string) *api.ErrorResponse {
func CheckUserPermissionsForTeam(
dataBase moira.Database,
teamID, userID string,
auth *api.Authorization,
) *api.ErrorResponse {
if auth.IsAdmin(userID) {
return nil
}

_, err := dataBase.GetTeam(teamID)
if err != nil {
if errors.Is(err, database.ErrNil) {
Expand Down
11 changes: 6 additions & 5 deletions api/controller/team_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,7 @@ func TestSetTeamUsers(t *testing.T) {
func TestCheckUserPermissionsForTeam(t *testing.T) {
const teamID = "testTeam"
const userID = "userID"
auth := &api.Authorization{}

Convey("CheckUserPermissionsForTeam", t, func() {
mockCtrl := gomock.NewController(t)
Expand All @@ -600,31 +601,31 @@ func TestCheckUserPermissionsForTeam(t *testing.T) {
Convey("user in team", func() {
dataBase.EXPECT().GetTeam(teamID).Return(moira.Team{}, nil)
dataBase.EXPECT().IsTeamContainUser(teamID, userID).Return(true, nil)
err := CheckUserPermissionsForTeam(dataBase, teamID, userID)
err := CheckUserPermissionsForTeam(dataBase, teamID, userID, auth)
So(err, ShouldBeNil)
})
Convey("user is not in team", func() {
dataBase.EXPECT().GetTeam(teamID).Return(moira.Team{}, nil)
dataBase.EXPECT().IsTeamContainUser(teamID, userID).Return(false, nil)
err := CheckUserPermissionsForTeam(dataBase, teamID, userID)
err := CheckUserPermissionsForTeam(dataBase, teamID, userID, auth)
So(err, ShouldResemble, api.ErrorForbidden("you are not permitted to manipulate with this team"))
})
Convey("error while checking user", func() {
returnErr := errors.New("returning error")
dataBase.EXPECT().GetTeam(teamID).Return(moira.Team{}, nil)
dataBase.EXPECT().IsTeamContainUser(teamID, userID).Return(false, returnErr)
err := CheckUserPermissionsForTeam(dataBase, teamID, userID)
err := CheckUserPermissionsForTeam(dataBase, teamID, userID, auth)
So(err, ShouldResemble, api.ErrorInternalServer(returnErr))
})
Convey("error while getting team", func() {
returnErr := errors.New("returning error")
dataBase.EXPECT().GetTeam(teamID).Return(moira.Team{}, returnErr)
err := CheckUserPermissionsForTeam(dataBase, teamID, userID)
err := CheckUserPermissionsForTeam(dataBase, teamID, userID, auth)
So(err, ShouldResemble, api.ErrorInternalServer(returnErr))
})
Convey("team is not exist", func() {
dataBase.EXPECT().GetTeam(teamID).Return(moira.Team{}, database.ErrNil)
err := CheckUserPermissionsForTeam(dataBase, teamID, userID)
err := CheckUserPermissionsForTeam(dataBase, teamID, userID, auth)
So(err, ShouldResemble, api.ErrorNotFound("team with ID 'testTeam' does not exists"))
})
})
Expand Down
3 changes: 2 additions & 1 deletion api/handler/contact.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ func contactFilter(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
contactID := middleware.GetContactID(request)
userLogin := middleware.GetLogin(request)
contactData, err := controller.CheckUserPermissionsForContact(database, contactID, userLogin)
auth := middleware.GetAuth(request)
contactData, err := controller.CheckUserPermissionsForContact(database, contactID, userLogin, auth)
if err != nil {
render.Render(writer, request, err) //nolint
return
Expand Down
1 change: 1 addition & 0 deletions api/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ func NewHandler(
// @tag.description APIs for interacting with Moira users
router.Route("/api", func(router chi.Router) {
router.Use(moiramiddle.DatabaseContext(database))
router.Use(moiramiddle.AuthorizationContext(&apiConfig.Authorization))
router.Route("/health", health)
router.Route("/", func(router chi.Router) {
router.Use(moiramiddle.ReadOnlyMiddleware(apiConfig))
Expand Down
Loading

0 comments on commit da72678

Please sign in to comment.