From 458371c8ebcd862ecff245c0944cd5b67c5c22cd Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Wed, 6 Oct 2021 13:15:09 +0200 Subject: [PATCH] AccessControl: Extend scope parameters with extra params from context (#39722) * AccessControl: Extend scope parameters with extra params from context Co-authored-by: Emil Tullstedt --- pkg/api/roles.go | 20 ++-- pkg/services/accesscontrol/evaluator.go | 8 +- pkg/services/accesscontrol/evaluator_test.go | 97 +++++++++++++++---- .../accesscontrol/middleware/middleware.go | 9 +- pkg/services/accesscontrol/models.go | 6 ++ pkg/services/accesscontrol/scope.go | 10 +- 6 files changed, 115 insertions(+), 35 deletions(-) diff --git a/pkg/api/roles.go b/pkg/api/roles.go index 78e30c520d9..fd7ad7c7261 100644 --- a/pkg/api/roles.go +++ b/pkg/api/roles.go @@ -17,17 +17,17 @@ const ( ) // API related scopes -const ( - ScopeProvisionersAll = "provisioners:*" - ScopeProvisionersDashboards = "provisioners:dashboards" - ScopeProvisionersPlugins = "provisioners:plugins" - ScopeProvisionersDatasources = "provisioners:datasources" - ScopeProvisionersNotifications = "provisioners:notifications" +var ( + ScopeProvisionersAll = accesscontrol.Scope("provisioners", "*") + ScopeProvisionersDashboards = accesscontrol.Scope("provisioners", "dashboards") + ScopeProvisionersPlugins = accesscontrol.Scope("provisioners", "plugins") + ScopeProvisionersDatasources = accesscontrol.Scope("provisioners", "datasources") + ScopeProvisionersNotifications = accesscontrol.Scope("provisioners", "notifications") - ScopeDatasourcesAll = `datasources:*` - ScopeDatasourceID = `datasources:id:{{ index . ":id" }}` - ScopeDatasourceUID = `datasources:uid:{{ index . ":uid" }}` - ScopeDatasourceName = `datasources:name:{{ index . ":name" }}` + ScopeDatasourcesAll = accesscontrol.Scope("datasources", "*") + ScopeDatasourceID = accesscontrol.Scope("datasources", "id", accesscontrol.Parameter(":id")) + ScopeDatasourceUID = accesscontrol.Scope("datasources", "uid", accesscontrol.Parameter(":uid")) + ScopeDatasourceName = accesscontrol.Scope("datasources", "name", accesscontrol.Parameter(":name")) ) // declareFixedRoles declares to the AccessControl service fixed roles and their diff --git a/pkg/services/accesscontrol/evaluator.go b/pkg/services/accesscontrol/evaluator.go index 54cbe773e02..3b352fd23db 100644 --- a/pkg/services/accesscontrol/evaluator.go +++ b/pkg/services/accesscontrol/evaluator.go @@ -15,7 +15,7 @@ type Evaluator interface { // Evaluate permissions that are grouped by action Evaluate(permissions map[string]map[string]struct{}) (bool, error) // Inject params into the evaluator's templated scopes. e.g. "settings:" + eval.Parameters(":id") and returns a new Evaluator - Inject(params map[string]string) (Evaluator, error) + Inject(params ScopeParams) (Evaluator, error) // String returns a string representation of permission required by the evaluator String() string } @@ -89,7 +89,7 @@ func match(scope, target string) (bool, error) { return scope == target, nil } -func (p permissionEvaluator) Inject(params map[string]string) (Evaluator, error) { +func (p permissionEvaluator) Inject(params ScopeParams) (Evaluator, error) { scopes := make([]string, 0, len(p.Scopes)) for _, scope := range p.Scopes { tmpl, err := template.New("scope").Parse(scope) @@ -129,7 +129,7 @@ func (a allEvaluator) Evaluate(permissions map[string]map[string]struct{}) (bool return true, nil } -func (a allEvaluator) Inject(params map[string]string) (Evaluator, error) { +func (a allEvaluator) Inject(params ScopeParams) (Evaluator, error) { var injected []Evaluator for _, e := range a.allOf { i, err := e.Inject(params) @@ -173,7 +173,7 @@ func (a anyEvaluator) Evaluate(permissions map[string]map[string]struct{}) (bool return false, nil } -func (a anyEvaluator) Inject(params map[string]string) (Evaluator, error) { +func (a anyEvaluator) Inject(params ScopeParams) (Evaluator, error) { var injected []Evaluator for _, e := range a.anyOf { i, err := e.Inject(params) diff --git a/pkg/services/accesscontrol/evaluator_test.go b/pkg/services/accesscontrol/evaluator_test.go index dd1f6bf9442..ee60d710e37 100644 --- a/pkg/services/accesscontrol/evaluator_test.go +++ b/pkg/services/accesscontrol/evaluator_test.go @@ -17,7 +17,7 @@ type injectTestCase struct { desc string expected bool evaluator Evaluator - params map[string]string + params ScopeParams permissions map[string]map[string]struct{} } @@ -77,13 +77,28 @@ func TestPermission_Evaluate(t *testing.T) { func TestPermission_Inject(t *testing.T) { tests := []injectTestCase{ + { + desc: "should inject field", + expected: true, + evaluator: EvalPermission("orgs:read", Scope("orgs", Field("OrgID"))), + params: ScopeParams{ + OrgID: 3, + }, + permissions: map[string]map[string]struct{}{ + "orgs:read": { + "orgs:3": struct{}{}, + }, + }, + }, { desc: "should inject correct param", expected: true, evaluator: EvalPermission("reports:read", Scope("reports", Parameter(":reportId"))), - params: map[string]string{ - ":id": "10", - ":reportId": "1", + params: ScopeParams{ + URLParams: map[string]string{ + ":id": "10", + ":reportId": "1", + }, }, permissions: map[string]map[string]struct{}{ "reports:read": { @@ -95,7 +110,7 @@ func TestPermission_Inject(t *testing.T) { desc: "should fail for nil params", expected: false, evaluator: EvalPermission("reports:read", Scope("reports", Parameter(":reportId"))), - params: nil, + params: ScopeParams{}, permissions: map[string]map[string]struct{}{ "reports:read": { "reports:1": struct{}{}, @@ -106,9 +121,11 @@ func TestPermission_Inject(t *testing.T) { desc: "should inject several parameters to one permission", expected: true, evaluator: EvalPermission("reports:read", Scope("reports", Parameter(":reportId"), Parameter(":reportId2"))), - params: map[string]string{ - ":reportId": "report", - ":reportId2": "report2", + params: ScopeParams{ + URLParams: map[string]string{ + ":reportId": "report", + ":reportId2": "report2", + }, }, permissions: map[string]map[string]struct{}{ "reports:read": { @@ -187,10 +204,12 @@ func TestAll_Inject(t *testing.T) { EvalPermission("reports:read", Scope("reports", Parameter(":reportId"))), EvalPermission("settings:read", Scope("settings", Parameter(":settingsId"))), ), - params: map[string]string{ - ":id": "10", - ":settingsId": "3", - ":reportId": "1", + params: ScopeParams{ + URLParams: map[string]string{ + ":id": "10", + ":settingsId": "3", + ":reportId": "1", + }, }, permissions: map[string]map[string]struct{}{ "reports:read": { @@ -201,6 +220,26 @@ func TestAll_Inject(t *testing.T) { }, }, }, + { + desc: "should inject field and URL param", + expected: true, + evaluator: EvalAll( + EvalPermission("orgs:read", Scope("orgs", Field("OrgID"))), + EvalPermission("orgs:read", Scope("orgs", Parameter(":orgId"))), + ), + params: ScopeParams{ + OrgID: 3, + URLParams: map[string]string{ + ":orgId": "4", + }, + }, + permissions: map[string]map[string]struct{}{ + "orgs:read": { + "orgs:3": struct{}{}, + "orgs:4": struct{}{}, + }, + }, + }, { desc: "should fail for nil params", expected: false, @@ -208,7 +247,7 @@ func TestAll_Inject(t *testing.T) { EvalPermission("settings:read", Scope("reports", Parameter(":settingsId"))), EvalPermission("reports:read", Scope("reports", Parameter(":reportId"))), ), - params: nil, + params: ScopeParams{}, permissions: map[string]map[string]struct{}{ "reports:read": { "reports:1": struct{}{}, @@ -287,10 +326,12 @@ func TestAny_Inject(t *testing.T) { EvalPermission("reports:read", Scope("reports", Parameter(":reportId"))), EvalPermission("settings:read", Scope("settings", Parameter(":settingsId"))), ), - params: map[string]string{ - ":id": "10", - ":settingsId": "3", - ":reportId": "1", + params: ScopeParams{ + URLParams: map[string]string{ + ":id": "10", + ":settingsId": "3", + ":reportId": "1", + }, }, permissions: map[string]map[string]struct{}{ "reports:read": { @@ -301,6 +342,26 @@ func TestAny_Inject(t *testing.T) { }, }, }, + { + desc: "should inject field and URL param", + expected: true, + evaluator: EvalAny( + EvalPermission("orgs:read", Scope("orgs", Field("OrgID"))), + EvalPermission("orgs:read", Scope("orgs", Parameter(":orgId"))), + ), + params: ScopeParams{ + OrgID: 3, + URLParams: map[string]string{ + ":orgId": "4", + }, + }, + permissions: map[string]map[string]struct{}{ + "orgs:read": { + "orgs:3": struct{}{}, + "orgs:4": struct{}{}, + }, + }, + }, { desc: "should fail for nil params", expected: false, @@ -308,7 +369,7 @@ func TestAny_Inject(t *testing.T) { EvalPermission("settings:read", Scope("reports", Parameter(":settingsId"))), EvalPermission("reports:read", Scope("reports", Parameter(":reportId"))), ), - params: nil, + params: ScopeParams{}, permissions: map[string]map[string]struct{}{ "reports:read": { "reports:1": struct{}{}, diff --git a/pkg/services/accesscontrol/middleware/middleware.go b/pkg/services/accesscontrol/middleware/middleware.go index 336363f242d..c881ac79de9 100644 --- a/pkg/services/accesscontrol/middleware/middleware.go +++ b/pkg/services/accesscontrol/middleware/middleware.go @@ -19,7 +19,7 @@ func Middleware(ac accesscontrol.AccessControl) func(macaron.Handler, accesscont } return func(c *models.ReqContext) { - injected, err := evaluator.Inject(macaron.Params(c.Req)) + injected, err := evaluator.Inject(buildScopeParams(c)) if err != nil { c.JsonApiErr(http.StatusInternalServerError, "Internal server error", err) return @@ -69,3 +69,10 @@ func newID() string { } return "ACE" + id } + +func buildScopeParams(c *models.ReqContext) accesscontrol.ScopeParams { + return accesscontrol.ScopeParams{ + OrgID: c.OrgId, + URLParams: macaron.Params(c.Req), + } +} diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index cded2098b4d..32b8954594b 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -140,6 +140,12 @@ func (p Permission) OSSPermission() Permission { } } +// ScopeParams holds the parameters used to fill in scope templates +type ScopeParams struct { + OrgID int64 + URLParams map[string]string +} + const ( GlobalOrgID = 0 // Permission actions diff --git a/pkg/services/accesscontrol/scope.go b/pkg/services/accesscontrol/scope.go index b25dd9150eb..30c8ae877a2 100644 --- a/pkg/services/accesscontrol/scope.go +++ b/pkg/services/accesscontrol/scope.go @@ -18,8 +18,14 @@ func Scope(parts ...string) string { return b.String() } -// Parameter returns injectable scope part +// Parameter returns injectable scope part, based on URL parameters. // e.g. Scope("users", Parameter(":id")) or "users:" + Parameter(":id") func Parameter(key string) string { - return fmt.Sprintf(`{{ index . "%s" }}`, key) + return fmt.Sprintf(`{{ index .URLParams "%s" }}`, key) +} + +// Field returns an injectable scope part for selected fields from the request's context available in accesscontrol.ScopeParams. +// e.g. Scope("orgs", Parameter("OrgID")) or "orgs:" + Parameter("OrgID") +func Field(key string) string { + return fmt.Sprintf(`{{ .%s }}`, key) }