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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user