Files
Serge Zaitsev 694b9dfe50 Chore: Replace xorm.io/xorm imports (#104458)
* replace xorm.io/xorm imports

* replace xorm from other go.mod files

* clean up workspace

* nolint does not make sense anymore as it is not a module

* try if nolint directive helps

* use nolint:all for xorm

* add more nolints

* try to skip xorm in linter config

* exclude xorm differently

* retrigger ci
2025-05-02 17:13:01 +02:00

218 lines
6.0 KiB
Go

package accesscontrol
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/grafana/grafana/pkg/util/xorm"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)
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)
if err != nil {
return err
}
allCreatedRoles = append(allCreatedRoles, createdRoles...)
return nil
})
return allCreatedRoles, err
}
func (m *permissionMigrator) bulkAssignRoles(allRoles []*accesscontrol.Role) error {
if len(allRoles) == 0 {
return nil
}
ts := time.Now()
userRoleAssignments := make([]accesscontrol.UserRole, 0)
teamRoleAssignments := make([]accesscontrol.TeamRole, 0)
builtInRoleAssignments := make([]accesscontrol.BuiltinRole, 0)
for _, role := range allRoles {
if strings.HasPrefix(role.Name, "managed:users") {
userID, err := strconv.ParseInt(strings.Split(role.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(role.Name, "managed:teams") {
teamID, err := strconv.ParseInt(strings.Split(role.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(role.Name, "managed:builtins") {
builtIn := strings.Title(strings.Split(role.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 fmt.Errorf("failed to create user role assignments: %w", 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 fmt.Errorf("failed to create team role assignments: %w", err)
}
return batch(len(builtInRoleAssignments), batchSize, func(start, end int) error {
if _, err := m.sess.Table("builtin_role").InsertMulti(builtInRoleAssignments[start:end]); err != nil {
return fmt.Errorf("failed to create builtin role assignments: %w", err)
}
return nil
})
}
// createRoles creates a list of roles and returns their id, orgID, name in a single query
func (m *permissionMigrator) createRoles(roles []*accesscontrol.Role) ([]*accesscontrol.Role, error) {
ts := time.Now()
createdRoles := make([]*accesscontrol.Role, 0, len(roles))
valueStrings := make([]string, len(roles))
args := make([]any, 0, len(roles)*5)
for i, r := range roles {
uid, err := GenerateManagedRoleUID(r.OrgID, r.Name)
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, fmt.Errorf("failed to create roles: %w", errCreate)
}
return createdRoles, nil
}
// createRolesMySQL creates a list of roles then fetches them
func (m *permissionMigrator) createRolesMySQL(roles []*accesscontrol.Role) ([]*accesscontrol.Role, error) {
ts := time.Now()
createdRoles := make([]*accesscontrol.Role, 0, len(roles))
where := make([]string, len(roles))
args := make([]any, 0, len(roles)*2)
for i := range roles {
uid, err := GenerateManagedRoleUID(roles[i].OrgID, roles[i].Name)
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
}
// GenerateManagedRoleUID generated a deterministic uid of the form `managed_{org_id}_{type}_{id}`.
func GenerateManagedRoleUID(orgID int64, name string) (string, error) {
parts := strings.Split(name, ":")
if len(parts) != 4 {
return "", fmt.Errorf("unexpected role name: %s", name)
}
return fmt.Sprintf("managed_%d_%s_%s", orgID, parts[1], parts[2]), nil
}