RBAC: Remove service dependency in Evaluator component (#54910)

* RBAC: Remove service dependency for Evaluator component

* RBAC: Add service and load permissions in target org if they are not
there

* RBAC: Use service if we need to load permissions for org

* API: remove service injection into evaluator

* API: set new user for each request in tests

* PublicDashboards: Use fake service to provide permissions

* RBAC: Set org id for dashboard provisioning user
This commit is contained in:
Karl Persson
2022-09-09 09:07:45 +02:00
committed by GitHub
parent e277ab0017
commit bcd7afd1f5
11 changed files with 76 additions and 48 deletions
+12 -1
View File
@@ -93,7 +93,7 @@ type User struct {
}
// HasGlobalAccess checks user access with globally assigned permissions only
func HasGlobalAccess(ac AccessControl, c *models.ReqContext) func(fallback func(*models.ReqContext) bool, evaluator Evaluator) bool {
func HasGlobalAccess(ac AccessControl, service Service, c *models.ReqContext) func(fallback func(*models.ReqContext) bool, evaluator Evaluator) bool {
return func(fallback func(*models.ReqContext) bool, evaluator Evaluator) bool {
if ac.IsDisabled() {
return fallback(c)
@@ -103,12 +103,23 @@ func HasGlobalAccess(ac AccessControl, c *models.ReqContext) func(fallback func(
userCopy.OrgID = GlobalOrgID
userCopy.OrgRole = ""
userCopy.OrgName = ""
if userCopy.Permissions[GlobalOrgID] == nil {
permissions, err := service.GetUserPermissions(c.Req.Context(), &userCopy, Options{})
if err != nil {
c.Logger.Error("failed fetching permissions for user", "userID", userCopy.UserID, "error", err)
}
userCopy.Permissions[GlobalOrgID] = GroupScopesByAction(permissions)
}
hasAccess, err := ac.Evaluate(c.Req.Context(), &userCopy, evaluator)
if err != nil {
c.Logger.Error("Error from access control system", "error", err)
return false
}
// set on user so we don't fetch global permissions every time this is called
c.SignedInUser.Permissions[GlobalOrgID] = userCopy.Permissions[GlobalOrgID]
return hasAccess
}
}
@@ -15,10 +15,10 @@ import (
var _ accesscontrol.AccessControl = new(AccessControl)
func ProvideAccessControl(cfg *setting.Cfg, service accesscontrol.Service) *AccessControl {
func ProvideAccessControl(cfg *setting.Cfg) *AccessControl {
logger := log.New("accesscontrol")
return &AccessControl{
cfg, logger, accesscontrol.NewResolvers(logger), service,
cfg, logger, accesscontrol.NewResolvers(logger),
}
}
@@ -26,7 +26,6 @@ type AccessControl struct {
cfg *setting.Cfg
log log.Logger
resolvers accesscontrol.Resolvers
service accesscontrol.Service
}
func (a *AccessControl) Evaluate(ctx context.Context, user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
@@ -34,18 +33,10 @@ func (a *AccessControl) Evaluate(ctx context.Context, user *user.SignedInUser, e
defer timer.ObserveDuration()
metrics.MAccessEvaluationCount.Inc()
if user.Permissions == nil {
user.Permissions = map[int64]map[string][]string{}
if !verifyPermissions(user) {
a.log.Warn("no permissions set for user", "userID", user.UserID, "orgID", user.OrgID)
return false, nil
}
if _, ok := user.Permissions[user.OrgID]; !ok {
permissions, err := a.service.GetUserPermissions(ctx, user, accesscontrol.Options{ReloadCache: true})
if err != nil {
return false, err
}
user.Permissions[user.OrgID] = accesscontrol.GroupScopesByAction(permissions)
}
// Test evaluation without scope resolver first, this will prevent 403 for wildcard scopes when resource does not exist
if evaluator.Evaluate(user.Permissions[user.OrgID]) {
return true, nil
@@ -69,3 +60,7 @@ func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver a
func (a *AccessControl) IsDisabled() bool {
return accesscontrol.IsDisabled(a.cfg)
}
func verifyPermissions(u *user.SignedInUser) bool {
return u.Permissions != nil || u.Permissions[u.OrgID] != nil
}
@@ -4,8 +4,6 @@ import (
"context"
"testing"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
@@ -65,8 +63,7 @@ func TestAccessControl_Evaluate(t *testing.T) {
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
fakeService := actest.FakeService{}
ac := ProvideAccessControl(setting.NewCfg(), fakeService)
ac := ProvideAccessControl(setting.NewCfg())
if tt.resolver != nil {
ac.RegisterScopeAttributeResolver(tt.resolverPrefix, tt.resolver)
+10 -2
View File
@@ -97,7 +97,7 @@ type userCache interface {
GetSignedInUserWithCacheCtx(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error)
}
func AuthorizeInOrgMiddleware(ac AccessControl, cache userCache) func(web.Handler, OrgIDGetter, Evaluator) web.Handler {
func AuthorizeInOrgMiddleware(ac AccessControl, service Service, cache userCache) func(web.Handler, OrgIDGetter, Evaluator) web.Handler {
return func(fallback web.Handler, getTargetOrg OrgIDGetter, evaluator Evaluator) web.Handler {
if ac.IsDisabled() {
return fallback
@@ -127,10 +127,18 @@ func AuthorizeInOrgMiddleware(ac AccessControl, cache userCache) func(web.Handle
userCopy.OrgRole = queryResult.OrgRole
}
if userCopy.Permissions[userCopy.OrgID] == nil {
permissions, err := service.GetUserPermissions(c.Req.Context(), &userCopy, Options{})
if err != nil {
deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err))
}
userCopy.Permissions[userCopy.OrgID] = GroupScopesByAction(permissions)
}
authorize(c, ac, &userCopy, evaluator)
// Set the sign-ed in user permissions in that org
c.SignedInUser.Permissions = userCopy.Permissions
c.SignedInUser.Permissions[userCopy.OrgID] = userCopy.Permissions[userCopy.OrgID]
}
}
}
@@ -271,6 +271,7 @@ func (dr *DashboardServiceImpl) SaveFolderForProvisionedDashboards(ctx context.C
dto.User = &user.SignedInUser{
UserID: 0,
OrgRole: org.RoleAdmin,
OrgID: dto.OrgId,
Permissions: map[int64]map[string][]string{dto.OrgId: provisionerPermissions},
}
cmd, err := dr.BuildSaveDashboardCommand(ctx, dto, false, false)
@@ -16,8 +16,9 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/database"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@@ -44,26 +45,27 @@ func setupTestServer(
// build router to register routes
rr := routing.NewRouteRegister()
// build access control - FIXME we should be able to mock this, but to get
// tests going, we're going to instantiate full accesscontrol
//ac := accesscontrolmock.New()
//ac.WithDisabled()
// create a sqlstore for access control.
if db == nil {
db = sqlstore.InitTestDB(t)
var permissions []accesscontrol.Permission
if user != nil && user.Permissions != nil {
for action, scopes := range user.Permissions[user.OrgID] {
for _, scope := range scopes {
permissions = append(permissions, accesscontrol.Permission{
Action: action,
Scope: scope,
})
}
}
}
var err error
acService, err := acimpl.ProvideService(cfg, database.ProvideService(db), rr)
require.NoError(t, err)
ac := acimpl.ProvideAccessControl(cfg, acService)
acService := actest.FakeService{ExpectedPermissions: permissions, ExpectedDisabled: !cfg.RBACEnabled}
ac := acimpl.ProvideAccessControl(cfg)
// build mux
m := web.New()
// set initial context
m.Use(contextProvider(&testContext{user}))
m.Use(accesscontrol.LoadPermissionsMiddleware(acService))
// build api, this will mount the routes at the same time if
// featuremgmt.FlagPublicDashboard is enabled