AccessControl: Extend scope parameters with extra params from context (#39722)

* AccessControl: Extend scope parameters with extra params from context

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
This commit is contained in:
Gabriel MABILLE 2021-10-06 13:15:09 +02:00 committed by GitHub
parent d1b2b10e6a
commit 458371c8eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 115 additions and 35 deletions

View File

@ -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

View File

@ -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)

View File

@ -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{}{},

View File

@ -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),
}
}

View File

@ -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

View File

@ -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)
}