mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
26ddeaf3d7
commit
e844b263c7
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user