|
|
|
@ -8,6 +8,7 @@ import ( |
|
|
|
|
"testing" |
|
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert" |
|
|
|
|
"github.com/stretchr/testify/mock" |
|
|
|
|
"github.com/stretchr/testify/require" |
|
|
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol" |
|
|
|
@ -16,6 +17,7 @@ import ( |
|
|
|
|
"github.com/grafana/grafana/pkg/services/annotations/annotationstest" |
|
|
|
|
"github.com/grafana/grafana/pkg/services/dashboards" |
|
|
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt" |
|
|
|
|
"github.com/grafana/grafana/pkg/services/folder" |
|
|
|
|
"github.com/grafana/grafana/pkg/services/folder/foldertest" |
|
|
|
|
"github.com/grafana/grafana/pkg/services/guardian" |
|
|
|
|
"github.com/grafana/grafana/pkg/setting" |
|
|
|
@ -23,12 +25,16 @@ import ( |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
func TestAPI_Annotations(t *testing.T) { |
|
|
|
|
dashUID := "test-dash" |
|
|
|
|
folderUID := "test-folder" |
|
|
|
|
|
|
|
|
|
type testCase struct { |
|
|
|
|
desc string |
|
|
|
|
path string |
|
|
|
|
method string |
|
|
|
|
body string |
|
|
|
|
expectedCode int |
|
|
|
|
featureFlags []any |
|
|
|
|
permissions []accesscontrol.Permission |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -61,6 +67,30 @@ func TestAPI_Annotations(t *testing.T) { |
|
|
|
|
expectedCode: http.StatusForbidden, |
|
|
|
|
permissions: []accesscontrol.Permission{}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to fetch dashboard annotation by id with correct dashboard scope with annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/2", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
method: http.MethodGet, |
|
|
|
|
expectedCode: http.StatusOK, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsRead, Scope: dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashUID)}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to fetch dashboard annotation by id with correct folder scope with annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/2", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
method: http.MethodGet, |
|
|
|
|
expectedCode: http.StatusOK, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsRead, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderUID)}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should not be able to fetch dashboard annotation by id with the old dashboard scope when annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/2", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
method: http.MethodGet, |
|
|
|
|
expectedCode: http.StatusForbidden, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsRead, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to fetch annotation tags with correct permission", |
|
|
|
|
path: "/api/annotations/tags", |
|
|
|
@ -89,6 +119,30 @@ func TestAPI_Annotations(t *testing.T) { |
|
|
|
|
expectedCode: http.StatusForbidden, |
|
|
|
|
permissions: []accesscontrol.Permission{}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to update dashboard annotation with correct dashboard scope with annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/2", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
method: http.MethodPut, |
|
|
|
|
expectedCode: http.StatusOK, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashUID)}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to update dashboard annotation with correct folder scope with annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/2", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
method: http.MethodPut, |
|
|
|
|
expectedCode: http.StatusOK, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderUID)}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should not be able to update dashboard annotation with the old dashboard scope when annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/2", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
method: http.MethodPut, |
|
|
|
|
expectedCode: http.StatusForbidden, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to update organization annotation with correct permission", |
|
|
|
|
path: "/api/annotations/1", |
|
|
|
@ -117,6 +171,30 @@ func TestAPI_Annotations(t *testing.T) { |
|
|
|
|
expectedCode: http.StatusForbidden, |
|
|
|
|
permissions: []accesscontrol.Permission{}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to patch dashboard annotation with correct dashboard scope with annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/2", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
method: http.MethodPatch, |
|
|
|
|
expectedCode: http.StatusOK, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashUID)}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to patch dashboard annotation with correct folder scope with annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/2", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
method: http.MethodPatch, |
|
|
|
|
expectedCode: http.StatusOK, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderUID)}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should not be able to patch dashboard annotation with the old dashboard scope when annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/2", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
method: http.MethodPatch, |
|
|
|
|
expectedCode: http.StatusForbidden, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to patch organization annotation with correct permission", |
|
|
|
|
path: "/api/annotations/1", |
|
|
|
@ -147,6 +225,33 @@ func TestAPI_Annotations(t *testing.T) { |
|
|
|
|
expectedCode: http.StatusForbidden, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to create dashboard annotation with correct dashboard scope with annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations", |
|
|
|
|
method: http.MethodPost, |
|
|
|
|
body: "{\"dashboardId\": 2,\"text\": \"test\"}", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
expectedCode: http.StatusOK, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsCreate, Scope: dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashUID)}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to create dashboard annotation with correct folder scope with annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations", |
|
|
|
|
method: http.MethodPost, |
|
|
|
|
body: "{\"dashboardId\": 2,\"text\": \"test\"}", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
expectedCode: http.StatusOK, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsCreate, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderUID)}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should not be able to create dashboard annotation with the old dashboard scope when annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations", |
|
|
|
|
method: http.MethodPost, |
|
|
|
|
body: "{\"dashboardId\": 2,\"text\": \"test\"}", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
expectedCode: http.StatusForbidden, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to create organization annotation with correct permission", |
|
|
|
|
path: "/api/annotations", |
|
|
|
@ -177,6 +282,30 @@ func TestAPI_Annotations(t *testing.T) { |
|
|
|
|
expectedCode: http.StatusForbidden, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to delete dashboard annotation with correct dashboard scope with annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/2", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
method: http.MethodDelete, |
|
|
|
|
expectedCode: http.StatusOK, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashUID)}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to delete dashboard annotation with correct folder scope with annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/2", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
method: http.MethodDelete, |
|
|
|
|
expectedCode: http.StatusOK, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderUID)}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should not be able to delete dashboard annotation with the old dashboard scope when annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/2", |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
method: http.MethodDelete, |
|
|
|
|
expectedCode: http.StatusForbidden, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to delete organization annotation with correct permission", |
|
|
|
|
path: "/api/annotations/1", |
|
|
|
@ -222,22 +351,59 @@ func TestAPI_Annotations(t *testing.T) { |
|
|
|
|
expectedCode: http.StatusForbidden, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to mass delete dashboard annotation with correct dashboard scope with annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/mass-delete", |
|
|
|
|
body: "{\"dashboardId\": 2, \"panelId\": 1}", |
|
|
|
|
method: http.MethodPost, |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
expectedCode: http.StatusOK, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: dashboards.ScopeDashboardsProvider.GetResourceScopeUID(dashUID)}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should be able to mass delete dashboard annotation with correct folder scope with annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/mass-delete", |
|
|
|
|
body: "{\"dashboardId\": 2, \"panelId\": 1}", |
|
|
|
|
method: http.MethodPost, |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
expectedCode: http.StatusOK, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: dashboards.ScopeFoldersProvider.GetResourceScopeUID(folderUID)}}, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
desc: "should not be able to mass delete dashboard annotation with the old dashboard scope when annotationPermissionUpdate enabled", |
|
|
|
|
path: "/api/annotations/mass-delete", |
|
|
|
|
body: "{\"dashboardId\": 2, \"panelId\": 1}", |
|
|
|
|
method: http.MethodPost, |
|
|
|
|
featureFlags: []any{featuremgmt.FlagAnnotationPermissionUpdate}, |
|
|
|
|
expectedCode: http.StatusForbidden, |
|
|
|
|
permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, tt := range tests { |
|
|
|
|
t.Run(tt.desc, func(t *testing.T) { |
|
|
|
|
setUpRBACGuardian(t) |
|
|
|
|
// Don't need access to dashboards if annotationPermissionUpdate is enabled
|
|
|
|
|
if len(tt.featureFlags) == 0 { |
|
|
|
|
setUpRBACGuardian(t) |
|
|
|
|
} |
|
|
|
|
server := SetupAPITestServer(t, func(hs *HTTPServer) { |
|
|
|
|
hs.Cfg = setting.NewCfg() |
|
|
|
|
repo := annotationstest.NewFakeAnnotationsRepo() |
|
|
|
|
_ = repo.Save(context.Background(), &annotations.Item{ID: 1, DashboardID: 0}) |
|
|
|
|
_ = repo.Save(context.Background(), &annotations.Item{ID: 2, DashboardID: 1}) |
|
|
|
|
hs.annotationsRepo = repo |
|
|
|
|
hs.Features = featuremgmt.WithFeatures(tt.featureFlags...) |
|
|
|
|
dashService := &dashboards.FakeDashboardService{} |
|
|
|
|
dashService.On("GetDashboard", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{UID: dashUID, FolderUID: folderUID, FolderID: 1}, nil) |
|
|
|
|
folderService := &foldertest.FakeService{} |
|
|
|
|
folderService.ExpectedFolder = &folder.Folder{UID: folderUID, ID: 1} |
|
|
|
|
folderDB := &foldertest.FakeFolderStore{} |
|
|
|
|
folderDB.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(&folder.Folder{UID: folderUID, ID: 1}, nil) |
|
|
|
|
hs.DashboardService = dashService |
|
|
|
|
hs.folderService = folderService |
|
|
|
|
hs.AccessControl = acimpl.ProvideAccessControl(hs.Cfg) |
|
|
|
|
features := featuremgmt.WithFeatures() |
|
|
|
|
dashSvc := &dashboards.FakeDashboardService{} |
|
|
|
|
folderSvc := &foldertest.FakeService{} |
|
|
|
|
hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo, features, dashSvc, folderSvc)) |
|
|
|
|
hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo, hs.Features, dashService, folderService)) |
|
|
|
|
hs.AccessControl.RegisterScopeAttributeResolver(dashboards.NewDashboardIDScopeResolver(folderDB, dashService, folderService)) |
|
|
|
|
}) |
|
|
|
|
var body io.Reader |
|
|
|
|
if tt.body != "" { |
|
|
|
@ -252,6 +418,7 @@ func TestAPI_Annotations(t *testing.T) { |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestService_AnnotationTypeScopeResolver(t *testing.T) { |
|
|
|
|
rootDashUID := "root-dashboard" |
|
|
|
|
folderDashUID := "folder-dashboard" |
|
|
|
|