grafana/pkg/services/accesscontrol/ossaccesscontrol/ossaccesscontrol.go
Gabriel MABILLE 54280fc9d7
AccessControl: Resolve attribute based scopes to id based scopes (#40742)
* AccessControl: POC scope attribute resolution

Refactor based on ScopeMutators

test errors and calls to cache

Add comments to tests

Rename logger

Create keywordMutator only once

* AccessControl: Add AttributeScopeResolver registration

Co-authored-by: gamab <gabriel.mabille@grafana.com>

* AccessControl: Add AttributeScopeResolver to datasources

Co-authored-by: gamab <gabriel.mabille@grafana.com>

* Test evaluation with translation

* fix imports

* AccessControl: Test attribute resolver

* Fix trailing white space

* Make ScopeResolver public for enterprise redefine

* Handle wildcard

Co-authored-by: Jguer <joao.guerreiro@grafana.com>

Co-authored-by: jguer <joao.guerreiro@grafana.com>
2022-01-18 17:34:35 +01:00

219 lines
6.8 KiB
Go

package ossaccesscontrol
import (
"context"
"errors"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
"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/setting"
"github.com/prometheus/client_golang/prometheus"
)
func ProvideService(cfg *setting.Cfg, usageStats usagestats.Service) *OSSAccessControlService {
s := &OSSAccessControlService{
Cfg: cfg,
UsageStats: usageStats,
Log: log.New("accesscontrol"),
ScopeResolver: accesscontrol.NewScopeResolver(),
}
s.registerUsageMetrics()
return s
}
// OSSAccessControlService is the service implementing role based access control.
type OSSAccessControlService struct {
Cfg *setting.Cfg
UsageStats usagestats.Service
Log log.Logger
registrations accesscontrol.RegistrationList
ScopeResolver accesscontrol.ScopeResolver
}
func (ac *OSSAccessControlService) IsDisabled() bool {
if ac.Cfg == nil {
return true
}
return !ac.Cfg.FeatureToggles["accesscontrol"]
}
func (ac *OSSAccessControlService) registerUsageMetrics() {
ac.UsageStats.RegisterMetricsFunc(func(context.Context) (map[string]interface{}, error) {
return map[string]interface{}{
"stats.oss.accesscontrol.enabled.count": ac.getUsageMetrics(),
}, nil
})
}
func (ac *OSSAccessControlService) getUsageMetrics() interface{} {
if ac.IsDisabled() {
return 0
}
return 1
}
// Evaluate evaluates access to the given resources
func (ac *OSSAccessControlService) Evaluate(ctx context.Context, user *models.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
timer := prometheus.NewTimer(metrics.MAccessEvaluationsSummary)
defer timer.ObserveDuration()
metrics.MAccessEvaluationCount.Inc()
if user.Permissions == nil {
user.Permissions = map[int64]map[string][]string{}
}
if _, ok := user.Permissions[user.OrgId]; !ok {
permissions, err := ac.GetUserPermissions(ctx, user)
if err != nil {
return false, err
}
user.Permissions[user.OrgId] = accesscontrol.GroupScopesByAction(permissions)
}
attributeMutator := ac.ScopeResolver.GetResolveAttributeScopeMutator(user.OrgId)
resolvedEvaluator, err := evaluator.MutateScopes(ctx, attributeMutator)
if err != nil {
return false, err
}
return resolvedEvaluator.Evaluate(user.Permissions[user.OrgId])
}
// GetUserRoles returns user permissions based on built-in roles
func (ac *OSSAccessControlService) GetUserRoles(ctx context.Context, user *models.SignedInUser) ([]*accesscontrol.RoleDTO, error) {
return nil, errors.New("unsupported function") //OSS users will continue to use builtin roles via GetUserPermissions
}
// GetUserPermissions returns user permissions based on built-in roles
func (ac *OSSAccessControlService) GetUserPermissions(ctx context.Context, user *models.SignedInUser) ([]*accesscontrol.Permission, error) {
timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary)
defer timer.ObserveDuration()
var err error
keywordMutator := ac.ScopeResolver.GetResolveKeywordScopeMutator(user)
builtinRoles := ac.GetUserBuiltInRoles(user)
permissions := make([]*accesscontrol.Permission, 0)
for _, builtin := range builtinRoles {
if roleNames, ok := accesscontrol.FixedRoleGrants[builtin]; ok {
for _, name := range roleNames {
role, exists := accesscontrol.FixedRoles[name]
if !exists {
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)
}
}
}
}
return permissions, nil
}
func (ac *OSSAccessControlService) GetUserBuiltInRoles(user *models.SignedInUser) []string {
roles := []string{string(user.OrgRole)}
for _, role := range user.OrgRole.Children() {
roles = append(roles, string(role))
}
if user.IsGrafanaAdmin {
roles = append(roles, accesscontrol.RoleGrafanaAdmin)
}
return roles
}
func (ac *OSSAccessControlService) saveFixedRole(role accesscontrol.RoleDTO) {
if storedRole, ok := accesscontrol.FixedRoles[role.Name]; ok {
// If a package wants to override another package's role, the version
// 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)
return
}
}
// Save role
accesscontrol.FixedRoles[role.Name] = role
}
func (ac *OSSAccessControlService) assignFixedRole(role accesscontrol.RoleDTO, builtInRoles []string) {
for _, builtInRole := range builtInRoles {
// Only record new assignments
alreadyAssigned := false
assignments, ok := accesscontrol.FixedRoleGrants[builtInRole]
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)
alreadyAssigned = true
}
}
}
if !alreadyAssigned {
assignments = append(assignments, role.Name)
accesscontrol.FixedRoleGrants[builtInRole] = assignments
}
}
}
// RegisterFixedRoles registers all declared roles in RAM
func (ac *OSSAccessControlService) RegisterFixedRoles() error {
// If accesscontrol is disabled no need to register roles
if ac.IsDisabled() {
return nil
}
var err error
ac.registrations.Range(func(registration accesscontrol.RoleRegistration) bool {
ac.registerFixedRole(registration.Role, registration.Grants)
return true
})
return err
}
// RegisterFixedRole saves a fixed role and assigns it to built-in roles
func (ac *OSSAccessControlService) registerFixedRole(role accesscontrol.RoleDTO, builtInRoles []string) {
ac.saveFixedRole(role)
ac.assignFixedRole(role, builtInRoles)
}
// DeclareFixedRoles allow the caller to declare, to the service, fixed roles and their assignments
// to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin"
func (ac *OSSAccessControlService) DeclareFixedRoles(registrations ...accesscontrol.RoleRegistration) error {
// If accesscontrol is disabled no need to register roles
if ac.IsDisabled() {
return nil
}
for _, r := range registrations {
err := accesscontrol.ValidateFixedRole(r.Role)
if err != nil {
return err
}
err = accesscontrol.ValidateBuiltInRoles(r.Grants)
if err != nil {
return err
}
ac.registrations.Append(r)
}
return nil
}
// 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)
}