4982ca3b1d
* Add actions and scopes * add resource service for dashboard and folder * Add dashboard guardian with fgac permission evaluation * Add CanDelete function to guardian interface * Add CanDelete property to folder and dashboard dto and set values * change to correct function name * Add accesscontrol to folder endpoints * add access control to dashboard endpoints * check access for nav links * Add fixed roles for dashboard and folders * use correct package * add hack to override guardian Constructor if accesscontrol is enabled * Add services * Add function to handle api backward compatability * Add permissionServices to HttpServer * Set permission when new dashboard is created * Add default permission when creating new dashboard * Set default permission when creating folder and dashboard * Add access control filter for dashboard search * Add to accept list * Add accesscontrol to dashboardimport * Disable access control in tests * Add check to see if user is allow to create a dashboard * Use SetPermissions * Use function to set several permissions at once * remove permissions for folder and dashboard on delete * update required permission * set permission for provisioning * Add CanCreate to dashboard guardian and set correct permisisons for provisioning * Dont set admin on folder / dashboard creation * Add dashboard and folder permission migrations * Add tests for CanCreate * Add roles and update descriptions * Solve uid to id for dashboard and folder permissions * Add folder and dashboard actions to permission filter * Handle viewer_can_edit flag * set folder and dashboard permissions services * Add dashboard permissions when importing a new dashboard * Set access control permissions on provisioning * Pass feature flags and only set permissions if access control is enabled * only add default permissions for folders and dashboards without folders * Batch create permissions in migrations * Remove `dashboards:edit` action * Remove unused function from interface * Update pkg/services/guardian/accesscontrol_guardian_test.go Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
231 lines
6.5 KiB
Go
231 lines
6.5 KiB
Go
package accesscontrol
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"xorm.io/xorm"
|
|
|
|
"github.com/grafana/grafana/pkg/models"
|
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
|
)
|
|
|
|
var dashboardPermissionTranslation = map[models.PermissionType][]string{
|
|
models.PERMISSION_VIEW: {
|
|
ac.ActionDashboardsRead,
|
|
},
|
|
models.PERMISSION_EDIT: {
|
|
ac.ActionDashboardsRead,
|
|
ac.ActionDashboardsWrite,
|
|
ac.ActionDashboardsCreate,
|
|
ac.ActionDashboardsDelete,
|
|
},
|
|
models.PERMISSION_ADMIN: {
|
|
ac.ActionDashboardsRead,
|
|
ac.ActionDashboardsWrite,
|
|
ac.ActionDashboardsCreate,
|
|
ac.ActionDashboardsDelete,
|
|
ac.ActionDashboardsPermissionsRead,
|
|
ac.ActionDashboardsPermissionsWrite,
|
|
},
|
|
}
|
|
|
|
var folderPermissionTranslation = map[models.PermissionType][]string{
|
|
models.PERMISSION_VIEW: append(dashboardPermissionTranslation[models.PERMISSION_VIEW], []string{
|
|
ac.ActionFoldersRead,
|
|
}...),
|
|
models.PERMISSION_EDIT: append(dashboardPermissionTranslation[models.PERMISSION_EDIT], []string{
|
|
ac.ActionFoldersRead,
|
|
ac.ActionFoldersWrite,
|
|
ac.ActionFoldersCreate,
|
|
ac.ActionFoldersDelete,
|
|
}...),
|
|
models.PERMISSION_ADMIN: append(dashboardPermissionTranslation[models.PERMISSION_ADMIN], []string{
|
|
ac.ActionFoldersRead,
|
|
ac.ActionFoldersWrite,
|
|
ac.ActionFoldersCreate,
|
|
ac.ActionFoldersDelete,
|
|
ac.ActionFoldersPermissionsRead,
|
|
ac.ActionFoldersPermissionsWrite,
|
|
}...),
|
|
}
|
|
|
|
func AddDashboardPermissionsMigrator(mg *migrator.Migrator) {
|
|
mg.AddMigration("dashboard permissions", &dashboardPermissionsMigrator{})
|
|
}
|
|
|
|
var _ migrator.CodeMigration = new(dashboardPermissionsMigrator)
|
|
|
|
type dashboardPermissionsMigrator struct {
|
|
permissionMigrator
|
|
}
|
|
|
|
type dashboard struct {
|
|
ID int64 `xorm:"id"`
|
|
FolderID int64 `xorm:"folder_id"`
|
|
OrgID int64 `xorm:"org_id"`
|
|
IsFolder bool
|
|
}
|
|
|
|
func (m dashboardPermissionsMigrator) Exec(sess *xorm.Session, migrator *migrator.Migrator) error {
|
|
m.sess = sess
|
|
m.dialect = migrator.Dialect
|
|
|
|
var dashboards []dashboard
|
|
if err := m.sess.SQL("SELECT id, is_folder, folder_id, org_id FROM dashboard").Find(&dashboards); err != nil {
|
|
return err
|
|
}
|
|
|
|
var acl []models.DashboardAcl
|
|
if err := m.sess.Find(&acl); err != nil {
|
|
return err
|
|
}
|
|
|
|
aclMap := make(map[int64][]models.DashboardAcl, len(acl))
|
|
for _, p := range acl {
|
|
aclMap[p.DashboardID] = append(aclMap[p.DashboardID], p)
|
|
}
|
|
|
|
if err := m.migratePermissions(dashboards, aclMap); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m dashboardPermissionsMigrator) migratePermissions(dashboards []dashboard, aclMap map[int64][]models.DashboardAcl) error {
|
|
permissionMap := map[int64]map[string][]*ac.Permission{}
|
|
for _, d := range dashboards {
|
|
if d.ID == -1 {
|
|
continue
|
|
}
|
|
acls := aclMap[d.ID]
|
|
if permissionMap[d.OrgID] == nil {
|
|
permissionMap[d.OrgID] = map[string][]*ac.Permission{}
|
|
}
|
|
|
|
if (d.IsFolder || d.FolderID == 0) && len(acls) == 0 {
|
|
permissionMap[d.OrgID]["managed:builtins:editor:permissions"] = append(
|
|
permissionMap[d.OrgID]["managed:builtins:editor:permissions"],
|
|
m.mapPermission(d.ID, models.PERMISSION_EDIT, d.IsFolder)...,
|
|
)
|
|
permissionMap[d.OrgID]["managed:builtins:viewer:permissions"] = append(
|
|
permissionMap[d.OrgID]["managed:builtins:viewer:permissions"],
|
|
m.mapPermission(d.ID, models.PERMISSION_VIEW, d.IsFolder)...,
|
|
)
|
|
} else {
|
|
for _, a := range acls {
|
|
permissionMap[d.OrgID][getRoleName(a)] = append(
|
|
permissionMap[d.OrgID][getRoleName(a)],
|
|
m.mapPermission(d.ID, a.Permission, d.IsFolder)...,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
var allRoles []*ac.Role
|
|
rolesToCreate := []*ac.Role{}
|
|
assignments := map[int64]map[string]struct{}{}
|
|
for orgID, roles := range permissionMap {
|
|
for name := range roles {
|
|
role, err := m.findRole(orgID, name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if role.ID == 0 {
|
|
rolesToCreate = append(rolesToCreate, &ac.Role{OrgID: orgID, Name: name})
|
|
if _, ok := assignments[orgID]; !ok {
|
|
assignments[orgID] = map[string]struct{}{}
|
|
}
|
|
assignments[orgID][name] = struct{}{}
|
|
} else {
|
|
allRoles = append(allRoles, &role)
|
|
}
|
|
}
|
|
}
|
|
|
|
createdRoles, err := m.bulkCreateRoles(rolesToCreate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rolesToAssign := map[int64]map[string]*ac.Role{}
|
|
for i := range createdRoles {
|
|
if _, ok := rolesToAssign[createdRoles[i].OrgID]; !ok {
|
|
rolesToAssign[createdRoles[i].OrgID] = map[string]*ac.Role{}
|
|
}
|
|
rolesToAssign[createdRoles[i].OrgID][createdRoles[i].Name] = createdRoles[i]
|
|
allRoles = append(allRoles, createdRoles[i])
|
|
}
|
|
|
|
if err := m.bulkAssignRoles(rolesToAssign, assignments); err != nil {
|
|
return err
|
|
}
|
|
|
|
return m.setPermissions(allRoles, permissionMap)
|
|
}
|
|
|
|
func (m dashboardPermissionsMigrator) setPermissions(allRoles []*ac.Role, permissionMap map[int64]map[string][]*ac.Permission) error {
|
|
now := time.Now()
|
|
for _, role := range allRoles {
|
|
if _, err := m.sess.Exec("DELETE FROM permission WHERE role_id = ? AND (action LIKE ? OR action LIKE ?)", role.ID, "dashboards%", "folders%"); err != nil {
|
|
return err
|
|
}
|
|
var permissions []ac.Permission
|
|
mappedPermissions := permissionMap[role.OrgID][role.Name]
|
|
for _, p := range mappedPermissions {
|
|
permissions = append(permissions, ac.Permission{
|
|
RoleID: role.ID,
|
|
Action: p.Action,
|
|
Scope: p.Scope,
|
|
Updated: now,
|
|
Created: now,
|
|
})
|
|
}
|
|
|
|
err := batch(len(permissions), batchSize, func(start, end int) error {
|
|
if _, err := m.sess.InsertMulti(permissions[start:end]); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m dashboardPermissionsMigrator) mapPermission(id int64, p models.PermissionType, isFolder bool) []*ac.Permission {
|
|
if isFolder {
|
|
actions := folderPermissionTranslation[p]
|
|
scope := ac.Scope("folders", "id", strconv.FormatInt(id, 10))
|
|
permissions := make([]*ac.Permission, 0, len(actions))
|
|
for _, action := range actions {
|
|
permissions = append(permissions, &ac.Permission{Action: action, Scope: scope})
|
|
}
|
|
return permissions
|
|
}
|
|
|
|
actions := dashboardPermissionTranslation[p]
|
|
scope := ac.Scope("dashboards", "id", strconv.FormatInt(id, 10))
|
|
permissions := make([]*ac.Permission, 0, len(actions))
|
|
for _, action := range actions {
|
|
permissions = append(permissions, &ac.Permission{Action: action, Scope: scope})
|
|
}
|
|
return permissions
|
|
}
|
|
|
|
func getRoleName(p models.DashboardAcl) string {
|
|
if p.UserID != 0 {
|
|
return fmt.Sprintf("managed:users:%d:permissions", p.UserID)
|
|
}
|
|
if p.TeamID != 0 {
|
|
return fmt.Sprintf("managed:teams:%d:permissions", p.TeamID)
|
|
}
|
|
return fmt.Sprintf("managed:builtins:%s:permissions", strings.ToLower(string(*p.Role)))
|
|
}
|