diff --git a/pkg/api/annotations_test.go b/pkg/api/annotations_test.go index 26534dd4025..7de7e945eba 100644 --- a/pkg/api/annotations_test.go +++ b/pkg/api/annotations_test.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/http" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -14,10 +15,10 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations/annotationstest" "github.com/grafana/grafana/pkg/services/dashboards" @@ -25,7 +26,8 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/team/teamtest" - "github.com/grafana/grafana/pkg/services/user" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/web/webtest" ) func TestAnnotationsAPIEndpoint(t *testing.T) { @@ -386,371 +388,232 @@ func deleteAnnotationsScenario(t *testing.T, desc string, url string, routePatte } func TestAPI_Annotations_AccessControl(t *testing.T) { - sc := setupHTTPServer(t, true) - setInitCtxSignedInEditor(sc.initCtx) - _, err := sc.hs.orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "TestOrg", UserID: testUserID}) - require.NoError(t, err) - - origNewGuardian := guardian.New - t.Cleanup(func() { - guardian.New = origNewGuardian - }) - - // Create a dashboard - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) - sc.dashboardPermissionsService.On("SetPermissions", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string"), mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) - cmd := &dashboards.SaveDashboardDTO{ - OrgId: testOrgID, - User: &user.SignedInUser{UserID: testUserID, OrgID: testOrgID}, - Dashboard: &models.Dashboard{ - OrgId: testOrgID, - Title: "1 test dash", - Data: simplejson.NewFromAny(map[string]interface{}{}), - }} - dashboard, err := sc.hs.DashboardService.SaveDashboard(context.Background(), cmd, false) - require.NoError(t, err) - require.NotNil(t, dashboard) - t.Cleanup(func() { - err := sc.hs.DashboardService.DeleteDashboard(context.Background(), dashboard.Id, dashboard.OrgId) - require.NoError(t, err) - }) - - dashboardAnnotation := &annotations.Item{Id: 1, DashboardId: dashboard.Id} - organizationAnnotation := &annotations.Item{Id: 2, DashboardId: 0} - - _ = sc.hs.annotationsRepo.Save(context.Background(), dashboardAnnotation) - _ = sc.hs.annotationsRepo.Save(context.Background(), organizationAnnotation) - - postOrganizationCmd := dtos.PostAnnotationsCmd{ - Time: 1000, - Text: "annotation text", - Tags: []string{"tag1", "tag2"}, - PanelId: 1, + type testCase struct { + desc string + path string + method string + body string + expectedCode int + permissions []accesscontrol.Permission } - postDashboardCmd := dtos.PostAnnotationsCmd{ - Time: 1000, - Text: "annotation text", - Tags: []string{"tag1", "tag2"}, - DashboardId: 1, - PanelId: 1, + tests := []testCase{ + { + desc: "should be able to fetch annotations with correct permission", + path: "/api/annotations", + method: http.MethodGet, + expectedCode: http.StatusOK, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsRead, Scope: accesscontrol.ScopeAnnotationsAll}}, + }, + { + desc: "should not be able to fetch annotations without correct permission", + path: "/api/annotations", + method: http.MethodGet, + expectedCode: http.StatusForbidden, + permissions: []accesscontrol.Permission{}, + }, + { + desc: "should be able to fetch annotation by id with correct permission", + path: "/api/annotations/1", + method: http.MethodGet, + expectedCode: http.StatusOK, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsRead, Scope: accesscontrol.ScopeAnnotationsAll}}, + }, + { + desc: "should not be able to fetch annotation by id without correct permission", + path: "/api/annotations/1", + method: http.MethodGet, + expectedCode: http.StatusForbidden, + permissions: []accesscontrol.Permission{}, + }, + { + desc: "should be able to fetch annotation tags with correct permission", + path: "/api/annotations/tags", + method: http.MethodGet, + expectedCode: http.StatusOK, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsRead}}, + }, + { + desc: "should not be able to fetch annotation tags without correct permission", + path: "/api/annotations/tags", + method: http.MethodGet, + expectedCode: http.StatusForbidden, + permissions: []accesscontrol.Permission{}, + }, + { + desc: "should be able to update dashboard annotation with correct permission", + path: "/api/annotations/2", + method: http.MethodPut, + expectedCode: http.StatusOK, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + }, + { + desc: "should not be able to update dashboard annotation without correct permission", + path: "/api/annotations/2", + method: http.MethodPut, + expectedCode: http.StatusForbidden, + permissions: []accesscontrol.Permission{}, + }, + { + desc: "should be able to update organization annotation with correct permission", + path: "/api/annotations/1", + method: http.MethodPut, + expectedCode: http.StatusOK, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, + }, + { + desc: "should not be able to update organization annotation without correct permission", + path: "/api/annotations/1", + method: http.MethodPut, + expectedCode: http.StatusForbidden, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + }, + { + desc: "should be able to patch dashboard annotation with correct permission", + path: "/api/annotations/2", + method: http.MethodPatch, + expectedCode: http.StatusOK, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + }, + { + desc: "should not be able to patch dashboard annotation without correct permission", + path: "/api/annotations/2", + method: http.MethodPatch, + expectedCode: http.StatusForbidden, + permissions: []accesscontrol.Permission{}, + }, + { + desc: "should be able to patch organization annotation with correct permission", + path: "/api/annotations/1", + method: http.MethodPatch, + expectedCode: http.StatusOK, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, + }, + { + desc: "should not be able to patch organization annotation without correct permission", + path: "/api/annotations/1", + method: http.MethodPatch, + expectedCode: http.StatusForbidden, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + }, + { + desc: "should be able to create dashboard annotation with correct permission", + path: "/api/annotations", + method: http.MethodPost, + body: "{\"dashboardId\": 2,\"text\": \"test\"}", + expectedCode: http.StatusOK, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + }, + { + desc: "should not be able to create dashboard annotation without correct permission", + path: "/api/annotations", + method: http.MethodPost, + body: "{\"dashboardId\": 2,\"text\": \"test\"}", + expectedCode: http.StatusForbidden, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, + }, + { + desc: "should be able to create organization annotation with correct permission", + path: "/api/annotations", + method: http.MethodPost, + body: "{\"text\": \"test\"}", + expectedCode: http.StatusOK, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, + }, + { + desc: "should not be able to create organization annotation without correct permission", + path: "/api/annotations", + method: http.MethodPost, + body: "{\"text\": \"test\"}", + expectedCode: http.StatusForbidden, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + }, + { + desc: "should be able to delete dashboard annotation with correct permission", + path: "/api/annotations/2", + method: http.MethodDelete, + expectedCode: http.StatusOK, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + }, + { + desc: "should not be able to delete dashboard annotation without correct permission", + path: "/api/annotations/2", + method: http.MethodDelete, + expectedCode: http.StatusForbidden, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, + }, + { + desc: "should be able to delete organization annotation with correct permission", + path: "/api/annotations/1", + method: http.MethodDelete, + expectedCode: http.StatusOK, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, + }, + { + desc: "should not be able to delete organization annotation without correct permission", + path: "/api/annotations/1", + method: http.MethodDelete, + expectedCode: http.StatusForbidden, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + }, + { + desc: "should be able to create graphite annotation with correct permission", + path: "/api/annotations/graphite", + body: "{\"what\": \"test\", \"tags\": []}", + method: http.MethodPost, + expectedCode: http.StatusOK, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, + }, + { + desc: "should not be able to create graphite annotation without correct permission", + path: "/api/annotations/graphite", + method: http.MethodPost, + expectedCode: http.StatusForbidden, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + }, + { + desc: "should be able to mass delete dashboard annotations with correct permission", + path: "/api/annotations/mass-delete", + body: "{\"dashboardId\": 2, \"panelId\": 1}", + method: http.MethodPost, + expectedCode: http.StatusOK, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, + }, + { + desc: "should not be able to mass delete dashboard annotations without correct permission", + path: "/api/annotations/mass-delete", + body: "{\"dashboardId\": 2, \"panelId\": 1}", + method: http.MethodPost, + expectedCode: http.StatusForbidden, + permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, + }, } - updateCmd := dtos.UpdateAnnotationsCmd{ - Time: 1000, - Text: "annotation text", - Tags: []string{"tag1", "tag2"}, - } - - patchCmd := dtos.PatchAnnotationsCmd{ - Time: 1000, - Text: "annotation text", - Tags: []string{"tag1", "tag2"}, - } - - postGraphiteCmd := dtos.PostGraphiteAnnotationsCmd{ - When: 1000, - What: "annotation text", - Data: "Deploy", - Tags: []string{"tag1", "tag2"}, - } - - 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: http.StatusOK, - }, - { - name: "AccessControl getting annotations without permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{}, - url: "/api/annotations", - method: http.MethodGet, - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl getting annotation by ID with correct permissions is allowed", - args: args{ - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsRead, Scope: accesscontrol.ScopeAnnotationsAll}}, - url: "/api/annotations/1", - method: http.MethodGet, - }, - want: http.StatusOK, - }, - { - name: "AccessControl getting annotation by ID without permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{}, - url: "/api/annotations", - method: http.MethodGet, - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl getting tags for annotations with correct permissions is allowed", - args: args{ - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsRead}}, - url: "/api/annotations/tags", - method: http.MethodGet, - }, - want: http.StatusOK, - }, - { - name: "AccessControl getting tags for annotations without correct permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsWrite}}, - url: "/api/annotations/tags", - method: http.MethodGet, - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl update dashboard annotation with permissions is allowed", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard, - }}, - url: "/api/annotations/1", - method: http.MethodPut, - body: mockRequestBody(updateCmd), - }, - want: http.StatusOK, - }, - { - name: "AccessControl update dashboard annotation without permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{}, - url: "/api/annotations/1", - method: http.MethodPut, - body: mockRequestBody(updateCmd), - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl update organization annotation with permissions is allowed", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsAll, - }}, - url: "/api/annotations/2", - method: http.MethodPut, - body: mockRequestBody(updateCmd), - }, - want: http.StatusOK, - }, - { - name: "AccessControl update organization annotation without permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard, - }}, - url: "/api/annotations/2", - method: http.MethodPut, - body: mockRequestBody(updateCmd), - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl patch dashboard annotation with permissions is allowed", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard, - }}, - url: "/api/annotations/1", - method: http.MethodPatch, - body: mockRequestBody(patchCmd), - }, - want: http.StatusOK, - }, - { - name: "AccessControl patch dashboard annotation without permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{}, - url: "/api/annotations/1", - method: http.MethodPatch, - body: mockRequestBody(patchCmd), - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl patch organization annotation with permissions is allowed", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsAll, - }}, - url: "/api/annotations/2", - method: http.MethodPatch, - body: mockRequestBody(patchCmd), - }, - want: http.StatusOK, - }, - { - name: "AccessControl patch organization annotation without permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsWrite, Scope: accesscontrol.ScopeAnnotationsTypeDashboard, - }}, - url: "/api/annotations/2", - method: http.MethodPatch, - body: mockRequestBody(patchCmd), - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl create dashboard annotation with permissions is allowed", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeDashboard, - }}, - url: "/api/annotations", - method: http.MethodPost, - body: mockRequestBody(postDashboardCmd), - }, - want: http.StatusOK, - }, - { - name: "AccessControl create dashboard annotation without permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{}, - url: "/api/annotations", - method: http.MethodPost, - body: mockRequestBody(postDashboardCmd), - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl create dashboard annotation with incorrect permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeOrganization, - }}, - url: "/api/annotations", - method: http.MethodPost, - body: mockRequestBody(postDashboardCmd), - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl create organization annotation with permissions is allowed", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsAll, - }}, - url: "/api/annotations", - method: http.MethodPost, - body: mockRequestBody(postOrganizationCmd), - }, - want: http.StatusOK, - }, - { - name: "AccessControl create organization annotation without permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeDashboard, - }}, - url: "/api/annotations", - method: http.MethodPost, - body: mockRequestBody(postOrganizationCmd), - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl delete dashboard annotation with permissions is allowed", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard, - }}, - url: "/api/annotations/1", - method: http.MethodDelete, - }, - want: http.StatusOK, - }, - { - name: "AccessControl delete dashboard annotation without permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{}, - url: "/api/annotations/1", - method: http.MethodDelete, - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl delete organization annotation with permissions is allowed", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsAll, - }}, - url: "/api/annotations/2", - method: http.MethodDelete, - }, - want: http.StatusOK, - }, - { - name: "AccessControl delete organization annotation without permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard, - }}, - url: "/api/annotations/2", - method: http.MethodDelete, - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl create graphite annotation with permissions is allowed", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsAll, - }}, - url: "/api/annotations/graphite", - method: http.MethodPost, - body: mockRequestBody(postGraphiteCmd), - }, - want: http.StatusOK, - }, - { - name: "AccessControl create organization annotation without permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{{ - Action: accesscontrol.ActionAnnotationsCreate, Scope: accesscontrol.ScopeAnnotationsTypeDashboard, - }}, - url: "/api/annotations/graphite", - method: http.MethodPost, - body: mockRequestBody(postGraphiteCmd), - }, - want: http.StatusForbidden, - }, - } for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + t.Run(tt.desc, func(t *testing.T) { setUpRBACGuardian(t) - sc.acmock. - RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(sc.hs.annotationsRepo)) - setAccessControlPermissions(sc.acmock, tt.args.permissions, sc.initCtx.OrgID) + 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.AccessControl = acimpl.ProvideAccessControl(hs.Cfg) + hs.AccessControl.RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(hs.annotationsRepo)) + }) + var body io.Reader + if tt.body != "" { + body = strings.NewReader(tt.body) + } - 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) + req := webtest.RequestWithSignedInUser(server.NewRequest(tt.method, tt.path, body), userWithPermissions(1, tt.permissions)) + res, err := server.SendJSON(req) + require.NoError(t, err) + assert.Equal(t, tt.expectedCode, res.StatusCode) + require.NoError(t, res.Body.Close()) }) } } - func TestService_AnnotationTypeScopeResolver(t *testing.T) { type testCaseResolver struct { desc string @@ -811,166 +674,6 @@ func TestService_AnnotationTypeScopeResolver(t *testing.T) { } } -func TestAPI_MassDeleteAnnotations_AccessControl(t *testing.T) { - sc := setupHTTPServer(t, true) - setInitCtxSignedInEditor(sc.initCtx) - _, err := sc.hs.orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "TestOrg", UserID: testUserID}) - require.NoError(t, err) - - type args struct { - permissions []accesscontrol.Permission - url string - body io.Reader - method string - } - - origNewGuardian := guardian.New - t.Cleanup(func() { - guardian.New = origNewGuardian - }) - - // Create a dashboard - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) - sc.dashboardPermissionsService.On("SetPermissions", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string"), mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) - cmd := &dashboards.SaveDashboardDTO{ - OrgId: testOrgID, - User: &user.SignedInUser{UserID: testUserID, OrgID: testOrgID}, - Dashboard: &models.Dashboard{ - OrgId: testOrgID, - Title: "1 test dash", - Data: simplejson.NewFromAny(map[string]interface{}{}), - }} - dashboard, err := sc.hs.DashboardService.SaveDashboard(context.Background(), cmd, false) - require.NoError(t, err) - require.NotNil(t, dashboard) - t.Cleanup(func() { - err := sc.hs.DashboardService.DeleteDashboard(context.Background(), dashboard.Id, dashboard.OrgId) - require.NoError(t, err) - }) - - tests := []struct { - name string - args args - want int - }{ - { - name: "Mass delete dashboard annotations without dashboardId is not allowed", - args: args{ - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, - url: "/api/annotations/mass-delete", - method: http.MethodPost, - body: mockRequestBody(dtos.MassDeleteAnnotationsCmd{ - DashboardId: 0, - PanelId: 1, - }), - }, - want: http.StatusBadRequest, - }, - { - name: "Mass delete dashboard annotations without panelId is not allowed", - args: args{ - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, - url: "/api/annotations/mass-delete", - method: http.MethodPost, - body: mockRequestBody(dtos.MassDeleteAnnotationsCmd{ - DashboardId: 10, - PanelId: 0, - }), - }, - want: http.StatusBadRequest, - }, - { - name: "AccessControl mass delete dashboard annotations with correct dashboardId and panelId as input is allowed", - args: args{ - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, - url: "/api/annotations/mass-delete", - method: http.MethodPost, - body: mockRequestBody(dtos.MassDeleteAnnotationsCmd{ - DashboardId: dashboard.Id, - PanelId: 1, - }), - }, - want: http.StatusOK, - }, - { - name: "Mass delete organization annotations without input to delete all organization annotations is allowed", - args: args{ - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, - url: "/api/annotations/mass-delete", - method: http.MethodPost, - body: mockRequestBody(dtos.MassDeleteAnnotationsCmd{ - DashboardId: 0, - PanelId: 0, - }), - }, - want: http.StatusOK, - }, - { - name: "Mass delete organization annotations without permissions is forbidden", - args: args{ - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, - url: "/api/annotations/mass-delete", - method: http.MethodPost, - body: mockRequestBody(dtos.MassDeleteAnnotationsCmd{ - DashboardId: 0, - PanelId: 0, - }), - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl mass delete dashboard annotations with correct annotationId as input is allowed", - args: args{ - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, - url: "/api/annotations/mass-delete", - method: http.MethodPost, - body: mockRequestBody(dtos.MassDeleteAnnotationsCmd{ - AnnotationId: 1, - }), - }, - want: http.StatusOK, - }, - { - name: "AccessControl mass delete annotation without access to dashboard annotations is forbidden", - args: args{ - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeOrganization}}, - url: "/api/annotations/mass-delete", - method: http.MethodPost, - body: mockRequestBody(dtos.MassDeleteAnnotationsCmd{ - AnnotationId: 1, - }), - }, - want: http.StatusForbidden, - }, - { - name: "AccessControl mass delete annotation without access to organization annotations is forbidden", - args: args{ - permissions: []accesscontrol.Permission{{Action: accesscontrol.ActionAnnotationsDelete, Scope: accesscontrol.ScopeAnnotationsTypeDashboard}}, - url: "/api/annotations/mass-delete", - method: http.MethodPost, - body: mockRequestBody(dtos.MassDeleteAnnotationsCmd{ - AnnotationId: 2, - }), - }, - want: http.StatusForbidden, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - setUpRBACGuardian(t) - setAccessControlPermissions(sc.acmock, tt.args.permissions, sc.initCtx.OrgID) - dashboardAnnotation := &annotations.Item{Id: 1, DashboardId: 1} - organizationAnnotation := &annotations.Item{Id: 2, DashboardId: 0} - - _ = sc.hs.annotationsRepo.Save(context.Background(), dashboardAnnotation) - _ = sc.hs.annotationsRepo.Save(context.Background(), organizationAnnotation) - - 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 := org.RoleViewer editorRole := org.RoleEditor diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index 65468edd6da..0d45cf55932 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -337,11 +337,6 @@ func setInitCtxSignedInViewer(initCtx *models.ReqContext) { initCtx.SignedInUser = &user.SignedInUser{UserID: testUserID, OrgID: 1, OrgRole: org.RoleViewer, Login: testUserLogin} } -func setInitCtxSignedInEditor(initCtx *models.ReqContext) { - initCtx.IsSignedIn = true - initCtx.SignedInUser = &user.SignedInUser{UserID: testUserID, OrgID: 1, OrgRole: org.RoleEditor, Login: testUserLogin} -} - func setInitCtxSignedInOrgAdmin(initCtx *models.ReqContext) { initCtx.IsSignedIn = true initCtx.SignedInUser = &user.SignedInUser{UserID: testUserID, OrgID: 1, OrgRole: org.RoleAdmin, Login: testUserLogin}