RBAC: Remove legacy AC from HasAccess permission check (#68995)

* remove unused HasAdmin and HasEdit permission methods

* remove legacy AC from HasAccess method

* remove unused function

* update alerting tests to work with RBAC
This commit is contained in:
Ieva
2023-05-30 14:39:09 +01:00
committed by GitHub
parent 82f353c696
commit d98813796c
16 changed files with 230 additions and 507 deletions

View File

@@ -68,7 +68,7 @@ func (srv AlertmanagerSrv) RouteCreateSilence(c *contextmodel.ReqContext, postab
if postableSilence.ID == "" {
action = accesscontrol.ActionAlertingInstanceCreate
}
if !accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqOrgAdminOrEditor, accesscontrol.EvalPermission(action)) {
if !accesscontrol.HasAccess(srv.ac, c)(accesscontrol.EvalPermission(action)) {
errAction := "update"
if postableSilence.ID == "" {
errAction = "create"

View File

@@ -17,7 +17,7 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol"
acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
@@ -163,7 +163,7 @@ func TestStatusForTestReceivers(t *testing.T) {
}
func TestAlertmanagerConfig(t *testing.T) {
sut := createSut(t, nil)
sut := createSut(t)
t.Run("assert 404 Not Found when applying config to nonexistent org", func(t *testing.T) {
rc := contextmodel.ReqContext{
@@ -199,7 +199,7 @@ func TestAlertmanagerConfig(t *testing.T) {
})
t.Run("assert 202 when alertmanager to configure is not ready", func(t *testing.T) {
sut := createSut(t, nil)
sut := createSut(t)
rc := contextmodel.ReqContext{
Context: &web.Context{
Req: &http.Request{},
@@ -217,7 +217,7 @@ func TestAlertmanagerConfig(t *testing.T) {
t.Run("when objects are not provisioned", func(t *testing.T) {
t.Run("route from GET config has no provenance", func(t *testing.T) {
sut := createSut(t, nil)
sut := createSut(t)
rc := createRequestCtxInOrg(1)
response := sut.RouteGetAlertingConfig(rc)
@@ -226,7 +226,7 @@ func TestAlertmanagerConfig(t *testing.T) {
require.Equal(t, apimodels.Provenance(ngmodels.ProvenanceNone), body.AlertmanagerConfig.Route.Provenance)
})
t.Run("contact point from GET config has no provenance", func(t *testing.T) {
sut := createSut(t, nil)
sut := createSut(t)
rc := createRequestCtxInOrg(1)
response := sut.RouteGetAlertingConfig(rc)
@@ -235,7 +235,7 @@ func TestAlertmanagerConfig(t *testing.T) {
require.Equal(t, apimodels.Provenance(ngmodels.ProvenanceNone), body.AlertmanagerConfig.Receivers[0].GrafanaManagedReceivers[0].Provenance)
})
t.Run("templates from GET config have no provenance", func(t *testing.T) {
sut := createSut(t, nil)
sut := createSut(t)
rc := createRequestCtxInOrg(1)
response := sut.RouteGetAlertingConfig(rc)
@@ -247,7 +247,7 @@ func TestAlertmanagerConfig(t *testing.T) {
t.Run("when objects are provisioned", func(t *testing.T) {
t.Run("route from GET config has expected provenance", func(t *testing.T) {
sut := createSut(t, nil)
sut := createSut(t)
rc := createRequestCtxInOrg(1)
setRouteProvenance(t, 1, sut.mam.ProvStore)
@@ -257,7 +257,7 @@ func TestAlertmanagerConfig(t *testing.T) {
require.Equal(t, apimodels.Provenance(ngmodels.ProvenanceAPI), body.AlertmanagerConfig.Route.Provenance)
})
t.Run("contact point from GET config has expected provenance", func(t *testing.T) {
sut := createSut(t, nil)
sut := createSut(t)
rc := createRequestCtxInOrg(1)
request := createAmConfigRequest(t)
@@ -277,7 +277,7 @@ func TestAlertmanagerConfig(t *testing.T) {
require.Equal(t, apimodels.Provenance(ngmodels.ProvenanceAPI), body.AlertmanagerConfig.Receivers[0].GrafanaManagedReceivers[0].Provenance)
})
t.Run("templates from GET config have expected provenance", func(t *testing.T) {
sut := createSut(t, nil)
sut := createSut(t)
rc := createRequestCtxInOrg(1)
setTemplateProvenance(t, 1, "a", sut.mam.ProvStore)
@@ -292,7 +292,7 @@ func TestAlertmanagerConfig(t *testing.T) {
}
func TestRouteGetAlertingConfigHistory(t *testing.T) {
sut := createSut(t, nil)
sut := createSut(t)
t.Run("assert 200 and empty slice when no applied configurations are found", func(tt *testing.T) {
req, err := http.NewRequest(http.MethodGet, "https://grafana.net", nil)
@@ -382,7 +382,7 @@ func TestRouteGetAlertingConfigHistory(t *testing.T) {
}
func TestRoutePostGrafanaAlertingConfigHistoryActivate(t *testing.T) {
sut := createSut(t, nil)
sut := createSut(t)
t.Run("assert 404 when no historical configurations are found", func(tt *testing.T) {
req, err := http.NewRequest(http.MethodGet, "https://grafana.net", nil)
@@ -422,7 +422,7 @@ func TestRoutePostGrafanaAlertingConfigHistoryActivate(t *testing.T) {
}
func TestRoutePostTestTemplates(t *testing.T) {
sut := createSut(t, nil)
sut := createSut(t)
t.Run("assert 404 when no alertmanager found", func(tt *testing.T) {
req, err := http.NewRequest(http.MethodGet, "https://grafana.net", nil)
@@ -507,10 +507,13 @@ func TestSilenceCreate(t *testing.T) {
SignedInUser: &user.SignedInUser{
OrgRole: org.RoleEditor,
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionAlertingInstanceCreate: {}},
},
},
}
srv := createSut(t, nil)
srv := createSut(t)
resp := srv.RouteCreateSilence(&rc, amv2.PostableSilence{
ID: "",
@@ -525,114 +528,54 @@ func TestRouteCreateSilence(t *testing.T) {
tesCases := []struct {
name string
silence func() apimodels.PostableSilence
accessControl func() accesscontrol.AccessControl
role org.RoleType
permissions map[int64]map[string][]string
expectedStatus int
}{
{
name: "new silence, role-based access control is enabled, not authorized",
silence: silenceGen(withEmptyID),
accessControl: func() accesscontrol.AccessControl {
return acMock.New()
permissions: map[int64]map[string][]string{
1: {},
},
expectedStatus: http.StatusUnauthorized,
},
{
name: "new silence, role-based access control is enabled, authorized",
silence: silenceGen(withEmptyID),
accessControl: func() accesscontrol.AccessControl {
return acMock.New().WithPermissions([]accesscontrol.Permission{
{Action: accesscontrol.ActionAlertingInstanceCreate},
})
permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionAlertingInstanceCreate: {}},
},
expectedStatus: http.StatusAccepted,
},
{
name: "new silence, role-based access control is disabled, Viewer",
silence: silenceGen(withEmptyID),
accessControl: func() accesscontrol.AccessControl {
return acMock.New().WithDisabled()
},
role: org.RoleViewer,
expectedStatus: http.StatusUnauthorized,
},
{
name: "new silence, role-based access control is disabled, Editor",
silence: silenceGen(withEmptyID),
accessControl: func() accesscontrol.AccessControl {
return acMock.New().WithDisabled()
},
role: org.RoleEditor,
expectedStatus: http.StatusAccepted,
},
{
name: "new silence, role-based access control is disabled, Admin",
silence: silenceGen(withEmptyID),
accessControl: func() accesscontrol.AccessControl {
return acMock.New().WithDisabled()
},
role: org.RoleAdmin,
expectedStatus: http.StatusAccepted,
},
{
name: "update silence, role-based access control is enabled, not authorized",
silence: silenceGen(),
accessControl: func() accesscontrol.AccessControl {
return acMock.New()
permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionAlertingInstanceCreate: {}},
},
expectedStatus: http.StatusUnauthorized,
},
{
name: "update silence, role-based access control is enabled, authorized",
silence: silenceGen(),
accessControl: func() accesscontrol.AccessControl {
return acMock.New().WithPermissions([]accesscontrol.Permission{
{Action: accesscontrol.ActionAlertingInstanceUpdate},
})
permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionAlertingInstanceUpdate: {}},
},
expectedStatus: http.StatusAccepted,
},
{
name: "update silence, role-based access control is disabled, Viewer",
silence: silenceGen(),
accessControl: func() accesscontrol.AccessControl {
return acMock.New().WithDisabled()
},
role: org.RoleViewer,
expectedStatus: http.StatusUnauthorized,
},
{
name: "update silence, role-based access control is disabled, Editor",
silence: silenceGen(),
accessControl: func() accesscontrol.AccessControl {
return acMock.New().WithDisabled()
},
role: org.RoleEditor,
expectedStatus: http.StatusAccepted,
},
{
name: "update silence, role-based access control is disabled, Admin",
silence: silenceGen(),
accessControl: func() accesscontrol.AccessControl {
return acMock.New().WithDisabled()
},
role: org.RoleAdmin,
expectedStatus: http.StatusAccepted,
},
}
for _, tesCase := range tesCases {
t.Run(tesCase.name, func(t *testing.T) {
ac := tesCase.accessControl()
sut := createSut(t, ac)
sut := createSut(t)
rc := contextmodel.ReqContext{
Context: &web.Context{
Req: &http.Request{},
},
SignedInUser: &user.SignedInUser{
OrgRole: tesCase.role,
OrgID: 1,
Permissions: tesCase.permissions,
OrgID: 1,
},
}
@@ -653,18 +596,15 @@ func TestRouteCreateSilence(t *testing.T) {
}
}
func createSut(t *testing.T, accessControl accesscontrol.AccessControl) AlertmanagerSrv {
func createSut(t *testing.T) AlertmanagerSrv {
t.Helper()
mam := createMultiOrgAlertmanager(t)
if accessControl == nil {
accessControl = acMock.New().WithDisabled()
}
log := log.NewNopLogger()
return AlertmanagerSrv{
mam: mam,
crypto: mam.Crypto,
ac: accessControl,
ac: acimpl.ProvideAccessControl(setting.NewCfg()),
log: log,
}
}

View File

@@ -214,7 +214,7 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon
return response.JSON(http.StatusInternalServerError, ruleResponse)
}
hasAccess := func(evaluator accesscontrol.Evaluator) bool {
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqViewer, evaluator)
return accesscontrol.HasAccess(srv.ac, c)(evaluator)
}
// Group rules together by Namespace and Rule Group. Rules are also grouped by Org ID,

View File

@@ -10,24 +10,25 @@ import (
"testing"
"time"
alertingModels "github.com/grafana/alerting/models"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
alertingModels "github.com/grafana/alerting/models"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/folder"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/state"
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web"
)
@@ -94,7 +95,7 @@ func TestRouteGetAlertStatuses(t *testing.T) {
orgID := int64(1)
t.Run("with no alerts", func(t *testing.T) {
_, _, _, api := setupAPI(t)
_, _, api := setupAPI(t)
req, err := http.NewRequest("GET", "/api/v1/alerts", nil)
require.NoError(t, err)
c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID}}
@@ -112,7 +113,7 @@ func TestRouteGetAlertStatuses(t *testing.T) {
})
t.Run("with two alerts", func(t *testing.T) {
_, fakeAIM, _, api := setupAPI(t)
_, fakeAIM, api := setupAPI(t)
fakeAIM.GenerateAlertInstances(1, util.GenerateShortUID(), 2)
req, err := http.NewRequest("GET", "/api/v1/alerts", nil)
require.NoError(t, err)
@@ -154,7 +155,7 @@ func TestRouteGetAlertStatuses(t *testing.T) {
})
t.Run("with two firing alerts", func(t *testing.T) {
_, fakeAIM, _, api := setupAPI(t)
_, fakeAIM, api := setupAPI(t)
fakeAIM.GenerateAlertInstances(1, util.GenerateShortUID(), 2, withAlertingState())
req, err := http.NewRequest("GET", "/api/v1/alerts", nil)
require.NoError(t, err)
@@ -196,7 +197,7 @@ func TestRouteGetAlertStatuses(t *testing.T) {
})
t.Run("with the inclusion of internal labels", func(t *testing.T) {
_, fakeAIM, _, api := setupAPI(t)
_, fakeAIM, api := setupAPI(t)
fakeAIM.GenerateAlertInstances(orgID, util.GenerateShortUID(), 2)
req, err := http.NewRequest("GET", "/api/v1/alerts?includeInternalLabels=true", nil)
require.NoError(t, err)
@@ -283,13 +284,14 @@ func withLabels(labels data.Labels) forEachState {
func TestRouteGetRuleStatuses(t *testing.T) {
timeNow = func() time.Time { return time.Date(2022, 3, 10, 14, 0, 0, 0, time.UTC) }
orgID := int64(1)
queryPermissions := map[int64]map[string][]string{1: {datasources.ActionQuery: {datasources.ScopeAll}}}
req, err := http.NewRequest("GET", "/api/v1/rules", nil)
require.NoError(t, err)
c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer}}
c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: queryPermissions}}
t.Run("with no rules", func(t *testing.T) {
_, _, _, api := setupAPI(t)
_, _, api := setupAPI(t)
r := api.RouteGetRuleStatuses(c)
require.JSONEq(t, `
@@ -303,7 +305,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
})
t.Run("with a rule that only has one query", func(t *testing.T) {
fakeStore, fakeAIM, _, api := setupAPI(t)
fakeStore, fakeAIM, api := setupAPI(t)
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery())
folder := fakeStore.Folders[orgID][0]
@@ -362,13 +364,13 @@ func TestRouteGetRuleStatuses(t *testing.T) {
})
t.Run("with the inclusion of internal Labels", func(t *testing.T) {
fakeStore, fakeAIM, _, api := setupAPI(t)
fakeStore, fakeAIM, api := setupAPI(t)
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withClassicConditionSingleQuery())
folder := fakeStore.Folders[orgID][0]
req, err := http.NewRequest("GET", "/api/v1/rules?includeInternalLabels=true", nil)
require.NoError(t, err)
c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer}}
c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: queryPermissions}}
r := api.RouteGetRuleStatuses(c)
require.Equal(t, http.StatusOK, r.Status())
@@ -428,7 +430,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
})
t.Run("with a rule that has multiple queries", func(t *testing.T) {
fakeStore, fakeAIM, _, api := setupAPI(t)
fakeStore, fakeAIM, api := setupAPI(t)
generateRuleAndInstanceWithQuery(t, orgID, fakeAIM, fakeStore, withExpressionsMultiQuery())
folder := fakeStore.Folders[orgID][0]
@@ -538,15 +540,16 @@ func TestRouteGetRuleStatuses(t *testing.T) {
ruleStore.PutRule(context.Background(), rules...)
ruleStore.PutRule(context.Background(), ngmodels.GenerateAlertRules(rand.Intn(4)+2, ngmodels.AlertRuleGen(withOrgID(orgID)))...)
acMock := acmock.New().WithPermissions(createPermissionsForRules(rules))
api := PrometheusSrv{
log: log.NewNopLogger(),
manager: fakeAIM,
store: ruleStore,
ac: acMock,
ac: acimpl.ProvideAccessControl(setting.NewCfg()),
}
c := &contextmodel.ReqContext{Context: &web.Context{Req: req}, SignedInUser: &user.SignedInUser{OrgID: orgID, Permissions: createPermissionsForRules(rules, orgID)}}
//c.SignedInUser.Permissions[1] = createPermissionsForRules(rules)
response := api.RouteGetRuleStatuses(c)
require.Equal(t, http.StatusOK, response.Status())
result := &apimodels.RuleResponse{}
@@ -568,7 +571,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
})
t.Run("test totals are expected", func(t *testing.T) {
fakeStore, fakeAIM, _, api := setupAPI(t)
fakeStore, fakeAIM, api := setupAPI(t)
// Create rules in the same Rule Group to keep assertions simple
rules := ngmodels.GenerateAlertRules(3, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"), withNamespace(&folder.Folder{
Title: "Folder-1",
@@ -593,8 +596,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -628,7 +631,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
})
t.Run("test time of first firing alert", func(t *testing.T) {
fakeStore, fakeAIM, _, api := setupAPI(t)
fakeStore, fakeAIM, api := setupAPI(t)
// Create rules in the same Rule Group to keep assertions simple
rules := ngmodels.GenerateAlertRules(1, ngmodels.AlertRuleGen(withOrgID(orgID)))
fakeStore.PutRule(context.Background(), rules...)
@@ -639,8 +642,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -684,7 +687,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
})
t.Run("test with limit on Rule Groups", func(t *testing.T) {
fakeStore, _, _, api := setupAPI(t)
fakeStore, _, api := setupAPI(t)
rules := ngmodels.GenerateAlertRules(2, ngmodels.AlertRuleGen(withOrgID(orgID)))
fakeStore.PutRule(context.Background(), rules...)
@@ -695,8 +698,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -720,8 +723,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -744,8 +747,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -757,7 +760,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
})
t.Run("test with limit rules", func(t *testing.T) {
fakeStore, _, _, api := setupAPI(t)
fakeStore, _, api := setupAPI(t)
rules := ngmodels.GenerateAlertRules(2, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1")))
fakeStore.PutRule(context.Background(), rules...)
@@ -767,8 +770,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -792,8 +795,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -816,8 +819,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -830,7 +833,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
})
t.Run("test with limit alerts", func(t *testing.T) {
fakeStore, fakeAIM, _, api := setupAPI(t)
fakeStore, fakeAIM, api := setupAPI(t)
rules := ngmodels.GenerateAlertRules(2, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1")))
fakeStore.PutRule(context.Background(), rules...)
// create a normal and firing alert for each rule
@@ -845,8 +848,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -873,8 +876,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -903,8 +906,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -918,7 +921,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
})
t.Run("test with filters on state", func(t *testing.T) {
fakeStore, fakeAIM, _, api := setupAPI(t)
fakeStore, fakeAIM, api := setupAPI(t)
// create two rules in the same Rule Group to keep assertions simple
rules := ngmodels.GenerateAlertRules(3, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"), withNamespace(&folder.Folder{
Title: "Folder-1",
@@ -944,8 +947,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -961,8 +964,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -997,8 +1000,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -1036,8 +1039,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -1075,7 +1078,7 @@ func TestRouteGetRuleStatuses(t *testing.T) {
})
t.Run("test with matcher on labels", func(t *testing.T) {
fakeStore, fakeAIM, _, api := setupAPI(t)
fakeStore, fakeAIM, api := setupAPI(t)
// create two rules in the same Rule Group to keep assertions simple
rules := ngmodels.GenerateAlertRules(1, ngmodels.AlertRuleGen(withOrgID(orgID), withGroup("Rule-Group-1"), withNamespace(&folder.Folder{
Title: "Folder-1",
@@ -1094,8 +1097,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -1111,8 +1114,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -1132,8 +1135,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -1158,8 +1161,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -1180,8 +1183,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -1202,8 +1205,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -1224,8 +1227,8 @@ func TestRouteGetRuleStatuses(t *testing.T) {
c := &contextmodel.ReqContext{
Context: &web.Context{Req: r},
SignedInUser: &user.SignedInUser{
OrgID: orgID,
OrgRole: org.RoleViewer,
OrgID: orgID,
Permissions: queryPermissions,
},
}
resp := api.RouteGetRuleStatuses(c)
@@ -1246,19 +1249,18 @@ func TestRouteGetRuleStatuses(t *testing.T) {
})
}
func setupAPI(t *testing.T) (*fakes.RuleStore, *fakeAlertInstanceManager, *acmock.Mock, PrometheusSrv) {
func setupAPI(t *testing.T) (*fakes.RuleStore, *fakeAlertInstanceManager, PrometheusSrv) {
fakeStore := fakes.NewRuleStore(t)
fakeAIM := NewFakeAlertInstanceManager(t)
acMock := acmock.New().WithDisabled()
api := PrometheusSrv{
log: log.NewNopLogger(),
manager: fakeAIM,
store: fakeStore,
ac: acMock,
ac: acimpl.ProvideAccessControl(setting.NewCfg()),
}
return fakeStore, fakeAIM, acMock, api
return fakeStore, fakeAIM, api
}
func generateRuleAndInstanceWithQuery(t *testing.T, orgID int64, fakeAIM *fakeAlertInstanceManager, fakeStore *fakes.RuleStore, query func(r *ngmodels.AlertRule)) {

View File

@@ -67,7 +67,7 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceT
logger := srv.log.New(loggerCtx...)
hasAccess := func(evaluator accesscontrol.Evaluator) bool {
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqOrgAdminOrEditor, evaluator)
return accesscontrol.HasAccess(srv.ac, c)(evaluator)
}
provenances, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgID, (&ngmodels.AlertRule{}).ResourceType())
@@ -165,7 +165,7 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *contextmodel.ReqContext, nam
result := apimodels.NamespaceConfigResponse{}
hasAccess := func(evaluator accesscontrol.Evaluator) bool {
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqViewer, evaluator)
return accesscontrol.HasAccess(srv.ac, c)(evaluator)
}
provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgID, (&ngmodels.AlertRule{}).ResourceType())
@@ -207,7 +207,7 @@ func (srv RulerSrv) RouteGetRulesGroupConfig(c *contextmodel.ReqContext, namespa
}
hasAccess := func(evaluator accesscontrol.Evaluator) bool {
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqViewer, evaluator)
return accesscontrol.HasAccess(srv.ac, c)(evaluator)
}
provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgID, (&ngmodels.AlertRule{}).ResourceType())
@@ -265,7 +265,7 @@ func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Res
}
hasAccess := func(evaluator accesscontrol.Evaluator) bool {
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqViewer, evaluator)
return accesscontrol.HasAccess(srv.ac, c)(evaluator)
}
provenanceRecords, err := srv.provenanceStore.GetProvenances(c.Req.Context(), c.SignedInUser.OrgID, (&ngmodels.AlertRule{}).ResourceType())
@@ -339,7 +339,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey
// if RBAC is disabled the permission are limited to folder access that is done upstream
if !srv.ac.IsDisabled() {
err = authorizeRuleChanges(groupChanges, func(evaluator accesscontrol.Evaluator) bool {
return hasAccess(accesscontrol.ReqOrgAdminOrEditor, evaluator)
return hasAccess(evaluator)
})
if err != nil {
return err

View File

@@ -14,8 +14,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol"
acMock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/folder"
@@ -24,8 +23,8 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web"
)
@@ -68,67 +67,15 @@ func TestRouteDeleteAlertRules(t *testing.T) {
return ruleStore
}
t.Run("when fine-grained access is disabled", func(t *testing.T) {
ac := acMock.New().WithDisabled()
t.Run("viewer should not be authorized", func(t *testing.T) {
ruleStore := initFakeRuleStore(t)
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
request := createRequestContext(orgID, org.RoleViewer, nil)
response := createService(ac, ruleStore).RouteDeleteAlertRules(request, folder.Title, "")
require.Equalf(t, 401, response.Status(), "Expected 401 but got %d: %v", response.Status(), string(response.Body()))
require.Empty(t, getRecordedCommand(ruleStore))
})
t.Run("editor should be able to delete all non-provisioned rules in folder", func(t *testing.T) {
ruleStore := initFakeRuleStore(t)
rulesInFolder := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))
ruleStore.PutRule(context.Background(), rulesInFolder...)
request := createRequestContext(orgID, org.RoleEditor, nil)
response := createService(ac, ruleStore).RouteDeleteAlertRules(request, folder.Title, "")
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
})
t.Run("editor should be able to delete rules group if it is not provisioned", func(t *testing.T) {
groupName := util.GenerateShortUID()
rulesInFolderInGroup := models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))
ruleStore := initFakeRuleStore(t)
ruleStore.PutRule(context.Background(), rulesInFolderInGroup...)
// rules in different groups but in the same namespace
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
// rules in the same group but different folder
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withGroup(groupName)))...)
request := createRequestContext(orgID, org.RoleEditor, nil)
response := createService(ac, ruleStore).RouteDeleteAlertRules(request, folder.Title, groupName)
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
assertRulesDeleted(t, rulesInFolderInGroup, ruleStore)
})
t.Run("should return 202 if folder is empty", func(t *testing.T) {
ruleStore := initFakeRuleStore(t)
requestCtx := createRequestContext(orgID, org.RoleEditor, nil)
response := createService(ac, ruleStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
require.Empty(t, getRecordedCommand(ruleStore))
})
})
t.Run("when fine-grained access is enabled", func(t *testing.T) {
requestCtx := createRequestContext(orgID, "None", nil)
t.Run("and group argument is empty", func(t *testing.T) {
t.Run("return 401 if user is not authorized to access any group in the folder", func(t *testing.T) {
ruleStore := initFakeRuleStore(t)
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder)))...)
ac := acMock.New()
request := createRequestContext(orgID, "None", nil)
request := createRequestContextWithPerms(orgID, map[int64]map[string][]string{}, nil)
response := createService(ac, ruleStore).RouteDeleteAlertRules(request, folder.Title, "")
response := createService(ruleStore).RouteDeleteAlertRules(request, folder.Title, "")
require.Equalf(t, 401, response.Status(), "Expected 401 but got %d: %v", response.Status(), string(response.Body()))
require.Empty(t, getRecordedCommand(ruleStore))
@@ -148,9 +95,10 @@ func TestRouteDeleteAlertRules(t *testing.T) {
// more rules in the same namespace but user does not have access to them
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup("unauthz"+util.GenerateShortUID())))...)
ac := acMock.New().WithPermissions(createPermissionsForRules(append(authorizedRulesInFolder, provisionedRulesInFolder...)))
permissions := createPermissionsForRules(append(authorizedRulesInFolder, provisionedRulesInFolder...), orgID)
requestCtx := createRequestContextWithPerms(orgID, permissions, nil)
response := createServiceWithProvenanceStore(ac, ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
assertRulesDeleted(t, authorizedRulesInFolder, ruleStore)
@@ -167,13 +115,23 @@ func TestRouteDeleteAlertRules(t *testing.T) {
// more rules in the same namespace but user does not have access to them
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(util.GenerateShortUID())))...)
ac := acMock.New().WithPermissions(createPermissionsForRules(provisionedRulesInFolder))
permissions := createPermissionsForRules(provisionedRulesInFolder, orgID)
requestCtx := createRequestContextWithPerms(orgID, permissions, nil)
response := createServiceWithProvenanceStore(ac, ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
require.Equalf(t, 400, response.Status(), "Expected 400 but got %d: %v", response.Status(), string(response.Body()))
require.Empty(t, getRecordedCommand(ruleStore))
})
t.Run("should return 202 if folder is empty", func(t *testing.T) {
ruleStore := initFakeRuleStore(t)
requestCtx := createRequestContext(orgID, nil)
response := createService(ruleStore).RouteDeleteAlertRules(requestCtx, folder.Title, "")
require.Equalf(t, 202, response.Status(), "Expected 202 but got %d: %v", response.Status(), string(response.Body()))
require.Empty(t, getRecordedCommand(ruleStore))
})
})
t.Run("and group argument is not empty", func(t *testing.T) {
groupName := util.GenerateShortUID()
@@ -185,9 +143,10 @@ func TestRouteDeleteAlertRules(t *testing.T) {
// more rules in the same group but user is not authorized to access them
ruleStore.PutRule(context.Background(), models.GenerateAlertRulesSmallNonEmpty(models.AlertRuleGen(withOrgID(orgID), withNamespace(folder), withGroup(groupName)))...)
ac := acMock.New().WithPermissions(createPermissionsForRules(authorizedRulesInGroup))
permissions := createPermissionsForRules(authorizedRulesInGroup, orgID)
requestCtx := createRequestContextWithPerms(orgID, permissions, nil)
response := createService(ac, ruleStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName)
response := createService(ruleStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName)
require.Equalf(t, 401, response.Status(), "Expected 401 but got %d: %v", response.Status(), string(response.Body()))
deleteCommands := getRecordedCommand(ruleStore)
@@ -203,9 +162,10 @@ func TestRouteDeleteAlertRules(t *testing.T) {
ruleStore.PutRule(context.Background(), provisionedRulesInFolder...)
ac := acMock.New().WithPermissions(createPermissionsForRules(provisionedRulesInFolder))
permissions := createPermissionsForRules(provisionedRulesInFolder, orgID)
requestCtx := createRequestContextWithPerms(orgID, permissions, nil)
response := createServiceWithProvenanceStore(ac, ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName)
response := createServiceWithProvenanceStore(ruleStore, provisioningStore).RouteDeleteAlertRules(requestCtx, folder.Title, groupName)
require.Equalf(t, 400, response.Status(), "Expected 400 but got %d: %v", response.Status(), string(response.Body()))
deleteCommands := getRecordedCommand(ruleStore)
@@ -225,45 +185,11 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
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))
req := createRequestContext(orgID, "", nil)
response := createService(ac, ruleStore).RouteGetNamespaceRulesConfig(req, folder.Title)
permissions := createPermissionsForRules(expectedRules, orgID)
req := createRequestContextWithPerms(orgID, permissions, nil)
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 := fakes.NewRuleStore(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()
req := createRequestContext(orgID, org.RoleViewer, nil)
response := createService(ac, ruleStore).RouteGetNamespaceRulesConfig(req, folder.Title)
response := createService(ruleStore).RouteGetNamespaceRulesConfig(req, folder.Title)
require.Equal(t, http.StatusAccepted, response.Status())
result := &apimodels.NamespaceConfigResponse{}
@@ -294,9 +220,8 @@ func TestRouteGetNamespaceRulesConfig(t *testing.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()
svc := createService(ac, ruleStore)
svc := createService(ruleStore)
// add provenance to the first generated rule
rule := &models.AlertRule{
@@ -305,7 +230,7 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
err := svc.provenanceStore.SetProvenance(context.Background(), rule, orgID, models.ProvenanceAPI)
require.NoError(t, err)
req := createRequestContext(orgID, org.RoleViewer, nil)
req := createRequestContext(orgID, nil)
response := svc.RouteGetNamespaceRulesConfig(req, folder.Title)
require.Equal(t, http.StatusAccepted, response.Status())
@@ -338,9 +263,9 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex()))
ruleStore.PutRule(context.Background(), expectedRules...)
ac := acMock.New().WithDisabled()
response := createService(ac, ruleStore).RouteGetNamespaceRulesConfig(createRequestContext(orgID, org.RoleViewer, nil), folder.Title)
req := createRequestContext(orgID, nil)
response := createService(ruleStore).RouteGetNamespaceRulesConfig(req, folder.Title)
require.Equal(t, http.StatusAccepted, response.Status())
result := &apimodels.NamespaceConfigResponse{}
@@ -389,10 +314,11 @@ func TestRouteGetRulesConfig(t *testing.T) {
group2 := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(group2Key)))
ruleStore.PutRule(context.Background(), append(group1, group2...)...)
request := createRequestContext(orgID, "", nil)
t.Run("and do not return group if user does not have access to one of rules", func(t *testing.T) {
ac := acMock.New().WithPermissions(createPermissionsForRules(append(group1, group2[1:]...)))
response := createService(ac, ruleStore).RouteGetRulesConfig(request)
permissions := createPermissionsForRules(append(group1, group2[1:]...), orgID)
request := createRequestContextWithPerms(orgID, permissions, nil)
response := createService(ruleStore).RouteGetRulesConfig(request)
require.Equal(t, http.StatusOK, response.Status())
result := &apimodels.NamespaceConfigResponse{}
@@ -420,9 +346,9 @@ func TestRouteGetRulesConfig(t *testing.T) {
expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex()))
ruleStore.PutRule(context.Background(), expectedRules...)
ac := acMock.New().WithDisabled()
response := createService(ac, ruleStore).RouteGetRulesConfig(createRequestContext(orgID, org.RoleViewer, nil))
req := createRequestContext(orgID, nil)
response := createService(ruleStore).RouteGetRulesConfig(req)
require.Equal(t, http.StatusOK, response.Status())
result := &apimodels.NamespaceConfigResponse{}
@@ -466,20 +392,23 @@ func TestRouteGetRulesGroupConfig(t *testing.T) {
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(groupKey)))
ruleStore.PutRule(context.Background(), expectedRules...)
request := createRequestContext(orgID, "", map[string]string{
":Namespace": folder.Title,
":Groupname": groupKey.RuleGroup,
})
t.Run("and return 401 if user does not have access one of rules", func(t *testing.T) {
ac := acMock.New().WithPermissions(createPermissionsForRules(expectedRules[1:]))
response := createService(ac, ruleStore).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup)
permissions := createPermissionsForRules(expectedRules[1:], orgID)
request := createRequestContextWithPerms(orgID, permissions, map[string]string{
":Namespace": folder.Title,
":Groupname": groupKey.RuleGroup,
})
response := createService(ruleStore).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup)
require.Equal(t, http.StatusUnauthorized, response.Status())
})
t.Run("and return rules if user has access to all of them", func(t *testing.T) {
ac := acMock.New().WithPermissions(createPermissionsForRules(expectedRules))
response := createService(ac, ruleStore).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup)
permissions := createPermissionsForRules(expectedRules, orgID)
request := createRequestContextWithPerms(orgID, permissions, map[string]string{
":Namespace": folder.Title,
":Groupname": groupKey.RuleGroup,
})
response := createService(ruleStore).RouteGetRulesGroupConfig(request, folder.Title, groupKey.RuleGroup)
require.Equal(t, http.StatusAccepted, response.Status())
result := &apimodels.RuleGroupConfigResponse{}
@@ -500,9 +429,9 @@ func TestRouteGetRulesGroupConfig(t *testing.T) {
expectedRules := models.GenerateAlertRules(rand.Intn(5)+5, models.AlertRuleGen(withGroupKey(groupKey), models.WithUniqueGroupIndex()))
ruleStore.PutRule(context.Background(), expectedRules...)
ac := acMock.New().WithDisabled()
response := createService(ac, ruleStore).RouteGetRulesGroupConfig(createRequestContext(orgID, org.RoleViewer, nil), folder.Title, groupKey.RuleGroup)
req := createRequestContext(orgID, nil)
response := createService(ruleStore).RouteGetRulesGroupConfig(req, folder.Title, groupKey.RuleGroup)
require.Equal(t, http.StatusAccepted, response.Status())
result := &apimodels.RuleGroupConfigResponse{}
@@ -589,13 +518,13 @@ func TestVerifyProvisionedRulesNotAffected(t *testing.T) {
})
}
func createServiceWithProvenanceStore(ac *acMock.Mock, store *fakes.RuleStore, provenanceStore provisioning.ProvisioningStore) *RulerSrv {
svc := createService(ac, store)
func createServiceWithProvenanceStore(store *fakes.RuleStore, provenanceStore provisioning.ProvisioningStore) *RulerSrv {
svc := createService(store)
svc.provenanceStore = provenanceStore
return svc
}
func createService(ac *acMock.Mock, store *fakes.RuleStore) *RulerSrv {
func createService(store *fakes.RuleStore) *RulerSrv {
return &RulerSrv{
xactManager: store,
store: store,
@@ -603,11 +532,16 @@ func createService(ac *acMock.Mock, store *fakes.RuleStore) *RulerSrv {
provenanceStore: provisioning.NewFakeProvisioningStore(),
log: log.New("test"),
cfg: nil,
ac: ac,
ac: acimpl.ProvideAccessControl(setting.NewCfg()),
}
}
func createRequestContext(orgID int64, role org.RoleType, params map[string]string) *contextmodel.ReqContext {
func createRequestContext(orgID int64, params map[string]string) *contextmodel.ReqContext {
defaultPerms := map[int64]map[string][]string{orgID: {datasources.ActionQuery: []string{datasources.ScopeAll}}}
return createRequestContextWithPerms(orgID, defaultPerms, params)
}
func createRequestContextWithPerms(orgID int64, permissions map[int64]map[string][]string, params map[string]string) *contextmodel.ReqContext {
uri, _ := url.Parse("http://localhost")
ctx := web.Context{Req: &http.Request{
URL: uri,
@@ -619,23 +553,21 @@ func createRequestContext(orgID int64, role org.RoleType, params map[string]stri
return &contextmodel.ReqContext{
IsSignedIn: true,
SignedInUser: &user.SignedInUser{
OrgRole: role,
OrgID: orgID,
Permissions: permissions,
OrgID: orgID,
},
Context: &ctx,
}
}
func createPermissionsForRules(rules []*models.AlertRule) []accesscontrol.Permission {
var permissions []accesscontrol.Permission
func createPermissionsForRules(rules []*models.AlertRule, orgID int64) map[int64]map[string][]string {
permissions := map[string][]string{}
for _, rule := range rules {
for _, query := range rule.Data {
permissions = append(permissions, accesscontrol.Permission{
Action: datasources.ActionQuery, Scope: datasources.ScopeProvider.GetResourceScopeUID(query.DatasourceUID),
})
permissions[datasources.ActionQuery] = append(permissions[datasources.ActionQuery], datasources.ScopeProvider.GetResourceScopeUID(query.DatasourceUID))
}
}
return permissions
return map[int64]map[string][]string{orgID: permissions}
}
func withOrgID(orgId int64) func(rule *models.AlertRule) {

View File

@@ -43,7 +43,7 @@ func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *contextmodel.ReqContext,
queries := AlertQueriesFromApiAlertQueries(body.GrafanaManagedCondition.Data)
if !authorizeDatasourceAccessForRule(&ngmodels.AlertRule{Data: queries}, func(evaluator accesscontrol.Evaluator) bool {
return accesscontrol.HasAccess(srv.accessControl, c)(accesscontrol.ReqSignedIn, evaluator)
return accesscontrol.HasAccess(srv.accessControl, c)(evaluator)
}) {
return errorToResponse(fmt.Errorf("%w to query one or many data sources used by the rule", ErrAuthorization))
}
@@ -116,7 +116,7 @@ func (srv TestingApiSrv) RouteTestRuleConfig(c *contextmodel.ReqContext, body ap
func (srv TestingApiSrv) RouteEvalQueries(c *contextmodel.ReqContext, cmd apimodels.EvalQueriesPayload) response.Response {
queries := AlertQueriesFromApiAlertQueries(cmd.Data)
if !authorizeDatasourceAccessForRule(&ngmodels.AlertRule{Data: queries}, func(evaluator accesscontrol.Evaluator) bool {
return accesscontrol.HasAccess(srv.accessControl, c)(accesscontrol.ReqSignedIn, evaluator)
return accesscontrol.HasAccess(srv.accessControl, c)(evaluator)
}) {
return ErrResp(http.StatusUnauthorized, fmt.Errorf("%w to query one or many data sources used by the rule", ErrAuthorization), "")
}
@@ -174,7 +174,7 @@ func (srv TestingApiSrv) BacktestAlertRule(c *contextmodel.ReqContext, cmd apimo
queries := AlertQueriesFromApiAlertQueries(cmd.Data)
if !authorizeDatasourceAccessForRule(&ngmodels.AlertRule{Data: queries}, func(evaluator accesscontrol.Evaluator) bool {
return accesscontrol.HasAccess(srv.accessControl, c)(accesscontrol.ReqSignedIn, evaluator)
return accesscontrol.HasAccess(srv.accessControl, c)(evaluator)
}) {
return errorToResponse(fmt.Errorf("%w to query one or many data sources used by the rule", ErrAuthorization))
}

View File

@@ -93,61 +93,6 @@ func TestRouteTestGrafanaRuleConfig(t *testing.T) {
evaluator.AssertCalled(t, "Evaluate", mock.Anything, currentTime)
})
})
t.Run("when fine-grained access is disabled", func(t *testing.T) {
rc := &contextmodel.ReqContext{
Context: &web.Context{
Req: &http.Request{},
},
IsSignedIn: false,
SignedInUser: &user.SignedInUser{
OrgID: 1,
},
}
ac := acMock.New().WithDisabled()
t.Run("should require user to be signed in", func(t *testing.T) {
data1 := models.GenerateAlertQuery()
ds := &fakes.FakeCacheService{DataSources: []*datasources.DataSource{
{UID: data1.DatasourceUID},
}}
currentTime := time.Now()
evaluator := &eval_mocks.ConditionEvaluatorMock{}
var result []eval.Result
evaluator.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(result, nil)
srv := createTestingApiSrv(ds, ac, eval_mocks.NewEvaluatorFactory(evaluator))
response := srv.RouteTestGrafanaRuleConfig(rc, definitions.TestRulePayload{
Expr: "",
GrafanaManagedCondition: &definitions.EvalAlertConditionCommand{
Condition: data1.RefID,
Data: ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1}),
Now: currentTime,
},
})
require.Equal(t, http.StatusUnauthorized, response.Status())
evaluator.AssertNotCalled(t, "Evaluate", mock.Anything, currentTime)
rc.IsSignedIn = true
response = srv.RouteTestGrafanaRuleConfig(rc, definitions.TestRulePayload{
Expr: "",
GrafanaManagedCondition: &definitions.EvalAlertConditionCommand{
Condition: data1.RefID,
Data: ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1}),
Now: currentTime,
},
})
require.Equal(t, http.StatusOK, response.Status())
evaluator.AssertCalled(t, "Evaluate", mock.Anything, currentTime)
})
})
}
func TestRouteEvalQueries(t *testing.T) {
@@ -220,61 +165,6 @@ func TestRouteEvalQueries(t *testing.T) {
evaluator.AssertCalled(t, "EvaluateRaw", mock.Anything, currentTime)
})
})
t.Run("when fine-grained access is disabled", func(t *testing.T) {
rc := &contextmodel.ReqContext{
Context: &web.Context{
Req: &http.Request{},
},
IsSignedIn: false,
SignedInUser: &user.SignedInUser{
OrgID: 1,
},
}
ac := acMock.New().WithDisabled()
t.Run("should require user to be signed in", func(t *testing.T) {
data1 := models.GenerateAlertQuery()
ds := &fakes.FakeCacheService{DataSources: []*datasources.DataSource{
{UID: data1.DatasourceUID},
}}
currentTime := time.Now()
evaluator := &eval_mocks.ConditionEvaluatorMock{}
result := &backend.QueryDataResponse{
Responses: map[string]backend.DataResponse{
"test": {
Frames: nil,
Error: nil,
},
},
}
evaluator.EXPECT().EvaluateRaw(mock.Anything, mock.Anything).Return(result, nil)
srv := createTestingApiSrv(ds, ac, eval_mocks.NewEvaluatorFactory(evaluator))
response := srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{
Data: ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1}),
Now: currentTime,
})
require.Equal(t, http.StatusUnauthorized, response.Status())
evaluator.AssertNotCalled(t, "EvaluateRaw", mock.Anything, mock.Anything)
rc.IsSignedIn = true
response = srv.RouteEvalQueries(rc, definitions.EvalQueriesPayload{
Data: ApiAlertQueriesFromAlertQueries([]models.AlertQuery{data1}),
Now: currentTime,
})
require.Equal(t, http.StatusOK, response.Status())
evaluator.AssertCalled(t, "EvaluateRaw", mock.Anything, currentTime)
})
})
}
func createTestingApiSrv(ds *fakes.FakeCacheService, ac *acMock.Mock, evaluator eval.EvaluatorFactory) *TestingApiSrv {