1547e279b1
* introduce a fallback handler that checks that role is Viewer.
* update UI nav links to allow alerting tabs for anonymous user
* update rule api to check for Viewer role instead of SignedIn when RBAC is disabled
(cherry picked from commit f7f2253072)
233 lines
7.7 KiB
Go
233 lines
7.7 KiB
Go
package accesscontrol
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/grafana/grafana/pkg/models"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
)
|
|
|
|
type Options struct {
|
|
ReloadCache bool
|
|
}
|
|
|
|
type AccessControl interface {
|
|
// Evaluate evaluates access to the given resources.
|
|
Evaluate(ctx context.Context, user *models.SignedInUser, evaluator Evaluator) (bool, error)
|
|
|
|
// GetUserPermissions returns user permissions.
|
|
GetUserPermissions(ctx context.Context, user *models.SignedInUser, options Options) ([]*Permission, error)
|
|
|
|
// GetUserRoles returns user roles.
|
|
GetUserRoles(ctx context.Context, user *models.SignedInUser) ([]*RoleDTO, 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
|
|
|
|
// RegisterAttributeScopeResolver allows the caller to register a scope resolver for a
|
|
// specific scope prefix (ex: datasources:name:)
|
|
RegisterAttributeScopeResolver(scopePrefix string, resolver AttributeScopeResolveFunc)
|
|
}
|
|
|
|
type PermissionsProvider interface {
|
|
GetUserPermissions(ctx context.Context, query GetUserPermissionsQuery) ([]*Permission, error)
|
|
}
|
|
|
|
type PermissionsServices interface {
|
|
GetTeamService() PermissionsService
|
|
GetFolderService() PermissionsService
|
|
GetDashboardService() PermissionsService
|
|
GetDataSourceService() 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
|
|
}
|
|
|
|
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.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol)
|
|
}
|