Alerting: Add check for datasource permission in alert rule read API (#47087)
* add check for access to rule's data source in GET APIs * use more general method GetAlertRules instead of GetNamespaceAlertRules. * remove unused GetNamespaceAlertRules. Tests: * create a method to generate permissions for rules * extract method to create RuleSrv * add tests for RouteGetNamespaceRulesConfig
This commit is contained in:
@@ -2,12 +2,14 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -16,6 +18,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
@@ -264,19 +267,6 @@ func TestCalculateChanges(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
createService := func(ac *acMock.Mock, store *store.FakeRuleStore, scheduler schedule.ScheduleService) *RulerSrv {
|
||||
return &RulerSrv{
|
||||
xactManager: store,
|
||||
store: store,
|
||||
DatasourceCache: nil,
|
||||
QuotaService: nil,
|
||||
scheduleService: scheduler,
|
||||
log: log.New("test"),
|
||||
cfg: nil,
|
||||
ac: ac,
|
||||
}
|
||||
}
|
||||
|
||||
getRecordedCommand := func(ruleStore *store.FakeRuleStore) []store.GenericRecordedQuery {
|
||||
results := ruleStore.GetRecordedCommands(func(cmd interface{}) (interface{}, bool) {
|
||||
c, ok := cmd.(store.GenericRecordedQuery)
|
||||
@@ -421,15 +411,7 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
scheduler := &schedule.FakeScheduleService{}
|
||||
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||
|
||||
var permissions []*accesscontrol.Permission
|
||||
for _, rule := range rulesInFolder {
|
||||
for _, query := range rule.Data {
|
||||
permissions = append(permissions, &accesscontrol.Permission{
|
||||
Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(query.DatasourceUID),
|
||||
})
|
||||
}
|
||||
}
|
||||
ac := acMock.New().WithPermissions(permissions)
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(rulesInFolder))
|
||||
request := createRequestContext(orgID, "None", map[string]string{
|
||||
":Namespace": folder.Title,
|
||||
})
|
||||
@@ -454,15 +436,7 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
scheduler := &schedule.FakeScheduleService{}
|
||||
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||
|
||||
var permissions []*accesscontrol.Permission
|
||||
for _, rule := range authorizedRulesInFolder {
|
||||
for _, query := range rule.Data {
|
||||
permissions = append(permissions, &accesscontrol.Permission{
|
||||
Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(query.DatasourceUID),
|
||||
})
|
||||
}
|
||||
}
|
||||
ac := acMock.New().WithPermissions(permissions)
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(authorizedRulesInFolder))
|
||||
request := createRequestContext(orgID, "None", map[string]string{
|
||||
":Namespace": folder.Title,
|
||||
})
|
||||
@@ -489,15 +463,7 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
scheduler := &schedule.FakeScheduleService{}
|
||||
scheduler.On("DeleteAlertRule", mock.Anything)
|
||||
|
||||
var permissions []*accesscontrol.Permission
|
||||
for _, rule := range authorizedRulesInGroup {
|
||||
for _, query := range rule.Data {
|
||||
permissions = append(permissions, &accesscontrol.Permission{
|
||||
Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(query.DatasourceUID),
|
||||
})
|
||||
}
|
||||
}
|
||||
ac := acMock.New().WithPermissions(permissions)
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(authorizedRulesInGroup))
|
||||
request := createRequestContext(orgID, "None", map[string]string{
|
||||
":Namespace": folder.Title,
|
||||
":Groupname": groupName,
|
||||
@@ -510,11 +476,101 @@ func TestRouteDeleteAlertRules(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRouteGetNamespaceRulesConfig(t *testing.T) {
|
||||
t.Run("fine-grained access is enabled", func(t *testing.T) {
|
||||
t.Run("should return rules for which user has access to data source", func(t *testing.T) {
|
||||
orgID := rand.Int63()
|
||||
folder := randFolder()
|
||||
ruleStore := store.NewFakeRuleStore(t)
|
||||
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
|
||||
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
ruleStore.PutRule(context.Background(), models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
|
||||
ac := acMock.New().WithPermissions(createPermissionsForRules(expectedRules))
|
||||
|
||||
response := createService(ac, ruleStore, nil).RouteGetNamespaceRulesConfig(createRequestContext(orgID, "", map[string]string{
|
||||
":Namespace": folder.Title,
|
||||
}))
|
||||
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
result := &apimodels.NamespaceConfigResponse{}
|
||||
require.NoError(t, json.Unmarshal(response.Body(), result))
|
||||
require.NotNil(t, result)
|
||||
for namespace, groups := range *result {
|
||||
require.Equal(t, folder.Title, namespace)
|
||||
for _, group := range groups {
|
||||
grouploop:
|
||||
for _, actualRule := range group.Rules {
|
||||
for i, expected := range expectedRules {
|
||||
if actualRule.GrafanaManagedAlert.UID == expected.UID {
|
||||
expectedRules = append(expectedRules[:i], expectedRules[i+1:]...)
|
||||
continue grouploop
|
||||
}
|
||||
}
|
||||
assert.Failf(t, "rule in a group was not found in expected", "rule %s group %s", actualRule.GrafanaManagedAlert.Title, group.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.Emptyf(t, expectedRules, "not all expected rules were returned")
|
||||
})
|
||||
})
|
||||
t.Run("fine-grained access is disabled", func(t *testing.T) {
|
||||
t.Run("should return all rules from folder", func(t *testing.T) {
|
||||
orgID := rand.Int63()
|
||||
folder := randFolder()
|
||||
ruleStore := store.NewFakeRuleStore(t)
|
||||
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
|
||||
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
|
||||
ruleStore.PutRule(context.Background(), expectedRules...)
|
||||
ac := acMock.New().WithDisabled()
|
||||
|
||||
response := createService(ac, ruleStore, nil).RouteGetNamespaceRulesConfig(createRequestContext(orgID, "", map[string]string{
|
||||
":Namespace": folder.Title,
|
||||
}))
|
||||
|
||||
require.Equal(t, http.StatusAccepted, response.Status())
|
||||
result := &apimodels.NamespaceConfigResponse{}
|
||||
require.NoError(t, json.Unmarshal(response.Body(), result))
|
||||
require.NotNil(t, result)
|
||||
for namespace, groups := range *result {
|
||||
require.Equal(t, folder.Title, namespace)
|
||||
for _, group := range groups {
|
||||
grouploop:
|
||||
for _, actualRule := range group.Rules {
|
||||
for i, expected := range expectedRules {
|
||||
if actualRule.GrafanaManagedAlert.UID == expected.UID {
|
||||
expectedRules = append(expectedRules[:i], expectedRules[i+1:]...)
|
||||
continue grouploop
|
||||
}
|
||||
}
|
||||
assert.Failf(t, "rule in a group was not found in expected", "rule %s group %s", actualRule.GrafanaManagedAlert.Title, group.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.Emptyf(t, expectedRules, "not all expected rules were returned")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func createService(ac *acMock.Mock, store *store.FakeRuleStore, scheduler schedule.ScheduleService) *RulerSrv {
|
||||
return &RulerSrv{
|
||||
xactManager: store,
|
||||
store: store,
|
||||
DatasourceCache: nil,
|
||||
QuotaService: nil,
|
||||
scheduleService: scheduler,
|
||||
log: log.New("test"),
|
||||
cfg: nil,
|
||||
ac: ac,
|
||||
}
|
||||
}
|
||||
|
||||
func createRequestContext(orgID int64, role models2.RoleType, params map[string]string) *models2.ReqContext {
|
||||
ctx := web.Context{Req: &http.Request{}}
|
||||
ctx.Req = web.SetURLParams(ctx.Req, params)
|
||||
|
||||
return &models2.ReqContext{
|
||||
IsSignedIn: true,
|
||||
SignedInUser: &models2.SignedInUser{
|
||||
OrgRole: role,
|
||||
OrgId: orgID,
|
||||
@@ -523,6 +579,18 @@ func createRequestContext(orgID int64, role models2.RoleType, params map[string]
|
||||
}
|
||||
}
|
||||
|
||||
func createPermissionsForRules(rules []*models.AlertRule) []*accesscontrol.Permission {
|
||||
var permissions []*accesscontrol.Permission
|
||||
for _, rule := range rules {
|
||||
for _, query := range rule.Data {
|
||||
permissions = append(permissions, &accesscontrol.Permission{
|
||||
Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(query.DatasourceUID),
|
||||
})
|
||||
}
|
||||
}
|
||||
return permissions
|
||||
}
|
||||
|
||||
func withOrgID(orgId int64) func(rule *models.AlertRule) {
|
||||
return func(rule *models.AlertRule) {
|
||||
rule.OrgID = orgId
|
||||
|
||||
Reference in New Issue
Block a user