mirror of
https://github.com/grafana/grafana.git
synced 2026-08-18 17:15:08 -05:00
AccessControl: Remove scopes from orgs endpoints (#41709)
* AccessControl: Check permissions in target org * Remove org scopes and add an authorizeInOrg middleware * Use query result org id and perform users permission check globally for GetOrgByName * Remove scope translation for orgs current * Suggestion from Ieva
This commit is contained in:
@@ -7,11 +7,26 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
func authorize(c *models.ReqContext, ac accesscontrol.AccessControl, user *models.SignedInUser, evaluator accesscontrol.Evaluator) {
|
||||
injected, err := evaluator.Inject(buildScopeParams(c))
|
||||
if err != nil {
|
||||
c.JsonApiErr(http.StatusInternalServerError, "Internal server error", err)
|
||||
return
|
||||
}
|
||||
|
||||
hasAccess, err := ac.Evaluate(c.Req.Context(), user, injected)
|
||||
if !hasAccess || err != nil {
|
||||
Deny(c, injected, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func Middleware(ac accesscontrol.AccessControl) func(web.Handler, accesscontrol.Evaluator) web.Handler {
|
||||
return func(fallback web.Handler, evaluator accesscontrol.Evaluator) web.Handler {
|
||||
if ac.IsDisabled() {
|
||||
@@ -19,17 +34,7 @@ func Middleware(ac accesscontrol.AccessControl) func(web.Handler, accesscontrol.
|
||||
}
|
||||
|
||||
return func(c *models.ReqContext) {
|
||||
injected, err := evaluator.Inject(buildScopeParams(c))
|
||||
if err != nil {
|
||||
c.JsonApiErr(http.StatusInternalServerError, "Internal server error", err)
|
||||
return
|
||||
}
|
||||
|
||||
hasAccess, err := ac.Evaluate(c.Req.Context(), c.SignedInUser, injected)
|
||||
if !hasAccess || err != nil {
|
||||
Deny(c, injected, err)
|
||||
return
|
||||
}
|
||||
authorize(c, ac, c.SignedInUser, evaluator)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,3 +87,53 @@ func buildScopeParams(c *models.ReqContext) accesscontrol.ScopeParams {
|
||||
URLParams: web.Params(c.Req),
|
||||
}
|
||||
}
|
||||
|
||||
type OrgIDGetter func(c *models.ReqContext) (int64, error)
|
||||
|
||||
func AuthorizeInOrgMiddleware(ac accesscontrol.AccessControl, db *sqlstore.SQLStore) func(web.Handler, OrgIDGetter, accesscontrol.Evaluator) web.Handler {
|
||||
return func(fallback web.Handler, getTargetOrg OrgIDGetter, evaluator accesscontrol.Evaluator) web.Handler {
|
||||
if ac.IsDisabled() {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return func(c *models.ReqContext) {
|
||||
// using a copy of the user not to modify the signedInUser, yet perform the permission evaluation in another org
|
||||
userCopy := *(c.SignedInUser)
|
||||
orgID, err := getTargetOrg(c)
|
||||
if err != nil {
|
||||
Deny(c, nil, fmt.Errorf("failed to get target org: %w", err))
|
||||
return
|
||||
}
|
||||
if orgID == accesscontrol.GlobalOrgID {
|
||||
userCopy.OrgId = orgID
|
||||
userCopy.OrgName = ""
|
||||
userCopy.OrgRole = ""
|
||||
} else {
|
||||
query := models.GetSignedInUserQuery{UserId: c.UserId, OrgId: orgID}
|
||||
if err := db.GetSignedInUserWithCacheCtx(c.Req.Context(), &query); err != nil {
|
||||
Deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err))
|
||||
return
|
||||
}
|
||||
userCopy.OrgId = query.Result.OrgId
|
||||
userCopy.OrgName = query.Result.OrgName
|
||||
userCopy.OrgRole = query.Result.OrgRole
|
||||
}
|
||||
|
||||
authorize(c, ac, &userCopy, evaluator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func UseOrgFromContextParams(c *models.ReqContext) (int64, error) {
|
||||
orgID := c.ParamsInt64(":orgId")
|
||||
// Special case of macaron handling invalid params
|
||||
if orgID == 0 {
|
||||
return 0, models.ErrOrgNotFound
|
||||
}
|
||||
|
||||
return orgID, nil
|
||||
}
|
||||
|
||||
func UseGlobalOrg(c *models.ReqContext) (int64, error) {
|
||||
return accesscontrol.GlobalOrgID, nil
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func setupTestEnv(t testing.TB) *OSSAccessControlService {
|
||||
@@ -19,7 +20,14 @@ func setupTestEnv(t testing.TB) *OSSAccessControlService {
|
||||
|
||||
cfg := setting.NewCfg()
|
||||
cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
|
||||
ac := ProvideService(cfg, &usagestats.UsageStatsMock{T: t})
|
||||
|
||||
ac := &OSSAccessControlService{
|
||||
Cfg: cfg,
|
||||
UsageStats: &usagestats.UsageStatsMock{T: t},
|
||||
Log: log.New("accesscontrol"),
|
||||
registrations: accesscontrol.RegistrationList{},
|
||||
scopeResolver: accesscontrol.NewScopeResolver(),
|
||||
}
|
||||
return ac
|
||||
}
|
||||
|
||||
@@ -77,8 +85,8 @@ func TestEvaluatingPermissions(t *testing.T) {
|
||||
desc: "should successfully evaluate access to the endpoint",
|
||||
user: userTestCase{
|
||||
name: "testuser",
|
||||
orgRole: "Grafana Admin",
|
||||
isGrafanaAdmin: false,
|
||||
orgRole: models.ROLE_VIEWER,
|
||||
isGrafanaAdmin: true,
|
||||
},
|
||||
endpoints: []endpointTestCase{
|
||||
{evaluator: accesscontrol.EvalPermission(accesscontrol.ActionUsersDisable, accesscontrol.ScopeGlobalUsersAll)},
|
||||
@@ -501,7 +509,7 @@ func TestOSSAccessControlService_RegisterFixedRoles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOSSAccessControlService_GetUserPermissions(t *testing.T) {
|
||||
testUser := &models.SignedInUser{
|
||||
testUser := models.SignedInUser{
|
||||
UserId: 2,
|
||||
OrgId: 3,
|
||||
OrgName: "TestOrg",
|
||||
@@ -522,18 +530,11 @@ func TestOSSAccessControlService_GetUserPermissions(t *testing.T) {
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
user *models.SignedInUser
|
||||
user models.SignedInUser
|
||||
rawPerm accesscontrol.Permission
|
||||
wantPerm accesscontrol.Permission
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Translate orgs:current",
|
||||
user: testUser,
|
||||
rawPerm: accesscontrol.Permission{Action: "orgs:read", Scope: "orgs:current"},
|
||||
wantPerm: accesscontrol.Permission{Action: "orgs:read", Scope: "orgs:id:3"},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Translate users:self",
|
||||
user: testUser,
|
||||
@@ -550,13 +551,7 @@ func TestOSSAccessControlService_GetUserPermissions(t *testing.T) {
|
||||
})
|
||||
|
||||
// Setup
|
||||
ac := &OSSAccessControlService{
|
||||
Cfg: setting.NewCfg(),
|
||||
UsageStats: &usagestats.UsageStatsMock{T: t},
|
||||
Log: log.New("accesscontrol-test"),
|
||||
registrations: accesscontrol.RegistrationList{},
|
||||
scopeResolver: accesscontrol.NewScopeResolver(),
|
||||
}
|
||||
ac := setupTestEnv(t)
|
||||
ac.Cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
|
||||
|
||||
registration.Role.Permissions = []accesscontrol.Permission{tt.rawPerm}
|
||||
@@ -567,7 +562,7 @@ func TestOSSAccessControlService_GetUserPermissions(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test
|
||||
userPerms, err := ac.GetUserPermissions(context.TODO(), tt.user)
|
||||
userPerms, err := ac.GetUserPermissions(context.TODO(), &tt.user)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err, "Expected an error with GetUserPermissions.")
|
||||
return
|
||||
|
||||
@@ -42,16 +42,11 @@ type ScopeResolver struct {
|
||||
func NewScopeResolver() ScopeResolver {
|
||||
return ScopeResolver{
|
||||
keywordResolvers: map[string]KeywordScopeResolveFunc{
|
||||
"orgs:current": resolveCurrentOrg,
|
||||
"users:self": resolveUserSelf,
|
||||
"users:self": resolveUserSelf,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resolveCurrentOrg(u *models.SignedInUser) (string, error) {
|
||||
return Scope("orgs", "id", fmt.Sprintf("%v", u.OrgId)), nil
|
||||
}
|
||||
|
||||
func resolveUserSelf(u *models.SignedInUser) (string, error) {
|
||||
return Scope("users", "id", fmt.Sprintf("%v", u.UserId)), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user