grafana/pkg/services/accesscontrol/accesscontrol.go
Ieva d85df0a560
Service Accounts: Managed permissions for service accounts (#51818)
* backend changes

* frontend changes

* linting

* nit

* import order

* allow SA creator to access the SA page

* fix merge

* tests

* fix frontend tests

Co-authored-by: alexanderzobnin alexanderzobnin@gmail.com
2022-07-08 05:53:18 -04:00

284 lines
8.9 KiB
Go

package accesscontrol
import (
"context"
"fmt"
"strings"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting"
)
type Options struct {
ReloadCache bool
}
type AccessControl interface {
registry.ProvidesUsageStats
// Evaluate evaluates access to the given resources.
Evaluate(ctx context.Context, user *models.SignedInUser, evaluator Evaluator) (bool, error)
// GetUserPermissions returns user permissions with only action and scope fields set.
GetUserPermissions(ctx context.Context, user *models.SignedInUser, options Options) ([]Permission, error)
//IsDisabled returns if access control is enabled or not
IsDisabled() bool
// DeclareFixedRoles allows the caller to declare, to the service, fixed roles and their
// assignments to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin"
DeclareFixedRoles(...RoleRegistration) error
// RegisterScopeAttributeResolver allows the caller to register a scope resolver for a
// specific scope prefix (ex: datasources:name:)
RegisterScopeAttributeResolver(scopePrefix string, resolver ScopeAttributeResolver)
}
type RoleRegistry interface {
// RegisterFixedRoles registers all roles declared to AccessControl
RegisterFixedRoles(ctx context.Context) error
}
type PermissionsStore interface {
// GetUserPermissions returns user permissions with only action and scope fields set.
GetUserPermissions(ctx context.Context, query GetUserPermissionsQuery) ([]Permission, error)
}
type TeamPermissionsService interface {
GetPermissions(ctx context.Context, user *models.SignedInUser, resourceID string) ([]ResourcePermission, error)
SetUserPermission(ctx context.Context, orgID int64, user User, resourceID, permission string) (*ResourcePermission, error)
}
type FolderPermissionsService interface {
PermissionsService
}
type DashboardPermissionsService interface {
PermissionsService
}
type DatasourcePermissionsService interface {
PermissionsService
}
type ServiceAccountPermissionsService interface {
PermissionsService
}
type PermissionsService interface {
// GetPermissions returns all permissions for given resourceID
GetPermissions(ctx context.Context, user *models.SignedInUser, resourceID string) ([]ResourcePermission, error)
// SetUserPermission sets permission on resource for a user
SetUserPermission(ctx context.Context, orgID int64, user User, resourceID, permission string) (*ResourcePermission, error)
// SetTeamPermission sets permission on resource for a team
SetTeamPermission(ctx context.Context, orgID, teamID int64, resourceID, permission string) (*ResourcePermission, error)
// SetBuiltInRolePermission sets permission on resource for a built-in role (Admin, Editor, Viewer)
SetBuiltInRolePermission(ctx context.Context, orgID int64, builtInRole string, resourceID string, permission string) (*ResourcePermission, error)
// SetPermissions sets several permissions on resource for either built-in role, team or user
SetPermissions(ctx context.Context, orgID int64, resourceID string, commands ...SetResourcePermissionCommand) ([]ResourcePermission, error)
// MapActions will map actions for a ResourcePermissions to it's "friendly" name configured in PermissionsToActions map.
MapActions(permission ResourcePermission) string
}
type User struct {
ID int64
IsExternal bool
}
// Metadata contains user accesses for a given resource
// Ex: map[string]bool{"create":true, "delete": true}
type Metadata map[string]bool
// HasGlobalAccess checks user access with globally assigned permissions only
func HasGlobalAccess(ac AccessControl, c *models.ReqContext) func(fallback func(*models.ReqContext) bool, evaluator Evaluator) bool {
return func(fallback func(*models.ReqContext) bool, evaluator Evaluator) bool {
if ac.IsDisabled() {
return fallback(c)
}
userCopy := *c.SignedInUser
userCopy.OrgId = GlobalOrgID
userCopy.OrgRole = ""
userCopy.OrgName = ""
hasAccess, err := ac.Evaluate(c.Req.Context(), &userCopy, evaluator)
if err != nil {
c.Logger.Error("Error from access control system", "error", err)
return false
}
return hasAccess
}
}
func HasAccess(ac AccessControl, c *models.ReqContext) func(fallback func(*models.ReqContext) bool, evaluator Evaluator) bool {
return func(fallback func(*models.ReqContext) bool, evaluator Evaluator) bool {
if ac.IsDisabled() {
return fallback(c)
}
hasAccess, err := ac.Evaluate(c.Req.Context(), c.SignedInUser, evaluator)
if err != nil {
c.Logger.Error("Error from access control system", "error", err)
return false
}
return hasAccess
}
}
var ReqSignedIn = func(c *models.ReqContext) bool {
return c.IsSignedIn
}
var ReqGrafanaAdmin = func(c *models.ReqContext) bool {
return c.IsGrafanaAdmin
}
// ReqViewer returns true if the current user has models.ROLE_VIEWER. Note: this can be anonymous user as well
var ReqViewer = func(c *models.ReqContext) bool {
return c.OrgRole.Includes(models.ROLE_VIEWER)
}
var ReqOrgAdmin = func(c *models.ReqContext) bool {
return c.OrgRole == models.ROLE_ADMIN
}
var ReqOrgAdminOrEditor = func(c *models.ReqContext) bool {
return c.OrgRole == models.ROLE_ADMIN || c.OrgRole == models.ROLE_EDITOR
}
func BuildPermissionsMap(permissions []Permission) map[string]bool {
permissionsMap := make(map[string]bool)
for _, p := range permissions {
permissionsMap[p.Action] = true
}
return permissionsMap
}
// GroupScopesByAction will group scopes on action
func GroupScopesByAction(permissions []Permission) map[string][]string {
m := make(map[string][]string)
for _, p := range permissions {
m[p.Action] = append(m[p.Action], p.Scope)
}
return m
}
func ValidateScope(scope string) bool {
prefix, last := scope[:len(scope)-1], scope[len(scope)-1]
// verify that last char is either ':' or '/' if last character of scope is '*'
if len(prefix) > 0 && last == '*' {
lastChar := prefix[len(prefix)-1]
if lastChar != ':' && lastChar != '/' {
return false
}
}
return !strings.ContainsAny(prefix, "*?")
}
func addActionToMetadata(allMetadata map[string]Metadata, action, id string) map[string]Metadata {
metadata, initialized := allMetadata[id]
if !initialized {
metadata = Metadata{action: true}
} else {
metadata[action] = true
}
allMetadata[id] = metadata
return allMetadata
}
// GetResourcesMetadata returns a map of accesscontrol metadata, listing for each resource, users available actions
func GetResourcesMetadata(ctx context.Context, permissions map[string][]string, prefix string, resourceIDs map[string]bool) map[string]Metadata {
rootPrefix, attributePrefix, ok := extractPrefixes(prefix)
if !ok {
return map[string]Metadata{}
}
allScope := GetResourceAllScope(strings.TrimSuffix(rootPrefix, ":"))
allAttributeScope := Scope(strings.TrimSuffix(attributePrefix, ":"), "*")
// index of the attribute in the scope
attributeIndex := len(attributePrefix)
// Loop through permissions once
result := map[string]Metadata{}
for action, scopes := range permissions {
for _, scope := range scopes {
if scope == "*" || scope == allScope || scope == allAttributeScope {
// Add global action to all resources
for id := range resourceIDs {
result = addActionToMetadata(result, action, id)
}
} else {
if len(scope) > attributeIndex && strings.HasPrefix(scope, attributePrefix) && resourceIDs[scope[attributeIndex:]] {
// Add action to a specific resource
result = addActionToMetadata(result, action, scope[attributeIndex:])
}
}
}
}
return result
}
// MergeMeta will merge actions matching prefix of second metadata into first
func MergeMeta(prefix string, first Metadata, second Metadata) Metadata {
if first == nil {
first = Metadata{}
}
for key := range second {
if strings.HasPrefix(key, prefix) {
first[key] = true
}
}
return first
}
func ManagedUserRoleName(userID int64) string {
return fmt.Sprintf("managed:users:%d:permissions", userID)
}
func ManagedTeamRoleName(teamID int64) string {
return fmt.Sprintf("managed:teams:%d:permissions", teamID)
}
func ManagedBuiltInRoleName(builtInRole string) string {
return fmt.Sprintf("managed:builtins:%s:permissions", strings.ToLower(builtInRole))
}
func extractPrefixes(prefix string) (string, string, bool) {
parts := strings.Split(strings.TrimSuffix(prefix, ":"), ":")
if len(parts) != 2 {
return "", "", false
}
rootPrefix := parts[0] + ":"
attributePrefix := rootPrefix + parts[1] + ":"
return rootPrefix, attributePrefix, true
}
func IsDisabled(cfg *setting.Cfg) bool {
return !cfg.RBACEnabled
}
// GetOrgRoles returns legacy org roles for a user
func GetOrgRoles(cfg *setting.Cfg, user *models.SignedInUser) []string {
roles := []string{string(user.OrgRole)}
// With built-in role simplifying, inheritance is performed upon role registration.
if cfg.RBACBuiltInRoleAssignmentEnabled {
for _, br := range user.OrgRole.Children() {
roles = append(roles, string(br))
}
}
if user.IsGrafanaAdmin {
roles = append(roles, RoleGrafanaAdmin)
}
return roles
}