RBAC: Rewrite rbac annotations test (#61036)
* API: Rewrite annotation rbac tests to not use mocked access control * API: rewrite mass deletion rbac tests
This commit is contained in:
+220
-517
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -14,10 +15,10 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
"github.com/grafana/grafana/pkg/api/routing"
|
"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/infra/db"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"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"
|
||||||
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
|
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"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/org"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
|
||||||
"github.com/grafana/grafana/pkg/services/team/teamtest"
|
"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) {
|
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) {
|
func TestAPI_Annotations_AccessControl(t *testing.T) {
|
||||||
sc := setupHTTPServer(t, true)
|
type testCase struct {
|
||||||
setInitCtxSignedInEditor(sc.initCtx)
|
desc string
|
||||||
_, err := sc.hs.orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "TestOrg", UserID: testUserID})
|
path string
|
||||||
require.NoError(t, err)
|
method string
|
||||||
|
body string
|
||||||
origNewGuardian := guardian.New
|
expectedCode int
|
||||||
t.Cleanup(func() {
|
permissions []accesscontrol.Permission
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
postDashboardCmd := dtos.PostAnnotationsCmd{
|
tests := []testCase{
|
||||||
Time: 1000,
|
{
|
||||||
Text: "annotation text",
|
desc: "should be able to fetch annotations with correct permission",
|
||||||
Tags: []string{"tag1", "tag2"},
|
path: "/api/annotations",
|
||||||
DashboardId: 1,
|
method: http.MethodGet,
|
||||||
PanelId: 1,
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
setUpRBACGuardian(t)
|
setUpRBACGuardian(t)
|
||||||
sc.acmock.
|
server := SetupAPITestServer(t, func(hs *HTTPServer) {
|
||||||
RegisterScopeAttributeResolver(AnnotationTypeScopeResolver(sc.hs.annotationsRepo))
|
hs.Cfg = setting.NewCfg()
|
||||||
setAccessControlPermissions(sc.acmock, tt.args.permissions, sc.initCtx.OrgID)
|
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)
|
req := webtest.RequestWithSignedInUser(server.NewRequest(tt.method, tt.path, body), userWithPermissions(1, tt.permissions))
|
||||||
assert.Equalf(t, tt.want, r.Code, "Annotations API(%v)", tt.args.url)
|
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) {
|
func TestService_AnnotationTypeScopeResolver(t *testing.T) {
|
||||||
type testCaseResolver struct {
|
type testCaseResolver struct {
|
||||||
desc string
|
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() {
|
func setUpACL() {
|
||||||
viewerRole := org.RoleViewer
|
viewerRole := org.RoleViewer
|
||||||
editorRole := org.RoleEditor
|
editorRole := org.RoleEditor
|
||||||
|
|||||||
@@ -337,11 +337,6 @@ func setInitCtxSignedInViewer(initCtx *models.ReqContext) {
|
|||||||
initCtx.SignedInUser = &user.SignedInUser{UserID: testUserID, OrgID: 1, OrgRole: org.RoleViewer, Login: testUserLogin}
|
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) {
|
func setInitCtxSignedInOrgAdmin(initCtx *models.ReqContext) {
|
||||||
initCtx.IsSignedIn = true
|
initCtx.IsSignedIn = true
|
||||||
initCtx.SignedInUser = &user.SignedInUser{UserID: testUserID, OrgID: 1, OrgRole: org.RoleAdmin, Login: testUserLogin}
|
initCtx.SignedInUser = &user.SignedInUser{UserID: testUserID, OrgID: 1, OrgRole: org.RoleAdmin, Login: testUserLogin}
|
||||||
|
|||||||
Reference in New Issue
Block a user