mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Access control: Use access control for dashboard and folder (#44702)
* 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>
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
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)))
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package accesscontrol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
var (
|
||||
batchSize = 500
|
||||
)
|
||||
|
||||
type permissionMigrator struct {
|
||||
sess *xorm.Session
|
||||
dialect migrator.Dialect
|
||||
migrator.MigrationBase
|
||||
}
|
||||
|
||||
func (m *permissionMigrator) SQL(dialect migrator.Dialect) string {
|
||||
return "code migration"
|
||||
}
|
||||
|
||||
func (m *permissionMigrator) findRole(orgID int64, name string) (accesscontrol.Role, error) {
|
||||
// check if role exists
|
||||
var role accesscontrol.Role
|
||||
_, err := m.sess.Table("role").Where("org_id = ? AND name = ?", orgID, name).Get(&role)
|
||||
return role, err
|
||||
}
|
||||
|
||||
func (m *permissionMigrator) bulkCreateRoles(allRoles []*accesscontrol.Role) ([]*accesscontrol.Role, error) {
|
||||
if len(allRoles) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
allCreatedRoles := make([]*accesscontrol.Role, 0, len(allRoles))
|
||||
|
||||
createRoles := m.createRoles
|
||||
if m.dialect.DriverName() == migrator.MySQL {
|
||||
createRoles = m.createRolesMySQL
|
||||
}
|
||||
|
||||
// bulk role creations
|
||||
err := batch(len(allRoles), batchSize, func(start, end int) error {
|
||||
roles := allRoles[start:end]
|
||||
createdRoles, err := createRoles(roles, start, end)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allCreatedRoles = append(allCreatedRoles, createdRoles...)
|
||||
return nil
|
||||
})
|
||||
|
||||
return allCreatedRoles, err
|
||||
}
|
||||
|
||||
func (m *permissionMigrator) bulkAssignRoles(rolesMap map[int64]map[string]*accesscontrol.Role, assignments map[int64]map[string]struct{}) error {
|
||||
if len(assignments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ts := time.Now()
|
||||
userRoleAssignments := make([]accesscontrol.UserRole, 0)
|
||||
teamRoleAssignments := make([]accesscontrol.TeamRole, 0)
|
||||
builtInRoleAssignments := make([]accesscontrol.BuiltinRole, 0)
|
||||
|
||||
for orgID, roleNames := range assignments {
|
||||
for name := range roleNames {
|
||||
role, ok := rolesMap[orgID][name]
|
||||
if !ok {
|
||||
return &ErrUnknownRole{name}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "managed:users") {
|
||||
userID, err := strconv.ParseInt(strings.Split(name, ":")[2], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userRoleAssignments = append(userRoleAssignments, accesscontrol.UserRole{
|
||||
OrgID: role.OrgID,
|
||||
RoleID: role.ID,
|
||||
UserID: userID,
|
||||
Created: ts,
|
||||
})
|
||||
} else if strings.HasPrefix(name, "managed:teams") {
|
||||
teamID, err := strconv.ParseInt(strings.Split(name, ":")[2], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
teamRoleAssignments = append(teamRoleAssignments, accesscontrol.TeamRole{
|
||||
OrgID: role.OrgID,
|
||||
RoleID: role.ID,
|
||||
TeamID: teamID,
|
||||
Created: ts,
|
||||
})
|
||||
} else if strings.HasPrefix(name, "managed:builtins") {
|
||||
builtIn := strings.Title(strings.Split(name, ":")[2])
|
||||
builtInRoleAssignments = append(builtInRoleAssignments, accesscontrol.BuiltinRole{
|
||||
OrgID: role.OrgID,
|
||||
RoleID: role.ID,
|
||||
Role: builtIn,
|
||||
Created: ts,
|
||||
Updated: ts,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := batch(len(userRoleAssignments), batchSize, func(start, end int) error {
|
||||
_, err := m.sess.Table("user_role").InsertMulti(userRoleAssignments[start:end])
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = batch(len(teamRoleAssignments), batchSize, func(start, end int) error {
|
||||
_, err := m.sess.Table("team_role").InsertMulti(teamRoleAssignments[start:end])
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return batch(len(builtInRoleAssignments), batchSize, func(start, end int) error {
|
||||
_, err := m.sess.Table("builtin_role").InsertMulti(builtInRoleAssignments[start:end])
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// createRoles creates a list of roles and returns their id, orgID, name in a single query
|
||||
func (m *permissionMigrator) createRoles(roles []*accesscontrol.Role, start int, end int) ([]*accesscontrol.Role, error) {
|
||||
ts := time.Now()
|
||||
createdRoles := make([]*accesscontrol.Role, 0, len(roles))
|
||||
valueStrings := make([]string, len(roles))
|
||||
args := make([]interface{}, 0, len(roles)*5)
|
||||
|
||||
for i, r := range roles {
|
||||
uid, err := generateNewRoleUID(m.sess, r.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
valueStrings[i] = "(?, ?, ?, 1, ?, ?)"
|
||||
args = append(args, r.OrgID, uid, r.Name, ts, ts)
|
||||
}
|
||||
|
||||
// Insert and fetch at once
|
||||
valueString := strings.Join(valueStrings, ",")
|
||||
sql := fmt.Sprintf("INSERT INTO role (org_id, uid, name, version, created, updated) VALUES %s RETURNING id, org_id, name", valueString)
|
||||
if errCreate := m.sess.SQL(sql, args...).Find(&createdRoles); errCreate != nil {
|
||||
return nil, errCreate
|
||||
}
|
||||
|
||||
return createdRoles, nil
|
||||
}
|
||||
|
||||
// createRolesMySQL creates a list of roles then fetches them
|
||||
func (m *permissionMigrator) createRolesMySQL(roles []*accesscontrol.Role, start int, end int) ([]*accesscontrol.Role, error) {
|
||||
ts := time.Now()
|
||||
createdRoles := make([]*accesscontrol.Role, 0, len(roles))
|
||||
|
||||
where := make([]string, len(roles))
|
||||
args := make([]interface{}, 0, len(roles)*2)
|
||||
|
||||
for i := range roles {
|
||||
uid, err := generateNewRoleUID(m.sess, roles[i].OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles[i].UID = uid
|
||||
roles[i].Created = ts
|
||||
roles[i].Updated = ts
|
||||
|
||||
where[i] = ("(org_id = ? AND uid = ?)")
|
||||
args = append(args, roles[i].OrgID, uid)
|
||||
}
|
||||
|
||||
// Insert roles
|
||||
if _, errCreate := m.sess.Table("role").Insert(&roles); errCreate != nil {
|
||||
return nil, errCreate
|
||||
}
|
||||
|
||||
// Fetch newly created roles
|
||||
if errFindInsertions := m.sess.Table("role").
|
||||
Where(strings.Join(where, " OR "), args...).
|
||||
Find(&createdRoles); errFindInsertions != nil {
|
||||
return nil, errFindInsertions
|
||||
}
|
||||
|
||||
return createdRoles, nil
|
||||
}
|
||||
|
||||
func batch(count, batchSize int, eachFn func(start, end int) error) error {
|
||||
for i := 0; i < count; {
|
||||
end := i + batchSize
|
||||
if end > count {
|
||||
end = count
|
||||
}
|
||||
|
||||
if err := eachFn(i, end); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i = end
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateNewRoleUID(sess *xorm.Session, orgID int64) (string, error) {
|
||||
for i := 0; i < 3; i++ {
|
||||
uid := util.GenerateShortUID()
|
||||
|
||||
exists, err := sess.Where("org_id=? AND uid=?", orgID, uid).Get(&accesscontrol.Role{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return uid, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("failed to generate uid")
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package accesscontrol
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm"
|
||||
@@ -11,12 +10,10 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
TeamsMigrationID = "teams permissions migration"
|
||||
batchSize = 500
|
||||
)
|
||||
|
||||
func AddTeamMembershipMigrations(mg *migrator.Migrator) {
|
||||
@@ -26,14 +23,8 @@ func AddTeamMembershipMigrations(mg *migrator.Migrator) {
|
||||
var _ migrator.CodeMigration = new(teamPermissionMigrator)
|
||||
|
||||
type teamPermissionMigrator struct {
|
||||
migrator.MigrationBase
|
||||
permissionMigrator
|
||||
editorsCanAdmin bool
|
||||
sess *xorm.Session
|
||||
dialect migrator.Dialect
|
||||
}
|
||||
|
||||
func (p *teamPermissionMigrator) getAssignmentKey(orgID int64, name string) string {
|
||||
return fmt.Sprint(orgID, "-", name)
|
||||
}
|
||||
|
||||
func (p *teamPermissionMigrator) SQL(dialect migrator.Dialect) string {
|
||||
@@ -46,168 +37,6 @@ func (p *teamPermissionMigrator) Exec(sess *xorm.Session, migrator *migrator.Mig
|
||||
return p.migrateMemberships()
|
||||
}
|
||||
|
||||
func generateNewRoleUID(sess *xorm.Session, orgID int64) (string, error) {
|
||||
for i := 0; i < 3; i++ {
|
||||
uid := util.GenerateShortUID()
|
||||
|
||||
exists, err := sess.Where("org_id=? AND uid=?", orgID, uid).Get(&accesscontrol.Role{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return uid, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("failed to generate uid")
|
||||
}
|
||||
|
||||
func (p *teamPermissionMigrator) findRole(orgID int64, name string) (accesscontrol.Role, error) {
|
||||
// check if role exists
|
||||
var role accesscontrol.Role
|
||||
_, err := p.sess.Table("role").Where("org_id = ? AND name = ?", orgID, name).Get(&role)
|
||||
return role, err
|
||||
}
|
||||
|
||||
func batch(count, batchSize int, eachFn func(start, end int) error) error {
|
||||
for i := 0; i < count; {
|
||||
end := i + batchSize
|
||||
if end > count {
|
||||
end = count
|
||||
}
|
||||
|
||||
if err := eachFn(i, end); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i = end
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *teamPermissionMigrator) bulkCreateRoles(allRoles []*accesscontrol.Role) ([]*accesscontrol.Role, error) {
|
||||
if len(allRoles) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
allCreatedRoles := make([]*accesscontrol.Role, 0, len(allRoles))
|
||||
|
||||
createRoles := p.createRoles
|
||||
if p.dialect.DriverName() == migrator.MySQL {
|
||||
createRoles = p.createRolesMySQL
|
||||
}
|
||||
|
||||
// bulk role creations
|
||||
err := batch(len(allRoles), batchSize, func(start, end int) error {
|
||||
roles := allRoles[start:end]
|
||||
createdRoles, err := createRoles(roles, start, end)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allCreatedRoles = append(allCreatedRoles, createdRoles...)
|
||||
return nil
|
||||
})
|
||||
|
||||
return allCreatedRoles, err
|
||||
}
|
||||
|
||||
// createRoles creates a list of roles and returns their id, orgID, name in a single query
|
||||
func (p *teamPermissionMigrator) createRoles(roles []*accesscontrol.Role, start int, end int) ([]*accesscontrol.Role, error) {
|
||||
ts := time.Now()
|
||||
createdRoles := make([]*accesscontrol.Role, 0, len(roles))
|
||||
valueStrings := make([]string, len(roles))
|
||||
args := make([]interface{}, 0, len(roles)*5)
|
||||
|
||||
for i, r := range roles {
|
||||
uid, err := generateNewRoleUID(p.sess, r.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
valueStrings[i] = "(?, ?, ?, 1, ?, ?)"
|
||||
args = append(args, r.OrgID, uid, r.Name, ts, ts)
|
||||
}
|
||||
|
||||
// Insert and fetch at once
|
||||
valueString := strings.Join(valueStrings, ",")
|
||||
sql := fmt.Sprintf("INSERT INTO role (org_id, uid, name, version, created, updated) VALUES %s RETURNING id, org_id, name", valueString)
|
||||
if errCreate := p.sess.SQL(sql, args...).Find(&createdRoles); errCreate != nil {
|
||||
return nil, errCreate
|
||||
}
|
||||
|
||||
return createdRoles, nil
|
||||
}
|
||||
|
||||
// createRolesMySQL creates a list of roles then fetches them
|
||||
func (p *teamPermissionMigrator) createRolesMySQL(roles []*accesscontrol.Role, start int, end int) ([]*accesscontrol.Role, error) {
|
||||
ts := time.Now()
|
||||
createdRoles := make([]*accesscontrol.Role, 0, len(roles))
|
||||
|
||||
where := make([]string, len(roles))
|
||||
args := make([]interface{}, 0, len(roles)*2)
|
||||
|
||||
for i := range roles {
|
||||
uid, err := generateNewRoleUID(p.sess, roles[i].OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles[i].UID = uid
|
||||
roles[i].Created = ts
|
||||
roles[i].Updated = ts
|
||||
|
||||
where[i] = ("(org_id = ? AND uid = ?)")
|
||||
args = append(args, roles[i].OrgID, uid)
|
||||
}
|
||||
|
||||
// Insert roles
|
||||
if _, errCreate := p.sess.Table("role").Insert(&roles); errCreate != nil {
|
||||
return nil, errCreate
|
||||
}
|
||||
|
||||
// Fetch newly created roles
|
||||
if errFindInsertions := p.sess.Table("role").
|
||||
Where(strings.Join(where, " OR "), args...).
|
||||
Find(&createdRoles); errFindInsertions != nil {
|
||||
return nil, errFindInsertions
|
||||
}
|
||||
|
||||
return createdRoles, nil
|
||||
}
|
||||
|
||||
func (p *teamPermissionMigrator) bulkAssignRoles(rolesMap map[string]*accesscontrol.Role, assignments map[int64]map[string]struct{}) error {
|
||||
if len(assignments) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ts := time.Now()
|
||||
|
||||
roleAssignments := make([]accesscontrol.UserRole, 0, len(assignments))
|
||||
for userID, rolesByRoleKey := range assignments {
|
||||
for key := range rolesByRoleKey {
|
||||
role, ok := rolesMap[key]
|
||||
if !ok {
|
||||
return &ErrUnknownRole{key}
|
||||
}
|
||||
|
||||
roleAssignments = append(roleAssignments, accesscontrol.UserRole{
|
||||
OrgID: role.OrgID,
|
||||
RoleID: role.ID,
|
||||
UserID: userID,
|
||||
Created: ts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return batch(len(roleAssignments), batchSize, func(start, end int) error {
|
||||
roleAssignmentsChunk := roleAssignments[start:end]
|
||||
_, err := p.sess.Table("user_role").InsertMulti(roleAssignmentsChunk)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// setRolePermissions sets the role permissions deleting any team related ones before inserting any.
|
||||
func (p *teamPermissionMigrator) setRolePermissions(roleID int64, permissions []accesscontrol.Permission) error {
|
||||
// First drop existing permissions
|
||||
@@ -316,8 +145,7 @@ func (p *teamPermissionMigrator) migrateMemberships() error {
|
||||
|
||||
// Populate rolesMap with the newly created roles
|
||||
for i := range createdRoles {
|
||||
roleKey := p.getAssignmentKey(createdRoles[i].OrgID, createdRoles[i].Name)
|
||||
rolesByOrg[roleKey] = createdRoles[i]
|
||||
rolesByOrg[createdRoles[i].OrgID][createdRoles[i].Name] = createdRoles[i]
|
||||
}
|
||||
|
||||
// Assign newly created roles
|
||||
@@ -329,14 +157,12 @@ func (p *teamPermissionMigrator) migrateMemberships() error {
|
||||
return p.setRolePermissionsForOrgs(userPermissionsByOrg, rolesByOrg)
|
||||
}
|
||||
|
||||
func (p *teamPermissionMigrator) setRolePermissionsForOrgs(userPermissionsByOrg map[int64]map[int64][]accesscontrol.Permission, rolesByOrg map[string]*accesscontrol.Role) error {
|
||||
func (p *teamPermissionMigrator) setRolePermissionsForOrgs(userPermissionsByOrg map[int64]map[int64][]accesscontrol.Permission, rolesByOrg map[int64]map[string]*accesscontrol.Role) error {
|
||||
for orgID, userPermissions := range userPermissionsByOrg {
|
||||
for userID, permissions := range userPermissions {
|
||||
key := p.getAssignmentKey(orgID, fmt.Sprintf("managed:users:%d:permissions", userID))
|
||||
|
||||
role, ok := rolesByOrg[key]
|
||||
role, ok := rolesByOrg[orgID][fmt.Sprintf("managed:users:%d:permissions", userID)]
|
||||
if !ok {
|
||||
return &ErrUnknownRole{key}
|
||||
return &ErrUnknownRole{fmt.Sprintf("managed:users:%d:permissions", userID)}
|
||||
}
|
||||
|
||||
if errSettingPerms := p.setRolePermissions(role.ID, permissions); errSettingPerms != nil {
|
||||
@@ -347,12 +173,12 @@ func (p *teamPermissionMigrator) setRolePermissionsForOrgs(userPermissionsByOrg
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *teamPermissionMigrator) sortRolesToAssign(userPermissionsByOrg map[int64]map[int64][]accesscontrol.Permission) ([]*accesscontrol.Role, map[int64]map[string]struct{}, map[string]*accesscontrol.Role, error) {
|
||||
func (p *teamPermissionMigrator) sortRolesToAssign(userPermissionsByOrg map[int64]map[int64][]accesscontrol.Permission) ([]*accesscontrol.Role, map[int64]map[string]struct{}, map[int64]map[string]*accesscontrol.Role, error) {
|
||||
var rolesToCreate []*accesscontrol.Role
|
||||
|
||||
assignments := map[int64]map[string]struct{}{}
|
||||
|
||||
rolesByOrg := map[string]*accesscontrol.Role{}
|
||||
rolesByOrg := map[int64]map[string]*accesscontrol.Role{}
|
||||
for orgID, userPermissions := range userPermissionsByOrg {
|
||||
for userID := range userPermissions {
|
||||
roleName := fmt.Sprintf("managed:users:%d:permissions", userID)
|
||||
@@ -361,10 +187,12 @@ func (p *teamPermissionMigrator) sortRolesToAssign(userPermissionsByOrg map[int6
|
||||
return nil, nil, nil, errFindingRoles
|
||||
}
|
||||
|
||||
roleKey := p.getAssignmentKey(orgID, roleName)
|
||||
if rolesByOrg[orgID] == nil {
|
||||
rolesByOrg[orgID] = map[string]*accesscontrol.Role{}
|
||||
}
|
||||
|
||||
if role.ID != 0 {
|
||||
rolesByOrg[roleKey] = &role
|
||||
rolesByOrg[orgID][roleName] = &role
|
||||
} else {
|
||||
roleToCreate := &accesscontrol.Role{
|
||||
Name: roleName,
|
||||
@@ -372,13 +200,13 @@ func (p *teamPermissionMigrator) sortRolesToAssign(userPermissionsByOrg map[int6
|
||||
}
|
||||
rolesToCreate = append(rolesToCreate, roleToCreate)
|
||||
|
||||
userAssignments, initialized := assignments[userID]
|
||||
userAssignments, initialized := assignments[orgID]
|
||||
if !initialized {
|
||||
userAssignments = map[string]struct{}{}
|
||||
}
|
||||
|
||||
userAssignments[roleKey] = struct{}{}
|
||||
assignments[userID] = userAssignments
|
||||
userAssignments[roleName] = struct{}{}
|
||||
assignments[orgID] = userAssignments
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user