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
upsert-leak
Karl Persson 3 years ago committed by GitHub
parent e277ab0017
commit bcd7afd1f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      pkg/api/api.go
  2. 2
      pkg/api/common_test.go
  3. 2
      pkg/api/index.go
  4. 26
      pkg/api/org_test.go
  5. 10
      pkg/api/quota_test.go
  6. 13
      pkg/services/accesscontrol/accesscontrol.go
  7. 23
      pkg/services/accesscontrol/acimpl/accesscontrol.go
  8. 5
      pkg/services/accesscontrol/acimpl/accesscontrol_test.go
  9. 12
      pkg/services/accesscontrol/middleware.go
  10. 1
      pkg/services/dashboards/service/dashboard_service.go
  11. 28
      pkg/services/publicdashboards/api/common_test.go

@ -66,7 +66,7 @@ func (hs *HTTPServer) registerRoutes() {
reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg) reqSnapshotPublicModeOrSignedIn := middleware.SnapshotPublicModeOrSignedIn(hs.Cfg)
redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg) redirectFromLegacyPanelEditURL := middleware.RedirectFromLegacyPanelEditURL(hs.Cfg)
authorize := ac.Middleware(hs.AccessControl) 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) quota := middleware.Quota(hs.QuotaService)
r := hs.RouteRegister r := hs.RouteRegister

@ -381,7 +381,7 @@ func setupHTTPServerWithCfgDb(
var err error var err error
acService, err = acimpl.ProvideService(cfg, database.ProvideService(db), routeRegister) acService, err = acimpl.ProvideService(cfg, database.ProvideService(db), routeRegister)
require.NoError(t, err) require.NoError(t, err)
ac = acimpl.ProvideAccessControl(cfg, acService) ac = acimpl.ProvideAccessControl(cfg)
} }
teamPermissionService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, routeRegister, db, ac, license, acService) teamPermissionService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, routeRegister, db, ac, license, acService)

@ -714,7 +714,7 @@ func (hs *HTTPServer) buildDataConnectionsNavLink(c *models.ReqContext) *dtos.Na
func (hs *HTTPServer) buildAdminNavLinks(c *models.ReqContext) []*dtos.NavLink { func (hs *HTTPServer) buildAdminNavLinks(c *models.ReqContext) []*dtos.NavLink {
hasAccess := ac.HasAccess(hs.AccessControl, c) hasAccess := ac.HasAccess(hs.AccessControl, c)
hasGlobalAccess := ac.HasGlobalAccess(hs.AccessControl, c) hasGlobalAccess := ac.HasGlobalAccess(hs.AccessControl, hs.accesscontrolService, c)
adminNavLinks := []*dtos.NavLink{} adminNavLinks := []*dtos.NavLink{}
if hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)) { if hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll)) {

@ -238,12 +238,11 @@ func TestAPIEndpoint_CreateOrgs_LegacyAccessControl(t *testing.T) {
func TestAPIEndpoint_CreateOrgs_AccessControl(t *testing.T) { func TestAPIEndpoint_CreateOrgs_AccessControl(t *testing.T) {
sc := setupHTTPServer(t, true) sc := setupHTTPServer(t, true)
setInitCtxSignedInViewer(sc.initCtx)
setupOrgsDBForAccessControlTests(t, sc.db, sc, 0) setupOrgsDBForAccessControlTests(t, sc.db, sc, 0)
input := strings.NewReader(fmt.Sprintf(testCreateOrgCmd, 2)) input := strings.NewReader(fmt.Sprintf(testCreateOrgCmd, 2))
t.Run("AccessControl allows creating Orgs with correct permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsCreate}}, accesscontrol.GlobalOrgID)
response := callAPI(sc.server, http.MethodPost, createOrgsURL, input, t) response := callAPI(sc.server, http.MethodPost, createOrgsURL, input, t)
assert.Equal(t, http.StatusOK, response.Code) 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)) input = strings.NewReader(fmt.Sprintf(testCreateOrgCmd, 3))
t.Run("AccessControl prevents creating Orgs with incorrect permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, accesscontrol.GlobalOrgID)
response := callAPI(sc.server, http.MethodPost, createOrgsURL, input, t) response := callAPI(sc.server, http.MethodPost, createOrgsURL, input, t)
assert.Equal(t, http.StatusForbidden, response.Code) assert.Equal(t, http.StatusForbidden, response.Code)
@ -282,16 +282,19 @@ func TestAPIEndpoint_DeleteOrgs_AccessControl(t *testing.T) {
setupOrgsDBForAccessControlTests(t, sc.db, sc, 2) setupOrgsDBForAccessControlTests(t, sc.db, sc, 2)
t.Run("AccessControl prevents deleting Orgs with incorrect permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, 2)
response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t) response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t)
assert.Equal(t, http.StatusForbidden, response.Code) assert.Equal(t, http.StatusForbidden, response.Code)
}) })
t.Run("AccessControl prevents deleting Orgs with correct permissions in another org", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsDelete}}, 1)
response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t) response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t)
assert.Equal(t, http.StatusForbidden, response.Code) assert.Equal(t, http.StatusForbidden, response.Code)
}) })
t.Run("AccessControl allows deleting Orgs with correct permissions", func(t *testing.T) { t.Run("AccessControl allows deleting Orgs with correct permissions", func(t *testing.T) {
setInitCtxSignedInViewer(sc.initCtx)
setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsDelete}}, 2) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsDelete}}, 2)
response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t) response := callAPI(sc.server, http.MethodDelete, fmt.Sprintf(deleteOrgsURL, 2), nil, t)
assert.Equal(t, http.StatusOK, response.Code) 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) { func TestAPIEndpoint_SearchOrgs_AccessControl(t *testing.T) {
sc := setupHTTPServer(t, true) sc := setupHTTPServer(t, true)
setInitCtxSignedInViewer(sc.initCtx)
t.Run("AccessControl allows listing Orgs with correct permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsRead}}, accesscontrol.GlobalOrgID)
response := callAPI(sc.server, http.MethodGet, searchOrgsURL, nil, t) response := callAPI(sc.server, http.MethodGet, searchOrgsURL, nil, t)
assert.Equal(t, http.StatusOK, response.Code) assert.Equal(t, http.StatusOK, response.Code)
}) })
t.Run("AccessControl prevents listing Orgs with correct permissions not granted globally", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsRead}}, 1)
response := callAPI(sc.server, http.MethodGet, searchOrgsURL, nil, t) response := callAPI(sc.server, http.MethodGet, searchOrgsURL, nil, t)
assert.Equal(t, http.StatusForbidden, response.Code) assert.Equal(t, http.StatusForbidden, response.Code)
}) })
t.Run("AccessControl prevents listing Orgs with incorrect permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, accesscontrol.GlobalOrgID)
response := callAPI(sc.server, http.MethodGet, searchOrgsURL, nil, t) response := callAPI(sc.server, http.MethodGet, searchOrgsURL, nil, t)
assert.Equal(t, http.StatusForbidden, response.Code) 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) { func TestAPIEndpoint_GetOrg_AccessControl(t *testing.T) {
sc := setupHTTPServer(t, true) sc := setupHTTPServer(t, true)
setInitCtxSignedInViewer(sc.initCtx)
// Create two orgs, to fetch another one than the logged in one // Create two orgs, to fetch another one than the logged in one
setupOrgsDBForAccessControlTests(t, sc.db, sc, 2) setupOrgsDBForAccessControlTests(t, sc.db, sc, 2)
t.Run("AccessControl allows viewing another org with correct permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsRead}}, 2)
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t)
assert.Equal(t, http.StatusOK, response.Code) assert.Equal(t, http.StatusOK, response.Code)
}) })
t.Run("AccessControl prevents viewing another org with correct permissions in another org", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsRead}}, 1)
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t)
assert.Equal(t, http.StatusForbidden, response.Code) assert.Equal(t, http.StatusForbidden, response.Code)
}) })
t.Run("AccessControl prevents viewing another org with incorrect permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, 2)
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsURL, 2), nil, t)
assert.Equal(t, http.StatusForbidden, response.Code) 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) { func TestAPIEndpoint_GetOrgByName_AccessControl(t *testing.T) {
sc := setupHTTPServer(t, true) sc := setupHTTPServer(t, true)
setInitCtxSignedInViewer(sc.initCtx)
// Create two orgs, to fetch another one than the logged in one // Create two orgs, to fetch another one than the logged in one
setupOrgsDBForAccessControlTests(t, sc.db, sc, 2) setupOrgsDBForAccessControlTests(t, sc.db, sc, 2)
t.Run("AccessControl allows viewing another org with correct permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsRead}}, accesscontrol.GlobalOrgID)
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsByNameURL, "TestOrg2"), nil, t) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsByNameURL, "TestOrg2"), nil, t)
assert.Equal(t, http.StatusOK, response.Code) assert.Equal(t, http.StatusOK, response.Code)
}) })
t.Run("AccessControl prevents viewing another org with incorrect permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, accesscontrol.GlobalOrgID)
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsByNameURL, "TestOrg2"), nil, t) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsByNameURL, "TestOrg2"), nil, t)
assert.Equal(t, http.StatusForbidden, response.Code) 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) { func TestAPIEndpoint_PutOrg_AccessControl(t *testing.T) {
sc := setupHTTPServer(t, true) sc := setupHTTPServer(t, true)
setInitCtxSignedInViewer(sc.initCtx)
sc.hs.orgService = orgimpl.ProvideService(sc.db, sc.cfg) sc.hs.orgService = orgimpl.ProvideService(sc.db, sc.cfg)
// Create two orgs, to update another one than the logged in one // Create two orgs, to update another one than the logged in one
setupOrgsDBForAccessControlTests(t, sc.db, sc, 2) setupOrgsDBForAccessControlTests(t, sc.db, sc, 2)
input := strings.NewReader(testUpdateOrgNameForm) input := strings.NewReader(testUpdateOrgNameForm)
t.Run("AccessControl allows updating another org with correct permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsWrite}}, 2)
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsURL, 2), input, t) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsURL, 2), input, t)
assert.Equal(t, http.StatusOK, response.Code) assert.Equal(t, http.StatusOK, response.Code)
}) })
t.Run("AccessControl prevents updating another org with correct permissions in another org", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsWrite}}, 1)
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsURL, 2), input, t) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsURL, 2), input, t)
assert.Equal(t, http.StatusForbidden, response.Code) assert.Equal(t, http.StatusForbidden, response.Code)
}) })
t.Run("AccessControl prevents updating another org with incorrect permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, 2)
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsURL, 2), input, t) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsURL, 2), input, t)
assert.Equal(t, http.StatusForbidden, response.Code) 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) { func TestAPIEndpoint_PutOrgAddress_AccessControl(t *testing.T) {
sc := setupHTTPServer(t, true) sc := setupHTTPServer(t, true)
setInitCtxSignedInViewer(sc.initCtx)
// Create two orgs, to update another one than the logged in one // Create two orgs, to update another one than the logged in one
setupOrgsDBForAccessControlTests(t, sc.db, sc, 2) setupOrgsDBForAccessControlTests(t, sc.db, sc, 2)
input := strings.NewReader(testUpdateOrgAddressForm) input := strings.NewReader(testUpdateOrgAddressForm)
t.Run("AccessControl allows updating another org address with correct permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsWrite}}, 2)
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsAddressURL, 2), input, t) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsAddressURL, 2), input, t)
assert.Equal(t, http.StatusOK, response.Code) assert.Equal(t, http.StatusOK, response.Code)
@ -511,12 +521,14 @@ func TestAPIEndpoint_PutOrgAddress_AccessControl(t *testing.T) {
input = strings.NewReader(testUpdateOrgAddressForm) input = strings.NewReader(testUpdateOrgAddressForm)
t.Run("AccessControl prevents updating another org address with correct permissions in the current org", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsWrite}}, 1)
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsAddressURL, 2), input, t) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsAddressURL, 2), input, t)
assert.Equal(t, http.StatusForbidden, response.Code) assert.Equal(t, http.StatusForbidden, response.Code)
}) })
t.Run("AccessControl prevents updating another org address with incorrect permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, 2)
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsAddressURL, 2), input, t) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsAddressURL, 2), input, t)
assert.Equal(t, http.StatusForbidden, response.Code) assert.Equal(t, http.StatusForbidden, response.Code)

@ -106,21 +106,22 @@ func TestAPIEndpoint_GetOrgQuotas_LegacyAccessControl(t *testing.T) {
func TestAPIEndpoint_GetOrgQuotas_AccessControl(t *testing.T) { func TestAPIEndpoint_GetOrgQuotas_AccessControl(t *testing.T) {
sc := setupHTTPServer(t, true) sc := setupHTTPServer(t, true)
setInitCtxSignedInViewer(sc.initCtx)
setupDBAndSettingsForAccessControlQuotaTests(t, sc) setupDBAndSettingsForAccessControlQuotaTests(t, sc)
t.Run("AccessControl allows viewing another org quotas with correct permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsQuotasRead}}, 2)
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t)
assert.Equal(t, http.StatusOK, response.Code) 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) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsQuotasRead}}, 1)
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t)
assert.Equal(t, http.StatusForbidden, response.Code) assert.Equal(t, http.StatusForbidden, response.Code)
}) })
t.Run("AccessControl prevents viewing another org quotas with incorrect permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, 2)
response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t) response := callAPI(sc.server, http.MethodGet, fmt.Sprintf(getOrgsQuotasURL, 2), nil, t)
assert.Equal(t, http.StatusForbidden, response.Code) 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) { func TestAPIEndpoint_PutOrgQuotas_AccessControl(t *testing.T) {
sc := setupHTTPServer(t, true) sc := setupHTTPServer(t, true)
setInitCtxSignedInViewer(sc.initCtx)
setupDBAndSettingsForAccessControlQuotaTests(t, sc) setupDBAndSettingsForAccessControlQuotaTests(t, sc)
input := strings.NewReader(testUpdateOrgQuotaCmd) input := strings.NewReader(testUpdateOrgQuotaCmd)
t.Run("AccessControl allows updating another org quotas with correct permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsQuotasWrite}}, 2)
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input, t) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input, t)
assert.Equal(t, http.StatusOK, response.Code) assert.Equal(t, http.StatusOK, response.Code)
@ -164,6 +164,7 @@ func TestAPIEndpoint_PutOrgQuotas_AccessControl(t *testing.T) {
input = strings.NewReader(testUpdateOrgQuotaCmd) input = strings.NewReader(testUpdateOrgQuotaCmd)
t.Run("AccessControl prevents updating another org quotas with correct permissions in another org", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: ActionOrgsQuotasWrite}}, 1)
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input, t) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input, t)
assert.Equal(t, http.StatusForbidden, response.Code) assert.Equal(t, http.StatusForbidden, response.Code)
@ -171,6 +172,7 @@ func TestAPIEndpoint_PutOrgQuotas_AccessControl(t *testing.T) {
input = strings.NewReader(testUpdateOrgQuotaCmd) input = strings.NewReader(testUpdateOrgQuotaCmd)
t.Run("AccessControl prevents updating another org quotas with incorrect permissions", func(t *testing.T) { 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) setAccessControlPermissions(sc.acmock, []accesscontrol.Permission{{Action: "orgs:invalid"}}, 2)
response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input, t) response := callAPI(sc.server, http.MethodPut, fmt.Sprintf(putOrgsQuotasURL, 2, "org_user"), input, t)
assert.Equal(t, http.StatusForbidden, response.Code) assert.Equal(t, http.StatusForbidden, response.Code)

@ -93,7 +93,7 @@ type User struct {
} }
// HasGlobalAccess checks user access with globally assigned permissions only // 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 { return func(fallback func(*models.ReqContext) bool, evaluator Evaluator) bool {
if ac.IsDisabled() { if ac.IsDisabled() {
return fallback(c) return fallback(c)
@ -103,12 +103,23 @@ func HasGlobalAccess(ac AccessControl, c *models.ReqContext) func(fallback func(
userCopy.OrgID = GlobalOrgID userCopy.OrgID = GlobalOrgID
userCopy.OrgRole = "" userCopy.OrgRole = ""
userCopy.OrgName = "" 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) hasAccess, err := ac.Evaluate(c.Req.Context(), &userCopy, evaluator)
if err != nil { if err != nil {
c.Logger.Error("Error from access control system", "error", err) c.Logger.Error("Error from access control system", "error", err)
return false 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 return hasAccess
} }
} }

@ -15,10 +15,10 @@ import (
var _ accesscontrol.AccessControl = new(AccessControl) var _ accesscontrol.AccessControl = new(AccessControl)
func ProvideAccessControl(cfg *setting.Cfg, service accesscontrol.Service) *AccessControl { func ProvideAccessControl(cfg *setting.Cfg) *AccessControl {
logger := log.New("accesscontrol") logger := log.New("accesscontrol")
return &AccessControl{ return &AccessControl{
cfg, logger, accesscontrol.NewResolvers(logger), service, cfg, logger, accesscontrol.NewResolvers(logger),
} }
} }
@ -26,7 +26,6 @@ type AccessControl struct {
cfg *setting.Cfg cfg *setting.Cfg
log log.Logger log log.Logger
resolvers accesscontrol.Resolvers resolvers accesscontrol.Resolvers
service accesscontrol.Service
} }
func (a *AccessControl) Evaluate(ctx context.Context, user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) { 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() defer timer.ObserveDuration()
metrics.MAccessEvaluationCount.Inc() metrics.MAccessEvaluationCount.Inc()
if user.Permissions == nil { if !verifyPermissions(user) {
user.Permissions = map[int64]map[string][]string{} 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 // 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]) { if evaluator.Evaluate(user.Permissions[user.OrgID]) {
return true, nil return true, nil
@ -69,3 +60,7 @@ func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver a
func (a *AccessControl) IsDisabled() bool { func (a *AccessControl) IsDisabled() bool {
return accesscontrol.IsDisabled(a.cfg) 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" "context"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@ -65,8 +63,7 @@ func TestAccessControl_Evaluate(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) { t.Run(tt.desc, func(t *testing.T) {
fakeService := actest.FakeService{} ac := ProvideAccessControl(setting.NewCfg())
ac := ProvideAccessControl(setting.NewCfg(), fakeService)
if tt.resolver != nil { if tt.resolver != nil {
ac.RegisterScopeAttributeResolver(tt.resolverPrefix, tt.resolver) ac.RegisterScopeAttributeResolver(tt.resolverPrefix, tt.resolver)

@ -97,7 +97,7 @@ type userCache interface {
GetSignedInUserWithCacheCtx(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) 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 { return func(fallback web.Handler, getTargetOrg OrgIDGetter, evaluator Evaluator) web.Handler {
if ac.IsDisabled() { if ac.IsDisabled() {
return fallback return fallback
@ -127,10 +127,18 @@ func AuthorizeInOrgMiddleware(ac AccessControl, cache userCache) func(web.Handle
userCopy.OrgRole = queryResult.OrgRole 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) authorize(c, ac, &userCopy, evaluator)
// Set the sign-ed in user permissions in that org // 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{ dto.User = &user.SignedInUser{
UserID: 0, UserID: 0,
OrgRole: org.RoleAdmin, OrgRole: org.RoleAdmin,
OrgID: dto.OrgId,
Permissions: map[int64]map[string][]string{dto.OrgId: provisionerPermissions}, Permissions: map[int64]map[string][]string{dto.OrgId: provisionerPermissions},
} }
cmd, err := dr.BuildSaveDashboardCommand(ctx, dto, false, false) 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/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "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/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/contexthandler/ctxkey"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
@ -44,26 +45,27 @@ func setupTestServer(
// build router to register routes // build router to register routes
rr := routing.NewRouteRegister() rr := routing.NewRouteRegister()
// build access control - FIXME we should be able to mock this, but to get var permissions []accesscontrol.Permission
// tests going, we're going to instantiate full accesscontrol if user != nil && user.Permissions != nil {
//ac := accesscontrolmock.New() for action, scopes := range user.Permissions[user.OrgID] {
//ac.WithDisabled() for _, scope := range scopes {
permissions = append(permissions, accesscontrol.Permission{
// create a sqlstore for access control. Action: action,
if db == nil { Scope: scope,
db = sqlstore.InitTestDB(t) })
}
}
} }
var err error acService := actest.FakeService{ExpectedPermissions: permissions, ExpectedDisabled: !cfg.RBACEnabled}
acService, err := acimpl.ProvideService(cfg, database.ProvideService(db), rr) ac := acimpl.ProvideAccessControl(cfg)
require.NoError(t, err)
ac := acimpl.ProvideAccessControl(cfg, acService)
// build mux // build mux
m := web.New() m := web.New()
// set initial context // set initial context
m.Use(contextProvider(&testContext{user})) m.Use(contextProvider(&testContext{user}))
m.Use(accesscontrol.LoadPermissionsMiddleware(acService))
// build api, this will mount the routes at the same time if // build api, this will mount the routes at the same time if
// featuremgmt.FlagPublicDashboard is enabled // featuremgmt.FlagPublicDashboard is enabled

Loading…
Cancel
Save