Access control: adding FGAC to annotation GET endpoints and fixed roles (#45102)

* Access control: adding FGAC to annotation GET endpoints and fixed roles

Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
This commit is contained in:
Ezequiel Victorero
2022-02-11 15:43:29 -03:00
committed by GitHub
parent 1a9638c363
commit 4f815e3d8e
5 changed files with 157 additions and 57 deletions

View File

@ -3,6 +3,8 @@ package api
import (
"context"
"fmt"
"io"
"net/http"
"testing"
"github.com/grafana/grafana/pkg/api/dtos"
@ -10,9 +12,11 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAnnotationsAPIEndpoint(t *testing.T) {
@ -127,50 +131,30 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
PanelId: 1,
}
viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR
aclMockResp := []*models.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: models.PERMISSION_VIEW},
{Role: &editorRole, Permission: models.PERMISSION_EDIT},
}
setUp := func() {
bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error {
query.Result = aclMockResp
return nil
})
bus.AddHandler("test", func(ctx context.Context, query *models.GetTeamsByUserQuery) error {
query.Result = []*models.TeamDTO{}
return nil
})
}
t.Run("When user is an Org Viewer", func(t *testing.T) {
role := models.ROLE_VIEWER
t.Run("Should not be allowed to save an annotation", func(t *testing.T) {
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) {
setUp()
setUpACL()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 403, sc.resp.Code)
})
putAnnotationScenario(t, "When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) {
setUp()
setUpACL()
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
assert.Equal(t, 403, sc.resp.Code)
})
patchAnnotationScenario(t, "When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
setUp()
setUpACL()
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
assert.Equal(t, 403, sc.resp.Code)
})
mock := mockstore.NewSQLStoreMock()
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1",
"/api/annotations/:annotationId", role, func(sc *scenarioContext) {
setUp()
setUpACL()
fakeAnnoRepo = &fakeAnnotationsRepo{}
annotations.SetRepository(fakeAnnoRepo)
sc.handlerFunc = DeleteAnnotationByID
@ -184,26 +168,26 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
role := models.ROLE_EDITOR
t.Run("Should be able to save an annotation", func(t *testing.T) {
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) {
setUp()
setUpACL()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
})
putAnnotationScenario(t, "When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) {
setUp()
setUpACL()
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
})
patchAnnotationScenario(t, "When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
setUp()
setUpACL()
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
})
mock := mockstore.NewSQLStoreMock()
loggedInUserScenarioWithRole(t, "When calling DELETE on", "DELETE", "/api/annotations/1",
"/api/annotations/:annotationId", role, func(sc *scenarioContext) {
setUp()
setUpACL()
fakeAnnoRepo = &fakeAnnotationsRepo{}
annotations.SetRepository(fakeAnnoRepo)
sc.handlerFunc = DeleteAnnotationByID
@ -217,26 +201,26 @@ func TestAnnotationsAPIEndpoint(t *testing.T) {
role := models.ROLE_ADMIN
t.Run("Should be able to do anything", func(t *testing.T) {
postAnnotationScenario(t, "When calling POST on", "/api/annotations", "/api/annotations", role, cmd, func(sc *scenarioContext) {
setUp()
setUpACL()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
})
putAnnotationScenario(t, "When calling PUT on", "/api/annotations/1", "/api/annotations/:annotationId", role, updateCmd, func(sc *scenarioContext) {
setUp()
setUpACL()
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
})
patchAnnotationScenario(t, "When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
setUp()
setUpACL()
sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
})
deleteAnnotationsScenario(t, "When calling POST on", "/api/annotations/mass-delete",
"/api/annotations/mass-delete", role, deleteCmd, func(sc *scenarioContext) {
setUp()
setUpACL()
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 200, sc.resp.Code)
})
@ -373,3 +357,87 @@ func deleteAnnotationsScenario(t *testing.T, desc string, url string, routePatte
fn(sc)
})
}
func TestAPI_Annotations_AccessControl(t *testing.T) {
sc := setupHTTPServer(t, true, true)
setInitCtxSignedInEditor(sc.initCtx)
_, err := sc.db.CreateOrgWithMember("TestOrg", testUserID)
require.NoError(t, err)
type args struct {
permissions []*accesscontrol.Permission
url string
body io.Reader
method string
}
tests := []struct {
name string
args args
want int
}{
{
name: "AccessControl getting annotations with correct permissions is allowed",
args: args{
permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsRead, Scope: accesscontrol.ScopeAnnotationsAll}},
url: "/api/annotations",
method: http.MethodGet,
},
want: 200,
},
{
name: "AccessControl getting annotations without permissions is forbidden",
args: args{
permissions: []*accesscontrol.Permission{},
url: "/api/annotations",
method: http.MethodGet,
},
want: 403,
},
{
name: "AccessControl getting tags for annotations with correct permissions is allowed",
args: args{
permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsTagsRead, Scope: accesscontrol.ScopeAnnotationsTagsAll}},
url: "/api/annotations/tags",
method: http.MethodGet,
},
want: 200,
},
{
name: "AccessControl getting tags for annotations without correct permissions is forbidden",
args: args{
permissions: []*accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsTagsRead}},
url: "/api/annotations/tags",
method: http.MethodGet,
},
want: 403,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
setAccessControlPermissions(sc.acmock, tt.args.permissions, sc.initCtx.OrgId)
r := callAPI(sc.server, tt.args.method, tt.args.url, tt.args.body, t)
assert.Equalf(t, tt.want, r.Code, "Annotations API(%v)", tt.args.url)
})
}
}
func setUpACL() {
viewerRole := models.ROLE_VIEWER
editorRole := models.ROLE_EDITOR
aclMockResp := []*models.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: models.PERMISSION_VIEW},
{Role: &editorRole, Permission: models.PERMISSION_EDIT},
}
bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardAclInfoListQuery) error {
query.Result = aclMockResp
return nil
})
bus.AddHandler("test", func(ctx context.Context, query *models.GetTeamsByUserQuery) error {
query.Result = []*models.TeamDTO{}
return nil
})
}