diff --git a/pkg/api/api.go b/pkg/api/api.go index 9b1f92e4146..86d8925c404 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -66,7 +66,7 @@ func (hs *HTTPServer) registerRoutes() { reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg) redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg) authorize := ac.Middleware(hs.AccessControl) - authorizeInOrg := ac.AuthorizeInOrgMiddleware(hs.AccessControl, hs.userService) + authorizeInOrg := ac.AuthorizeInOrgMiddleware(hs.AccessControl, hs.accesscontrolService, hs.userService) quota := middleware.Quota(hs.QuotaService) r := hs.RouteRegister diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index ca5f7d88e0e..74111f26669 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -381,7 +381,7 @@ func setupHTTPServerWithCfgDb( var err error acService, err = acimpl.ProvideService(cfg, database.ProvideService(db), routeRegister) require.NoError(t, err) - ac = acimpl.ProvideAccessControl(cfg, acService) + ac = acimpl.ProvideAccessControl(cfg) } teamPermissionService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, routeRegister, db, ac, license, acService) diff --git a/pkg/api/index.go b/pkg/api/index.go index a2eb513aabe..1fd9cea32a9 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -714,7 +714,7 @@ func (hs *HTTPServer) buildDataConnectionsNavLink(c *models.ReqContext) *dtos.Na func (hs *HTTPServer) buildAdminNavLinks(c *models.ReqContext) []*dtos.NavLink { hasAccess := ac.HasAccess(hs.AccessControl, c) - hasGlobalAccess := ac.HasGlobalAccess(hs.AccessControl, c) + hasGlobalAccess := ac.HasGlobalAccess(hs.AccessControl, hs.accesscontrolService, c) adminNavLinks := []*dtos.NavLink{} if hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)) { diff --git a/pkg/api/org_test.go b/pkg/api/org_test.go index f8a6b569dec..91259753faa 100644 --- a/pkg/api/org_test.go +++ b/pkg/api/org_test.go @@ -238,12 +238,11 @@ func TestAPIEndpoint_CreateOrgs_LegacyAccessControl(t *testing.T) { func TestAPIEndpoint_CreateOrgs_AccessControl(t *testing.T) { sc := setupHTTPServer(t, true) - setInitCtxSignedInViewer(sc.initCtx) - setupOrgsDBForAccessControlTests(t, sc.db, sc, 0) input := strings.NewReader(fmt.Sprintf(testCreateOrgCmd, 2)) t.Run("AccessControl allows creating Orgs with correct permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsCreate}}, accesscontrol.GlobalOrgID) response := callAPI(sc.server, http.MethodPost, createOrgsURL, input, t) assert.Equal(t, http.StatusOK, response.Code) @@ -251,6 +250,7 @@ func TestAPIEndpoint_CreateOrgs_AccessControl(t *testing.T) { input = strings.NewReader(fmt.Sprintf(testCreateOrgCmd, 3)) t.Run("AccessControl prevents creating Orgs with incorrect permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, accesscontrol.GlobalOrgID) response := callAPI(sc.server, http.MethodPost, createOrgsURL, input, t) assert.Equal(t, http.StatusForbidden, response.Code) @@ -282,16 +282,19 @@ func TestAPIEndpoint_DeleteOrgs_AccessControl(t *testing.T) { setupOrgsDBForAccessControlTests(t, sc.db, sc, 2) t.Run("AccessControl prevents deleting Orgs with incorrect permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, 2) response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t) assert.Equal(t, http.StatusForbidden, response.Code) }) t.Run("AccessControl prevents deleting Orgs with correct permissions in another org", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsDelete}}, 1) response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t) assert.Equal(t, http.StatusForbidden, response.Code) }) t.Run("AccessControl allows deleting Orgs with correct permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsDelete}}, 2) response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t) assert.Equal(t, http.StatusOK, response.Code) @@ -318,19 +321,21 @@ func TestAPIEndpoint_SearchOrgs_LegacyAccessControl(t *testing.T) { func TestAPIEndpoint_SearchOrgs_AccessControl(t *testing.T) { sc := setupHTTPServer(t, true) - setInitCtxSignedInViewer(sc.initCtx) t.Run("AccessControl allows listing Orgs with correct permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsRead}}, accesscontrol.GlobalOrgID) response := callAPI(sc.server, http.MethodGet, searchOrgsURL, nil, t) assert.Equal(t, http.StatusOK, response.Code) }) t.Run("AccessControl prevents listing Orgs with correct permissions not granted globally", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsRead}}, 1) response := callAPI(sc.server, http.MethodGet, searchOrgsURL, nil, t) assert.Equal(t, http.StatusForbidden, response.Code) }) t.Run("AccessControl prevents listing Orgs with incorrect permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, accesscontrol.GlobalOrgID) response := callAPI(sc.server, http.MethodGet, searchOrgsURL, nil, t) assert.Equal(t, http.StatusForbidden, response.Code) @@ -360,22 +365,24 @@ func TestAPIEndpoint_GetOrg_LegacyAccessControl(t *testing.T) { func TestAPIEndpoint_GetOrg_AccessControl(t *testing.T) { sc := setupHTTPServer(t, true) - setInitCtxSignedInViewer(sc.initCtx) // Create two orgs, to fetch another one than the logged in one setupOrgsDBForAccessControlTests(t, sc.db, sc, 2) t.Run("AccessControl allows viewing another org with correct permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsRead}}, 2) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t) assert.Equal(t, http.StatusOK, response.Code) }) t.Run("AccessControl prevents viewing another org with correct permissions in another org", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsRead}}, 1) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t) assert.Equal(t, http.StatusForbidden, response.Code) }) t.Run("AccessControl prevents viewing another org with incorrect permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, 2) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t) assert.Equal(t, http.StatusForbidden, response.Code) @@ -405,17 +412,18 @@ func TestAPIEndpoint_GetOrgByName_LegacyAccessControl(t *testing.T) { func TestAPIEndpoint_GetOrgByName_AccessControl(t *testing.T) { sc := setupHTTPServer(t, true) - setInitCtxSignedInViewer(sc.initCtx) // Create two orgs, to fetch another one than the logged in one setupOrgsDBForAccessControlTests(t, sc.db, sc, 2) t.Run("AccessControl allows viewing another org with correct permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsRead}}, accesscontrol.GlobalOrgID) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsByNameURL, "TestOrg2"), nil, t) assert.Equal(t, http.StatusOK, response.Code) }) t.Run("AccessControl prevents viewing another org with incorrect permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, accesscontrol.GlobalOrgID) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsByNameURL, "TestOrg2"), nil, t) assert.Equal(t, http.StatusForbidden, response.Code) @@ -447,25 +455,27 @@ func TestAPIEndpoint_PutOrg_LegacyAccessControl(t *testing.T) { func TestAPIEndpoint_PutOrg_AccessControl(t *testing.T) { sc := setupHTTPServer(t, true) - setInitCtxSignedInViewer(sc.initCtx) sc.hs.orgService = orgimpl.ProvideService(sc.db, sc.cfg) // Create two orgs, to update another one than the logged in one setupOrgsDBForAccessControlTests(t, sc.db, sc, 2) input := strings.NewReader(testUpdateOrgNameForm) t.Run("AccessControl allows updating another org with correct permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsWrite}}, 2) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsURL, 2), input, t) assert.Equal(t, http.StatusOK, response.Code) }) t.Run("AccessControl prevents updating another org with correct permissions in another org", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsWrite}}, 1) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsURL, 2), input, t) assert.Equal(t, http.StatusForbidden, response.Code) }) t.Run("AccessControl prevents updating another org with incorrect permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, 2) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsURL, 2), input, t) assert.Equal(t, http.StatusForbidden, response.Code) @@ -497,13 +507,13 @@ func TestAPIEndpoint_PutOrgAddress_LegacyAccessControl(t *testing.T) { func TestAPIEndpoint_PutOrgAddress_AccessControl(t *testing.T) { sc := setupHTTPServer(t, true) - setInitCtxSignedInViewer(sc.initCtx) // Create two orgs, to update another one than the logged in one setupOrgsDBForAccessControlTests(t, sc.db, sc, 2) input := strings.NewReader(testUpdateOrgAddressForm) t.Run("AccessControl allows updating another org address with correct permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsWrite}}, 2) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsAddressURL, 2), input, t) assert.Equal(t, http.StatusOK, response.Code) @@ -511,12 +521,14 @@ func TestAPIEndpoint_PutOrgAddress_AccessControl(t *testing.T) { input = strings.NewReader(testUpdateOrgAddressForm) t.Run("AccessControl prevents updating another org address with correct permissions in the current org", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsWrite}}, 1) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsAddressURL, 2), input, t) assert.Equal(t, http.StatusForbidden, response.Code) }) t.Run("AccessControl prevents updating another org address with incorrect permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, 2) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsAddressURL, 2), input, t) assert.Equal(t, http.StatusForbidden, response.Code) diff --git a/pkg/api/quota_test.go b/pkg/api/quota_test.go index 7dd3462fe7c..292cb22a1f8 100644 --- a/pkg/api/quota_test.go +++ b/pkg/api/quota_test.go @@ -106,21 +106,22 @@ func TestAPIEndpoint_GetOrgQuotas_LegacyAccessControl(t *testing.T) { func TestAPIEndpoint_GetOrgQuotas_AccessControl(t *testing.T) { sc := setupHTTPServer(t, true) - setInitCtxSignedInViewer(sc.initCtx) - setupDBAndSettingsForAccessControlQuotaTests(t, sc) t.Run("AccessControl allows viewing another org quotas with correct permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsQuotasRead}}, 2) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t) assert.Equal(t, http.StatusOK, response.Code) }) t.Run("AccessControl prevents viewing another org quotas with correct permissions in another org", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsQuotasRead}}, 1) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t) assert.Equal(t, http.StatusForbidden, response.Code) }) t.Run("AccessControl prevents viewing another org quotas with incorrect permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, 2) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t) assert.Equal(t, http.StatusForbidden, response.Code) @@ -151,12 +152,11 @@ func TestAPIEndpoint_PutOrgQuotas_LegacyAccessControl(t *testing.T) { func TestAPIEndpoint_PutOrgQuotas_AccessControl(t *testing.T) { sc := setupHTTPServer(t, true) - setInitCtxSignedInViewer(sc.initCtx) - setupDBAndSettingsForAccessControlQuotaTests(t, sc) input := strings.NewReader(testUpdateOrgQuotaCmd) t.Run("AccessControl allows updating another org quotas with correct permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsQuotasWrite}}, 2) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input, t) assert.Equal(t, http.StatusOK, response.Code) @@ -164,6 +164,7 @@ func TestAPIEndpoint_PutOrgQuotas_AccessControl(t *testing.T) { input = strings.NewReader(testUpdateOrgQuotaCmd) t.Run("AccessControl prevents updating another org quotas with correct permissions in another org", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsQuotasWrite}}, 1) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input, t) assert.Equal(t, http.StatusForbidden, response.Code) @@ -171,6 +172,7 @@ func TestAPIEndpoint_PutOrgQuotas_AccessControl(t *testing.T) { input = strings.NewReader(testUpdateOrgQuotaCmd) t.Run("AccessControl prevents updating another org quotas with incorrect permissions", func(t *testing.T) { + setInitCtxSignedInViewer(sc.initCtx) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, 2) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input, t) assert.Equal(t, http.StatusForbidden, response.Code) diff --git a/pkg/services/accesscontrol/accesscontrol.go b/pkg/services/accesscontrol/accesscontrol.go index 91e1bfab902..fb8cd018618 100644 --- a/pkg/services/accesscontrol/accesscontrol.go +++ b/pkg/services/accesscontrol/accesscontrol.go @@ -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 } } diff --git a/pkg/services/accesscontrol/acimpl/accesscontrol.go b/pkg/services/accesscontrol/acimpl/accesscontrol.go index 3f3c8c04b9b..cbfd9d0aa0e 100644 --- a/pkg/services/accesscontrol/acimpl/accesscontrol.go +++ b/pkg/services/accesscontrol/acimpl/accesscontrol.go @@ -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 +} diff --git a/pkg/services/accesscontrol/acimpl/accesscontrol_test.go b/pkg/services/accesscontrol/acimpl/accesscontrol_test.go index c2bbcbc2b64..4c53ee45aea 100644 --- a/pkg/services/accesscontrol/acimpl/accesscontrol_test.go +++ b/pkg/services/accesscontrol/acimpl/accesscontrol_test.go @@ -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) diff --git a/pkg/services/accesscontrol/middleware.go b/pkg/services/accesscontrol/middleware.go index a23937f91cc..75845018f8a 100644 --- a/pkg/services/accesscontrol/middleware.go +++ b/pkg/services/accesscontrol/middleware.go @@ -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] } } } diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index f981915b264..b793d513b10 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -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) diff --git a/pkg/services/publicdashboards/api/common_test.go b/pkg/services/publicdashboards/api/common_test.go index 6fe51bf0893..0e64857dbe3 100644 --- a/pkg/services/publicdashboards/api/common_test.go +++ b/pkg/services/publicdashboards/api/common_test.go @@ -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