Access control: Load permissions from memory and database (#42080)

* Load permission from both in memory and from database
Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
Karl Persson 2022-01-28 16:11:18 +01:00 committed by GitHub
parent 26ddeaf3d7
commit e844b263c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 111 additions and 60 deletions

View File

@ -330,7 +330,6 @@ func setupHTTPServerWithCfg(t *testing.T, useFakeAccessControl, enableAccessCont
cfg.IsFeatureToggleEnabled = features.IsEnabled
var acmock *accesscontrolmock.Mock
var ac *ossaccesscontrol.OSSAccessControlService
// Use a test DB
db := sqlstore.InitTestDB(t)
@ -362,7 +361,7 @@ func setupHTTPServerWithCfg(t *testing.T, useFakeAccessControl, enableAccessCont
require.NoError(t, err)
hs.TeamPermissionsService = teamPermissionService
} else {
ac = ossaccesscontrol.ProvideService(hs.Features, &usagestats.UsageStatsMock{T: t})
ac := ossaccesscontrol.ProvideService(hs.Features, &usagestats.UsageStatsMock{T: t}, database.ProvideService(db))
hs.AccessControl = ac
// Perform role registration
err := hs.declareFixedRoles()

View File

@ -37,6 +37,17 @@ func (s *AccessControlStore) GetUserPermissions(ctx context.Context, query acces
INNER JOIN role ON role.id = permission.role_id
` + filter
if query.Actions != nil {
q += " AND permission.action IN("
if len(query.Actions) > 0 {
q += "?" + strings.Repeat(",?", len(query.Actions)-1)
}
q += ")"
for _, a := range query.Actions {
params = append(params, a)
}
}
if err := sess.SQL(q, params...).Find(&result); err != nil {
return err
}

View File

@ -20,6 +20,7 @@ type getUserPermissionsTestCase struct {
userPermissions []string
teamPermissions []string
builtinPermissions []string
actions []string
expected int
}
@ -52,6 +53,26 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
builtinPermissions: []string{"5", "6"},
expected: 5,
},
{
desc: "Should filter on actions",
orgID: 1,
role: "",
userPermissions: []string{"1", "2", "10"},
teamPermissions: []string{"100", "2"},
builtinPermissions: []string{"5", "6"},
expected: 3,
actions: []string{"dashboards:write"},
},
{
desc: "Should return no permission when passing empty slice of actions",
orgID: 1,
role: "Viewer",
userPermissions: []string{"1", "2", "10"},
teamPermissions: []string{"100", "2"},
builtinPermissions: []string{"5", "6"},
expected: 0,
actions: []string{},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
@ -61,7 +82,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
for _, id := range tt.userPermissions {
_, err := store.SetUserResourcePermission(context.Background(), tt.orgID, user.Id, accesscontrol.SetResourcePermissionCommand{
Actions: []string{"dashboards:read"},
Actions: []string{"dashboards:write"},
Resource: "dashboards",
ResourceID: id,
}, nil)
@ -97,9 +118,10 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
}
permissions, err := store.GetUserPermissions(context.Background(), accesscontrol.GetUserPermissionsQuery{
OrgID: tt.orgID,
UserID: user.Id,
Roles: roles,
OrgID: tt.orgID,
UserID: user.Id,
Roles: roles,
Actions: tt.actions,
})
require.NoError(t, err)

View File

@ -176,9 +176,10 @@ func (p Permission) OSSPermission() Permission {
}
type GetUserPermissionsQuery struct {
OrgID int64 `json:"-"`
UserID int64 `json:"userId"`
Roles []string
OrgID int64 `json:"-"`
UserID int64 `json:"userId"`
Roles []string
Actions []string
}
// ScopeParams holds the parameters used to fill in scope templates

View File

@ -13,24 +13,31 @@ import (
"github.com/prometheus/client_golang/prometheus"
)
func ProvideService(features featuremgmt.FeatureToggles, usageStats usagestats.Service) *OSSAccessControlService {
s := &OSSAccessControlService{
features: features,
UsageStats: usageStats,
Log: log.New("accesscontrol"),
ScopeResolver: accesscontrol.NewScopeResolver(),
}
func ProvideService(features featuremgmt.FeatureToggles, usageStats usagestats.Service, provider accesscontrol.PermissionsProvider) *OSSAccessControlService {
s := ProvideOSSAccessControl(features, usageStats, provider)
s.registerUsageMetrics()
return s
}
// ProvideOSSAccessControl creates an oss implementation of access control without usage stats registration
func ProvideOSSAccessControl(features featuremgmt.FeatureToggles, usageStats usagestats.Service, provider accesscontrol.PermissionsProvider) *OSSAccessControlService {
return &OSSAccessControlService{
features: features,
provider: provider,
usageStats: usageStats,
log: log.New("accesscontrol"),
scopeResolver: accesscontrol.NewScopeResolver(),
}
}
// OSSAccessControlService is the service implementing role based access control.
type OSSAccessControlService struct {
log log.Logger
usageStats usagestats.Service
features featuremgmt.FeatureToggles
UsageStats usagestats.Service
Log log.Logger
scopeResolver accesscontrol.ScopeResolver
provider accesscontrol.PermissionsProvider
registrations accesscontrol.RegistrationList
ScopeResolver accesscontrol.ScopeResolver
}
func (ac *OSSAccessControlService) IsDisabled() bool {
@ -41,7 +48,7 @@ func (ac *OSSAccessControlService) IsDisabled() bool {
}
func (ac *OSSAccessControlService) registerUsageMetrics() {
ac.UsageStats.RegisterMetricsFunc(func(context.Context) (map[string]interface{}, error) {
ac.usageStats.RegisterMetricsFunc(func(context.Context) (map[string]interface{}, error) {
return map[string]interface{}{
"stats.oss.accesscontrol.enabled.count": ac.getUsageMetrics(),
}, nil
@ -74,7 +81,7 @@ func (ac *OSSAccessControlService) Evaluate(ctx context.Context, user *models.Si
user.Permissions[user.OrgId] = accesscontrol.GroupScopesByAction(permissions)
}
attributeMutator := ac.ScopeResolver.GetResolveAttributeScopeMutator(user.OrgId)
attributeMutator := ac.scopeResolver.GetResolveAttributeScopeMutator(user.OrgId)
resolvedEvaluator, err := evaluator.MutateScopes(ctx, attributeMutator)
if err != nil {
return false, err
@ -92,12 +99,37 @@ func (ac *OSSAccessControlService) GetUserPermissions(ctx context.Context, user
timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary)
defer timer.ObserveDuration()
var err error
keywordMutator := ac.ScopeResolver.GetResolveKeywordScopeMutator(user)
permissions := ac.getFixedPermissions(ctx, user)
builtinRoles := ac.GetUserBuiltInRoles(user)
dbPermissions, err := ac.provider.GetUserPermissions(ctx, accesscontrol.GetUserPermissionsQuery{
OrgID: user.OrgId,
UserID: user.UserId,
Roles: ac.GetUserBuiltInRoles(user),
Actions: []string{},
})
if err != nil {
return nil, err
}
permissions = append(permissions, dbPermissions...)
resolved := make([]*accesscontrol.Permission, 0, len(permissions))
keywordMutator := ac.scopeResolver.GetResolveKeywordScopeMutator(user)
for _, p := range permissions {
// if the permission has a keyword in its scope it will be resolved
p.Scope, err = keywordMutator(ctx, p.Scope)
if err != nil {
return nil, err
}
resolved = append(resolved, p)
}
return resolved, nil
}
func (ac *OSSAccessControlService) getFixedPermissions(ctx context.Context, user *models.SignedInUser) []*accesscontrol.Permission {
permissions := make([]*accesscontrol.Permission, 0)
for _, builtin := range builtinRoles {
for _, builtin := range ac.GetUserBuiltInRoles(user) {
if roleNames, ok := accesscontrol.FixedRoleGrants[builtin]; ok {
for _, name := range roleNames {
role, exists := accesscontrol.FixedRoles[name]
@ -105,19 +137,13 @@ func (ac *OSSAccessControlService) GetUserPermissions(ctx context.Context, user
continue
}
for i := range role.Permissions {
// if the permission has a keyword in its scope it will be resolved
p := (role.Permissions[i])
p.Scope, err = keywordMutator(ctx, p.Scope)
if err != nil {
return nil, err
}
permissions = append(permissions, &p)
permissions = append(permissions, &role.Permissions[i])
}
}
}
}
return permissions, nil
return permissions
}
func (ac *OSSAccessControlService) GetUserBuiltInRoles(user *models.SignedInUser) []string {
@ -138,7 +164,7 @@ func (ac *OSSAccessControlService) saveFixedRole(role accesscontrol.RoleDTO) {
// needs to be increased. Hence, we don't overwrite a role with a
// greater version.
if storedRole.Version >= role.Version {
ac.Log.Debug("the has already been stored in a greater version, skipping registration", "role", role.Name)
ac.log.Debug("the role has already been stored in a greater version, skipping registration", "role", role.Name)
return
}
}
@ -154,7 +180,7 @@ func (ac *OSSAccessControlService) assignFixedRole(role accesscontrol.RoleDTO, b
if ok {
for _, assignedRole := range assignments {
if assignedRole == role.Name {
ac.Log.Debug("the role has already been assigned", "rolename", role.Name, "build_in_role", builtInRole)
ac.log.Debug("the role has already been assigned", "rolename", role.Name, "build_in_role", builtInRole)
alreadyAssigned = true
}
}
@ -214,5 +240,5 @@ func (ac *OSSAccessControlService) DeclareFixedRoles(registrations ...accesscont
// RegisterAttributeScopeResolver allows the caller to register scope resolvers for a
// specific scope prefix (ex: datasources:name:)
func (ac *OSSAccessControlService) RegisterAttributeScopeResolver(scopePrefix string, resolver accesscontrol.AttributeScopeResolveFunc) {
ac.ScopeResolver.AddAttributeResolver(scopePrefix, resolver)
ac.scopeResolver.AddAttributeResolver(scopePrefix, resolver)
}

View File

@ -12,7 +12,9 @@ import (
"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/services/accesscontrol/database"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
func setupTestEnv(t testing.TB) *OSSAccessControlService {
@ -20,10 +22,11 @@ func setupTestEnv(t testing.TB) *OSSAccessControlService {
ac := &OSSAccessControlService{
features: featuremgmt.WithFeatures(featuremgmt.FlagAccesscontrol),
UsageStats: &usagestats.UsageStatsMock{T: t},
Log: log.New("accesscontrol"),
usageStats: &usagestats.UsageStatsMock{T: t},
log: log.New("accesscontrol"),
registrations: accesscontrol.RegistrationList{},
ScopeResolver: accesscontrol.NewScopeResolver(),
scopeResolver: accesscontrol.NewScopeResolver(),
provider: database.ProvideService(sqlstore.InitTestDB(t)),
}
return ac
}
@ -145,10 +148,12 @@ func TestUsageMetrics(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
features := featuremgmt.WithFeatures("accesscontrol", tt.enabled)
s := ProvideService(features, &usagestats.UsageStatsMock{T: t})
report, err := s.UsageStats.GetUsageReport(context.Background())
s := ProvideService(
featuremgmt.WithFeatures("accesscontrol", tt.enabled),
&usagestats.UsageStatsMock{T: t},
database.ProvideService(sqlstore.InitTestDB(t)),
)
report, err := s.usageStats.GetUsageReport(context.Background())
assert.Nil(t, err)
assert.Equal(t, tt.expectedValue, report.Metrics["stats.oss.accesscontrol.enabled.count"])
@ -262,8 +267,8 @@ func TestOSSAccessControlService_RegisterFixedRole(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
ac := &OSSAccessControlService{
features: featuremgmt.WithFeatures(),
UsageStats: &usagestats.UsageStatsMock{T: t},
Log: log.New("accesscontrol-test"),
usageStats: &usagestats.UsageStatsMock{T: t},
log: log.New("accesscontrol-test"),
}
for i, run := range tc.runs {
@ -379,12 +384,7 @@ func TestOSSAccessControlService_DeclareFixedRoles(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ac := &OSSAccessControlService{
features: featuremgmt.WithFeatures(featuremgmt.FlagAccesscontrol),
UsageStats: &usagestats.UsageStatsMock{T: t},
Log: log.New("accesscontrol-test"),
registrations: accesscontrol.RegistrationList{},
}
ac := setupTestEnv(t)
// Test
err := ac.DeclareFixedRoles(tt.registrations...)
@ -459,14 +459,7 @@ func TestOSSAccessControlService_RegisterFixedRoles(t *testing.T) {
removeRoleHelper(registration.Role.Name)
}
})
// Setup
ac := &OSSAccessControlService{
features: featuremgmt.WithFeatures(featuremgmt.FlagAccesscontrol),
UsageStats: &usagestats.UsageStatsMock{T: t},
Log: log.New("accesscontrol-test"),
registrations: accesscontrol.RegistrationList{},
}
ac := setupTestEnv(t)
ac.registrations.Append(tt.registrations...)
// Test
@ -541,7 +534,6 @@ func TestOSSAccessControlService_GetUserPermissions(t *testing.T) {
// Setup
ac := setupTestEnv(t)
ac.features = featuremgmt.WithFeatures(featuremgmt.FlagAccesscontrol)
registration.Role.Permissions = []accesscontrol.Permission{tt.rawPerm}
err := ac.DeclareFixedRoles(registration)