AccessControl: keyword scope resolution (#40229)

pull/40627/head
Gabriel MABILLE 4 years ago committed by GitHub
parent 26dda75db5
commit fbc6febb0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol.go
  2. 91
      pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol_test.go
  3. 43
      pkg/services/accesscontrol/scoperesolution.go
  4. 55
      pkg/services/accesscontrol/scoperesolution_test.go

@ -14,9 +14,10 @@ import (
func ProvideService(cfg *setting.Cfg, usageStats usagestats.Service) *OSSAccessControlService {
s := &OSSAccessControlService{
Cfg: cfg,
UsageStats: usageStats,
Log: log.New("accesscontrol"),
Cfg: cfg,
UsageStats: usageStats,
Log: log.New("accesscontrol"),
scopeResolver: accesscontrol.NewScopeResolver(),
}
s.registerUsageMetrics()
return s
@ -28,6 +29,7 @@ type OSSAccessControlService struct {
UsageStats usagestats.Service
Log log.Logger
registrations accesscontrol.RegistrationList
scopeResolver accesscontrol.ScopeResolver
}
func (ac *OSSAccessControlService) IsDisabled() bool {
@ -65,6 +67,7 @@ func (ac *OSSAccessControlService) Evaluate(ctx context.Context, user *models.Si
if err != nil {
return false, err
}
return evaluator.Evaluate(accesscontrol.GroupScopesByAction(permissions))
}
@ -83,8 +86,12 @@ func (ac *OSSAccessControlService) GetUserPermissions(ctx context.Context, user
continue
}
for _, p := range role.Permissions {
permission := p
permissions = append(permissions, &permission)
// if the permission has a keyword in its scope it will be resolved
permission, err := ac.scopeResolver.ResolveKeyword(user, p)
if err != nil {
return nil, err
}
permissions = append(permissions, permission)
}
}
}

@ -45,6 +45,15 @@ func removeRoleHelper(role string) {
}
}
// extractRawPermissionsHelper extracts action and scope fields only from a permission slice
func extractRawPermissionsHelper(perms []*accesscontrol.Permission) []*accesscontrol.Permission {
res := make([]*accesscontrol.Permission, len(perms))
for i, p := range perms {
res[i] = &accesscontrol.Permission{Action: p.Action, Scope: p.Scope}
}
return res
}
type evaluatingPermissionsTestCase struct {
desc string
user userTestCase
@ -490,3 +499,85 @@ func TestOSSAccessControlService_RegisterFixedRoles(t *testing.T) {
})
}
}
func TestOSSAccessControlService_GetUserPermissions(t *testing.T) {
testUser := &models.SignedInUser{
UserId: 2,
OrgId: 3,
OrgName: "TestOrg",
OrgRole: models.ROLE_VIEWER,
Login: "testUser",
Name: "Test User",
Email: "testuser@example.org",
}
registration := accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Version: 1,
UID: "fixed:test:test",
Name: "fixed:test:test",
Description: "Test role",
Permissions: []accesscontrol.Permission{},
},
Grants: []string{"Viewer"},
}
tests := []struct {
name string
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,
rawPerm: accesscontrol.Permission{Action: "users:read", Scope: "users:self"},
wantPerm: accesscontrol.Permission{Action: "users:read", Scope: "users:id:2"},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Remove any inserted role after the test case has been run
t.Cleanup(func() {
removeRoleHelper(registration.Role.Name)
})
// Setup
ac := &OSSAccessControlService{
Cfg: setting.NewCfg(),
UsageStats: &usagestats.UsageStatsMock{T: t},
Log: log.New("accesscontrol-test"),
registrations: accesscontrol.RegistrationList{},
scopeResolver: accesscontrol.NewScopeResolver(),
}
ac.Cfg.FeatureToggles = map[string]bool{"accesscontrol": true}
registration.Role.Permissions = []accesscontrol.Permission{tt.rawPerm}
err := ac.DeclareFixedRoles(registration)
require.NoError(t, err)
err = ac.RegisterFixedRoles()
require.NoError(t, err)
// Test
userPerms, err := ac.GetUserPermissions(context.TODO(), tt.user)
if tt.wantErr {
assert.Error(t, err, "Expected an error with GetUserPermissions.")
return
}
require.NoError(t, err, "Did not expect an error with GetUserPermissions.")
rawUserPerms := extractRawPermissionsHelper(userPerms)
assert.Contains(t, rawUserPerms, &tt.wantPerm, "Expected resolution of raw permission")
assert.NotContains(t, rawUserPerms, &tt.rawPerm, "Expected raw permission to have been resolved")
})
}
}

@ -0,0 +1,43 @@
package accesscontrol
import (
"fmt"
"github.com/grafana/grafana/pkg/models"
)
type KeywordScopeResolveFunc func(*models.SignedInUser) (string, error)
// ScopeResolver contains a map of functions to resolve scope keywords such as `self` or `current` into `id` based scopes
type ScopeResolver struct {
keywordResolvers map[string]KeywordScopeResolveFunc
}
func NewScopeResolver() ScopeResolver {
return ScopeResolver{
keywordResolvers: map[string]KeywordScopeResolveFunc{
"orgs:current": resolveCurrentOrg,
"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
}
// ResolveKeyword resolves scope with keywords such as `self` or `current` into `id` based scopes
func (s *ScopeResolver) ResolveKeyword(user *models.SignedInUser, permission Permission) (*Permission, error) {
if fn, ok := s.keywordResolvers[permission.Scope]; ok {
resolvedScope, err := fn(user)
if err != nil {
return nil, fmt.Errorf("could not resolve %v: %v", permission.Scope, err)
}
permission.Scope = resolvedScope
}
return &permission, nil
}

@ -0,0 +1,55 @@
package accesscontrol
import (
"testing"
"github.com/grafana/grafana/pkg/models"
"github.com/stretchr/testify/assert"
)
var testUser = &models.SignedInUser{
UserId: 2,
OrgId: 3,
OrgName: "TestOrg",
OrgRole: models.ROLE_VIEWER,
Login: "testUser",
Name: "Test User",
Email: "testuser@example.org",
}
func TestResolveKeywordedScope(t *testing.T) {
tests := []struct {
name string
user *models.SignedInUser
permission Permission
want *Permission
wantErr bool
}{
{
name: "no scope",
user: testUser,
permission: Permission{Action: "users:read"},
want: &Permission{Action: "users:read"},
wantErr: false,
},
{
name: "user if resolution",
user: testUser,
permission: Permission{Action: "users:read", Scope: "users:self"},
want: &Permission{Action: "users:read", Scope: "users:id:2"},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resolver := NewScopeResolver()
resolved, err := resolver.ResolveKeyword(tt.user, tt.permission)
if tt.wantErr {
assert.Error(t, err, "expected an error during the resolution of the scope")
return
}
assert.NoError(t, err)
assert.EqualValues(t, tt.want, resolved, "permission did not match expected resolution")
})
}
}
Loading…
Cancel
Save