diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index d836cdfa1c8..5145c2965c0 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -1,7 +1,6 @@ package api import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -16,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/accesscontrol" + accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth/jwt" "github.com/grafana/grafana/pkg/services/contexthandler" @@ -229,33 +229,6 @@ func (s *fakeRenderService) Init() error { return nil } -var _ accesscontrol.AccessControl = new(fakeAccessControl) - -type fakeAccessControl struct { - isDisabled bool - permissions []*accesscontrol.Permission -} - -func (f *fakeAccessControl) Evaluate(ctx context.Context, user *models.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) { - permissions, err := f.GetUserPermissions(ctx, user) - if err != nil { - return false, err - } - return evaluator.Evaluate(accesscontrol.GroupScopesByAction(permissions)) -} - -func (f *fakeAccessControl) GetUserPermissions(ctx context.Context, user *models.SignedInUser) ([]*accesscontrol.Permission, error) { - return f.permissions, nil -} - -func (f *fakeAccessControl) IsDisabled() bool { - return f.isDisabled -} - -func (f *fakeAccessControl) DeclareFixedRoles(registrations ...accesscontrol.RoleRegistration) error { - return nil -} - func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url string, permissions []*accesscontrol.Permission) (*scenarioContext, *HTTPServer) { cfg.FeatureToggles = make(map[string]bool) cfg.FeatureToggles["accesscontrol"] = true @@ -263,7 +236,7 @@ func setupAccessControlScenarioContext(t *testing.T, cfg *setting.Cfg, url strin hs := &HTTPServer{ Cfg: cfg, RouteRegister: routing.NewRouteRegister(), - AccessControl: &fakeAccessControl{permissions: permissions}, + AccessControl: accesscontrolmock.New().WithPermissions(permissions), } sc := setupScenarioContext(t, url) diff --git a/pkg/services/accesscontrol/middleware/middleware_test.go b/pkg/services/accesscontrol/middleware/middleware_test.go index ad38dc2e574..6c52365b9ae 100644 --- a/pkg/services/accesscontrol/middleware/middleware_test.go +++ b/pkg/services/accesscontrol/middleware/middleware_test.go @@ -1,7 +1,6 @@ package middleware import ( - "context" "net/http" "net/http/httptest" "testing" @@ -13,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/mock" ) type middlewareTestCase struct { @@ -27,26 +27,24 @@ func TestMiddleware(t *testing.T) { tests := []middlewareTestCase{ { desc: "should use fallback if access control is disabled", - ac: fakeAccessControl{isDisabled: true}, + ac: mock.New().WithDisabled(), expectFallback: true, expectEndpoint: true, }, { desc: "should pass middleware for correct permissions", - ac: fakeAccessControl{ - isDisabled: false, - permissions: []*accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}}, - }, + ac: mock.New().WithPermissions( + []*accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}}, + ), evaluator: accesscontrol.EvalPermission("users:read", "users:*"), expectFallback: false, expectEndpoint: true, }, { desc: "should not reach endpoint when missing permissions", - ac: fakeAccessControl{ - isDisabled: false, - permissions: []*accesscontrol.Permission{{Action: "users:read", Scope: "users:1"}}, - }, + ac: mock.New().WithPermissions( + []*accesscontrol.Permission{{Action: "users:read", Scope: "users:1"}}, + ), evaluator: accesscontrol.EvalPermission("users:read", "users:*"), expectFallback: false, expectEndpoint: false, @@ -95,27 +93,3 @@ func contextProvider() macaron.Handler { c.Map(reqCtx) } } - -var _ accesscontrol.AccessControl = new(fakeAccessControl) - -type fakeAccessControl struct { - isDisabled bool - permissions []*accesscontrol.Permission -} - -func (f fakeAccessControl) Evaluate(ctx context.Context, user *models.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) { - permissions, _ := f.GetUserPermissions(ctx, user) - return evaluator.Evaluate(accesscontrol.GroupScopesByAction(permissions)) -} - -func (f fakeAccessControl) GetUserPermissions(ctx context.Context, user *models.SignedInUser) ([]*accesscontrol.Permission, error) { - return f.permissions, nil -} - -func (f fakeAccessControl) IsDisabled() bool { - return f.isDisabled -} - -func (f fakeAccessControl) DeclareFixedRoles(registration ...accesscontrol.RoleRegistration) error { - return nil -} diff --git a/pkg/services/accesscontrol/mock/mock.go b/pkg/services/accesscontrol/mock/mock.go new file mode 100644 index 00000000000..e2f6d91c0e8 --- /dev/null +++ b/pkg/services/accesscontrol/mock/mock.go @@ -0,0 +1,152 @@ +package mock + +import ( + "context" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/accesscontrol" +) + +type fullAccessControl interface { + accesscontrol.AccessControl + GetUserBuiltInRoles(user *models.SignedInUser) []string + RegisterFixedRoles() error +} + +type Calls struct { + Evaluate []interface{} + GetUserPermissions []interface{} + IsDisabled []interface{} + DeclareFixedRoles []interface{} + GetUserBuiltInRoles []interface{} + RegisterFixedRoles []interface{} +} + +type Mock struct { + // Unless an override is provided, permissions will be returned by GetUserPermissions + permissions []*accesscontrol.Permission + // Unless an override is provided, disabled will be returned by IsDisabled + disabled bool + // Unless an override is provided, builtInRoles will be returned by GetUserBuiltInRoles + builtInRoles []string + + // Track the list of calls + Calls Calls + + // Override functions + EvaluateFunc func(context.Context, *models.SignedInUser, accesscontrol.Evaluator) (bool, error) + GetUserPermissionsFunc func(context.Context, *models.SignedInUser) ([]*accesscontrol.Permission, error) + IsDisabledFunc func() bool + DeclareFixedRolesFunc func(...accesscontrol.RoleRegistration) error + GetUserBuiltInRolesFunc func(user *models.SignedInUser) []string + RegisterFixedRolesFunc func() error +} + +type MockOptions func(*Mock) + +// Ensure the mock stays in line with the interface +var _ fullAccessControl = New() + +func New() *Mock { + mock := &Mock{ + Calls: Calls{}, + disabled: false, + permissions: []*accesscontrol.Permission{}, + builtInRoles: []string{}, + } + + return mock +} + +func (m Mock) WithPermissions(permissions []*accesscontrol.Permission) *Mock { + m.permissions = permissions + return &m +} + +func (m Mock) WithDisabled() *Mock { + m.disabled = true + return &m +} + +func (m Mock) WithBuiltInRoles(builtInRoles []string) *Mock { + m.builtInRoles = builtInRoles + return &m +} + +// Evaluate evaluates access to the given resource. +// This mock uses GetUserPermissions to then call the evaluator Evaluate function. +func (m *Mock) Evaluate(ctx context.Context, user *models.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) { + m.Calls.Evaluate = append(m.Calls.Evaluate, []interface{}{ctx, user, evaluator}) + // Use override if provided + if m.EvaluateFunc != nil { + return m.EvaluateFunc(ctx, user, evaluator) + } + // Otherwise perform an actual evaluation of the permissions + permissions, err := m.GetUserPermissions(ctx, user) + if err != nil { + return false, err + } + return evaluator.Evaluate(accesscontrol.GroupScopesByAction(permissions)) +} + +// GetUserPermissions returns user permissions. +// This mock return m.permissions unless an override is provided. +func (m *Mock) GetUserPermissions(ctx context.Context, user *models.SignedInUser) ([]*accesscontrol.Permission, error) { + m.Calls.GetUserPermissions = append(m.Calls.GetUserPermissions, []interface{}{ctx, user}) + // Use override if provided + if m.GetUserPermissionsFunc != nil { + return m.GetUserPermissionsFunc(ctx, user) + } + // Otherwise return the Permissions list + return m.permissions, nil +} + +// Middleware checks if service disabled or not to switch to fallback authorization. +// This mock return m.disabled unless an override is provided. +func (m *Mock) IsDisabled() bool { + m.Calls.IsDisabled = append(m.Calls.IsDisabled, struct{}{}) + // Use override if provided + if m.IsDisabledFunc != nil { + return m.IsDisabledFunc() + } + // Otherwise return the Disabled bool + return m.disabled +} + +// DeclareFixedRoles allow the caller to declare, to the service, fixed roles and their +// assignments to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin" +// This mock returns no error unless an override is provided. +func (m *Mock) DeclareFixedRoles(registrations ...accesscontrol.RoleRegistration) error { + m.Calls.DeclareFixedRoles = append(m.Calls.DeclareFixedRoles, []interface{}{registrations}) + // Use override if provided + if m.DeclareFixedRolesFunc != nil { + return m.DeclareFixedRolesFunc(registrations...) + } + return nil +} + +// GetUserBuiltInRoles returns the list of organizational roles ("Viewer", "Editor", "Admin") +// or "Grafana Admin" associated to a user +// This mock returns m.builtInRoles unless an override is provided. +func (m *Mock) GetUserBuiltInRoles(user *models.SignedInUser) []string { + m.Calls.GetUserBuiltInRoles = append(m.Calls.GetUserBuiltInRoles, []interface{}{user}) + + // Use override if provided + if m.GetUserBuiltInRolesFunc != nil { + return m.GetUserBuiltInRolesFunc(user) + } + + // Otherwise return the BuiltInRoles list + return m.builtInRoles +} + +// RegisterFixedRoles registers all roles declared to AccessControl +// This mock returns no error unless an override is provided. +func (m *Mock) RegisterFixedRoles() error { + m.Calls.RegisterFixedRoles = append(m.Calls.RegisterFixedRoles, []struct{}{}) + // Use override if provided + if m.RegisterFixedRolesFunc != nil { + return m.RegisterFixedRolesFunc() + } + return nil +}