Access Control: Store permissions on SignedInUser (#43040)

* add permission structure to signedinuser

* add middleware to load user permissions into signedinuser struct

* apply LoadPermissionsMiddleware to http server

* check for permissions in signedinuser struct

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
This commit is contained in:
Karl Persson
2021-12-14 16:05:59 +01:00
committed by GitHub
parent f1101efcec
commit 9558c09a7c
7 changed files with 105 additions and 123 deletions

View File

@@ -29,6 +29,7 @@ import (
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/plugincontext" "github.com/grafana/grafana/pkg/plugins/plugincontext"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
acmiddleware "github.com/grafana/grafana/pkg/services/accesscontrol/middleware"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/cleanup" "github.com/grafana/grafana/pkg/services/cleanup"
"github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/contexthandler"
@@ -440,6 +441,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
m.Use(hs.ContextHandler.Middleware) m.Use(hs.ContextHandler.Middleware)
m.Use(middleware.OrgRedirect(hs.Cfg)) m.Use(middleware.OrgRedirect(hs.Cfg))
m.Use(acmiddleware.LoadPermissionsMiddleware(hs.AccessControl))
// needs to be after context handler // needs to be after context handler
if hs.Cfg.EnforceDomain { if hs.Cfg.EnforceDomain {

View File

@@ -183,6 +183,8 @@ type SignedInUser struct {
HelpFlags1 HelpFlags1 HelpFlags1 HelpFlags1
LastSeenAt time.Time LastSeenAt time.Time
Teams []int64 Teams []int64
// Permissions grouped by orgID and actions
Permissions map[int64]map[string][]string
} }
func (u *SignedInUser) ShouldUpdateLastSeenAt() bool { func (u *SignedInUser) ShouldUpdateLastSeenAt() bool {

View File

@@ -97,14 +97,10 @@ func BuildPermissionsMap(permissions []*Permission) map[string]bool {
} }
// GroupScopesByAction will group scopes on action // GroupScopesByAction will group scopes on action
func GroupScopesByAction(permissions []*Permission) map[string]map[string]struct{} { func GroupScopesByAction(permissions []*Permission) map[string][]string {
m := make(map[string]map[string]struct{}) m := make(map[string][]string)
for _, p := range permissions { for _, p := range permissions {
if _, ok := m[p.Action]; ok { m[p.Action] = append(m[p.Action], p.Scope)
m[p.Action][p.Scope] = struct{}{}
} else {
m[p.Action] = map[string]struct{}{p.Scope: {}}
}
} }
return m return m
} }

View File

@@ -13,7 +13,7 @@ var logger = log.New("accesscontrol.evaluator")
type Evaluator interface { type Evaluator interface {
// Evaluate permissions that are grouped by action // Evaluate permissions that are grouped by action
Evaluate(permissions map[string]map[string]struct{}) (bool, error) Evaluate(permissions map[string][]string) (bool, error)
// Inject params into the evaluator's templated scopes. e.g. "settings:" + eval.Parameters(":id") and returns a new Evaluator // Inject params into the evaluator's templated scopes. e.g. "settings:" + eval.Parameters(":id") and returns a new Evaluator
Inject(params ScopeParams) (Evaluator, error) Inject(params ScopeParams) (Evaluator, error)
// String returns a string representation of permission required by the evaluator // String returns a string representation of permission required by the evaluator
@@ -32,7 +32,7 @@ type permissionEvaluator struct {
Scopes []string Scopes []string
} }
func (p permissionEvaluator) Evaluate(permissions map[string]map[string]struct{}) (bool, error) { func (p permissionEvaluator) Evaluate(permissions map[string][]string) (bool, error) {
userScopes, ok := permissions[p.Action] userScopes, ok := permissions[p.Action]
if !ok { if !ok {
return false, nil return false, nil
@@ -46,7 +46,7 @@ func (p permissionEvaluator) Evaluate(permissions map[string]map[string]struct{}
var err error var err error
var matches bool var matches bool
for scope := range userScopes { for _, scope := range userScopes {
matches, err = match(scope, target) matches, err = match(scope, target)
if err != nil { if err != nil {
return false, err return false, err
@@ -120,7 +120,7 @@ type allEvaluator struct {
allOf []Evaluator allOf []Evaluator
} }
func (a allEvaluator) Evaluate(permissions map[string]map[string]struct{}) (bool, error) { func (a allEvaluator) Evaluate(permissions map[string][]string) (bool, error) {
for _, e := range a.allOf { for _, e := range a.allOf {
if ok, err := e.Evaluate(permissions); !ok || err != nil { if ok, err := e.Evaluate(permissions); !ok || err != nil {
return false, err return false, err
@@ -160,7 +160,7 @@ type anyEvaluator struct {
anyOf []Evaluator anyOf []Evaluator
} }
func (a anyEvaluator) Evaluate(permissions map[string]map[string]struct{}) (bool, error) { func (a anyEvaluator) Evaluate(permissions map[string][]string) (bool, error) {
for _, e := range a.anyOf { for _, e := range a.anyOf {
ok, err := e.Evaluate(permissions) ok, err := e.Evaluate(permissions)
if err != nil { if err != nil {

View File

@@ -10,15 +10,7 @@ type evaluateTestCase struct {
desc string desc string
expected bool expected bool
evaluator Evaluator evaluator Evaluator
permissions map[string]map[string]struct{} permissions map[string][]string
}
type injectTestCase struct {
desc string
expected bool
evaluator Evaluator
params ScopeParams
permissions map[string]map[string]struct{}
} }
func TestPermission_Evaluate(t *testing.T) { func TestPermission_Evaluate(t *testing.T) {
@@ -27,41 +19,32 @@ func TestPermission_Evaluate(t *testing.T) {
desc: "should evaluate to true", desc: "should evaluate to true",
expected: true, expected: true,
evaluator: EvalPermission("reports:read", "reports:1"), evaluator: EvalPermission("reports:read", "reports:1"),
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"reports:read": { "reports:read": {"reports:1"},
"reports:1": struct{}{},
},
}, },
}, },
{ {
desc: "should evaluate to true when allEvaluator required scopes matches", desc: "should evaluate to true when allEvaluator required scopes matches",
expected: true, expected: true,
evaluator: EvalPermission("reports:read", "reports:1", "reports:2"), evaluator: EvalPermission("reports:read", "reports:1", "reports:2"),
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"reports:read": { "reports:read": {"reports:1", "reports:2"},
"reports:1": struct{}{},
"reports:2": struct{}{},
},
}, },
}, },
{ {
desc: "should evaluate to true for empty scope", desc: "should evaluate to true for empty scope",
expected: true, expected: true,
evaluator: EvalPermission("reports:read"), evaluator: EvalPermission("reports:read"),
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"reports:read": { "reports:read": {"reports:1"},
"reports:1": struct{}{},
},
}, },
}, },
{ {
desc: "should evaluate to false when only one of required scopes exists", desc: "should evaluate to false when only one of required scopes exists",
expected: false, expected: false,
evaluator: EvalPermission("reports:read", "reports:1", "reports:2"), evaluator: EvalPermission("reports:read", "reports:1", "reports:2"),
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"reports:read": { "reports:read": {"reports:1"},
"reports:1": struct{}{},
},
}, },
}, },
} }
@@ -75,6 +58,14 @@ func TestPermission_Evaluate(t *testing.T) {
} }
} }
type injectTestCase struct {
desc string
expected bool
evaluator Evaluator
params ScopeParams
permissions map[string][]string
}
func TestPermission_Inject(t *testing.T) { func TestPermission_Inject(t *testing.T) {
tests := []injectTestCase{ tests := []injectTestCase{
{ {
@@ -84,10 +75,8 @@ func TestPermission_Inject(t *testing.T) {
params: ScopeParams{ params: ScopeParams{
OrgID: 3, OrgID: 3,
}, },
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"orgs:read": { "orgs:read": {"orgs:3"},
"orgs:3": struct{}{},
},
}, },
}, },
{ {
@@ -100,10 +89,8 @@ func TestPermission_Inject(t *testing.T) {
":reportId": "1", ":reportId": "1",
}, },
}, },
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"reports:read": { "reports:read": {"reports:1"},
"reports:1": struct{}{},
},
}, },
}, },
{ {
@@ -111,10 +98,8 @@ func TestPermission_Inject(t *testing.T) {
expected: false, expected: false,
evaluator: EvalPermission("reports:read", Scope("reports", Parameter(":reportId"))), evaluator: EvalPermission("reports:read", Scope("reports", Parameter(":reportId"))),
params: ScopeParams{}, params: ScopeParams{},
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"reports:read": { "reports:read": {"reports:1"},
"reports:1": struct{}{},
},
}, },
}, },
{ {
@@ -127,10 +112,8 @@ func TestPermission_Inject(t *testing.T) {
":reportId2": "report2", ":reportId2": "report2",
}, },
}, },
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"reports:read": { "reports:read": {"reports:report:report2"},
"reports:report:report2": struct{}{},
},
}, },
}, },
} }
@@ -153,8 +136,8 @@ func TestAll_Evaluate(t *testing.T) {
evaluator: EvalAll( evaluator: EvalAll(
EvalPermission("settings:write", Scope("settings", "*")), EvalPermission("settings:write", Scope("settings", "*")),
), ),
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"settings:write": {"settings:*": struct{}{}}, "settings:write": {"settings:*"},
}, },
expected: true, expected: true,
}, },
@@ -164,9 +147,9 @@ func TestAll_Evaluate(t *testing.T) {
EvalPermission("settings:write", Scope("settings", "*")), EvalPermission("settings:write", Scope("settings", "*")),
EvalPermission("settings:read", Scope("settings", "auth.saml", "*")), EvalPermission("settings:read", Scope("settings", "auth.saml", "*")),
), ),
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"settings:write": {"settings:*": struct{}{}}, "settings:write": {"settings:*"},
"settings:read": {"settings:*": struct{}{}}, "settings:read": {"settings:*"},
}, },
expected: true, expected: true,
}, },
@@ -177,10 +160,10 @@ func TestAll_Evaluate(t *testing.T) {
EvalPermission("settings:read", Scope("settings", "auth.saml", "*")), EvalPermission("settings:read", Scope("settings", "auth.saml", "*")),
EvalPermission("report:read", Scope("reports", "*")), EvalPermission("report:read", Scope("reports", "*")),
), ),
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"settings:write": {"settings:*": struct{}{}}, "settings:write": {"settings:*"},
"settings:read": {"settings:*": struct{}{}}, "settings:read": {"settings:*"},
"report:read": {"report:1": struct{}{}}, "report:read": {"report:1"},
}, },
expected: false, expected: false,
}, },
@@ -211,13 +194,9 @@ func TestAll_Inject(t *testing.T) {
":reportId": "1", ":reportId": "1",
}, },
}, },
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"reports:read": { "reports:read": {"reports:1"},
"reports:1": struct{}{}, "settings:read": {"settings:3"},
},
"settings:read": {
"settings:3": struct{}{},
},
}, },
}, },
{ {
@@ -233,11 +212,8 @@ func TestAll_Inject(t *testing.T) {
":orgId": "4", ":orgId": "4",
}, },
}, },
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"orgs:read": { "orgs:read": {"orgs:3", "orgs:4"},
"orgs:3": struct{}{},
"orgs:4": struct{}{},
},
}, },
}, },
{ {
@@ -248,13 +224,9 @@ func TestAll_Inject(t *testing.T) {
EvalPermission("reports:read", Scope("reports", Parameter(":reportId"))), EvalPermission("reports:read", Scope("reports", Parameter(":reportId"))),
), ),
params: ScopeParams{}, params: ScopeParams{},
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"reports:read": { "reports:read": {"reports:1"},
"reports:1": struct{}{}, "settings:read": {"settings:3"},
},
"settings:read": {
"settings:3": struct{}{},
},
}, },
}, },
} }
@@ -277,8 +249,8 @@ func TestAny_Evaluate(t *testing.T) {
evaluator: EvalAny( evaluator: EvalAny(
EvalPermission("settings:write", Scope("settings", "*")), EvalPermission("settings:write", Scope("settings", "*")),
), ),
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"settings:write": {"settings:*": struct{}{}}, "settings:write": {"settings:*"},
}, },
expected: true, expected: true,
}, },
@@ -289,8 +261,8 @@ func TestAny_Evaluate(t *testing.T) {
EvalPermission("report:read", Scope("reports", "1")), EvalPermission("report:read", Scope("reports", "1")),
EvalPermission("report:write", Scope("reports", "10")), EvalPermission("report:write", Scope("reports", "10")),
), ),
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"settings:write": {"settings:*": struct{}{}}, "settings:write": {"settings:*"},
}, },
expected: true, expected: true,
}, },
@@ -301,8 +273,8 @@ func TestAny_Evaluate(t *testing.T) {
EvalPermission("report:read", Scope("reports", "1")), EvalPermission("report:read", Scope("reports", "1")),
EvalPermission("report:write", Scope("reports", "10")), EvalPermission("report:write", Scope("reports", "10")),
), ),
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"permissions:write": {"permissions:delegate": struct{}{}}, "permissions:write": {"permissions:delegate"},
}, },
expected: false, expected: false,
}, },
@@ -333,13 +305,9 @@ func TestAny_Inject(t *testing.T) {
":reportId": "1", ":reportId": "1",
}, },
}, },
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"reports:read": { "reports:read": {"reports:1"},
"reports:1": struct{}{}, "settings:read": {"settings:3"},
},
"settings:read": {
"settings:3": struct{}{},
},
}, },
}, },
{ {
@@ -355,11 +323,8 @@ func TestAny_Inject(t *testing.T) {
":orgId": "4", ":orgId": "4",
}, },
}, },
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"orgs:read": { "orgs:read": {"orgs:3", "orgs:4"},
"orgs:3": struct{}{},
"orgs:4": struct{}{},
},
}, },
}, },
{ {
@@ -370,13 +335,9 @@ func TestAny_Inject(t *testing.T) {
EvalPermission("reports:read", Scope("reports", Parameter(":reportId"))), EvalPermission("reports:read", Scope("reports", Parameter(":reportId"))),
), ),
params: ScopeParams{}, params: ScopeParams{},
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"reports:read": { "reports:read": {"reports:1"},
"reports:1": struct{}{}, "settings:read": {"settings:3"},
},
"settings:read": {
"settings:3": struct{}{},
},
}, },
}, },
} }
@@ -396,7 +357,7 @@ type combinedTestCase struct {
desc string desc string
evaluator Evaluator evaluator Evaluator
expected bool expected bool
permissions map[string]map[string]struct{} permissions map[string][]string
} }
func TestEval(t *testing.T) { func TestEval(t *testing.T) {
@@ -411,8 +372,8 @@ func TestEval(t *testing.T) {
), ),
), ),
expected: true, expected: true,
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"settings:write": {"settings:*": struct{}{}}, "settings:write": {"settings:*"},
}, },
}, },
{ {
@@ -425,11 +386,8 @@ func TestEval(t *testing.T) {
), ),
), ),
expected: true, expected: true,
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"settings:write": { "settings:write": {"settings:auth.saml:enabled", "settings:auth.saml:max_issue_delay"},
"settings:auth.saml:enabled": struct{}{},
"settings:auth.saml:max_issue_delay": struct{}{},
},
}, },
}, },
{ {
@@ -442,10 +400,8 @@ func TestEval(t *testing.T) {
), ),
), ),
expected: false, expected: false,
permissions: map[string]map[string]struct{}{ permissions: map[string][]string{
"settings:write": { "settings:write": {"settings:auth.saml:enabled"},
"settings:auth.saml:enabled": struct{}{},
},
}, },
}, },
} }

View File

@@ -137,3 +137,22 @@ func UseOrgFromContextParams(c *models.ReqContext) (int64, error) {
func UseGlobalOrg(c *models.ReqContext) (int64, error) { func UseGlobalOrg(c *models.ReqContext) (int64, error) {
return accesscontrol.GlobalOrgID, nil return accesscontrol.GlobalOrgID, nil
} }
func LoadPermissionsMiddleware(ac accesscontrol.AccessControl) web.Handler {
return func(c *models.ReqContext) {
if ac.IsDisabled() {
return
}
permissions, err := ac.GetUserPermissions(c.Req.Context(), c.SignedInUser)
if err != nil {
c.JsonApiErr(http.StatusForbidden, "", err)
return
}
if c.SignedInUser.Permissions == nil {
c.SignedInUser.Permissions = make(map[int64]map[string][]string)
}
c.SignedInUser.Permissions[c.OrgId] = accesscontrol.GroupScopesByAction(permissions)
}
}

View File

@@ -64,12 +64,19 @@ func (ac *OSSAccessControlService) Evaluate(ctx context.Context, user *models.Si
defer timer.ObserveDuration() defer timer.ObserveDuration()
metrics.MAccessEvaluationCount.Inc() metrics.MAccessEvaluationCount.Inc()
permissions, err := ac.GetUserPermissions(ctx, user) if user.Permissions == nil {
if err != nil { user.Permissions = map[int64]map[string][]string{}
return false, err
} }
return evaluator.Evaluate(accesscontrol.GroupScopesByAction(permissions)) if _, ok := user.Permissions[user.OrgId]; !ok {
permissions, err := ac.GetUserPermissions(ctx, user)
if err != nil {
return false, err
}
user.Permissions[user.OrgId] = accesscontrol.GroupScopesByAction(permissions)
}
return evaluator.Evaluate(user.Permissions[user.OrgId])
} }
// GetUserRoles returns user permissions based on built-in roles // GetUserRoles returns user permissions based on built-in roles