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

231 lines
7.6 KiB
Go

package accesscontrol
import (
"fmt"
"strconv"
"time"
"github.com/grafana/grafana/pkg/util/xorm"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/team"
)
const (
TeamsMigrationID = "teams permissions migration"
)
func AddTeamMembershipMigrations(mg *migrator.Migrator) {
mg.AddMigration(TeamsMigrationID, &teamPermissionMigrator{})
}
var _ migrator.CodeMigration = new(teamPermissionMigrator)
type teamPermissionMigrator struct {
permissionMigrator
}
func (p *teamPermissionMigrator) SQL(dialect migrator.Dialect) string {
return "code migration"
}
func (p *teamPermissionMigrator) Exec(sess *xorm.Session, migrator *migrator.Migrator) error {
p.sess = sess
p.dialect = migrator.Dialect
return p.migrateMemberships()
}
// 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
if _, errDeletingPerms := p.sess.Exec("DELETE FROM permission WHERE role_id = ? AND (action LIKE ? OR action LIKE ?)", roleID, "teams:%", "teams.permissions:%"); errDeletingPerms != nil {
return errDeletingPerms
}
// Then insert new permissions
newPermissions := make([]accesscontrol.Permission, 0, len(permissions))
now := time.Now()
for _, permission := range permissions {
permission.RoleID = roleID
permission.Created = now
permission.Updated = now
newPermissions = append(newPermissions, permission)
}
if _, errInsertPerms := p.sess.InsertMulti(&newPermissions); errInsertPerms != nil {
return errInsertPerms
}
return nil
}
// mapPermissionToRBAC translates the legacy membership (Member or Admin) into RBAC permissions
func (p *teamPermissionMigrator) mapPermissionToRBAC(permission team.PermissionType, teamID int64) []accesscontrol.Permission {
teamIDScope := accesscontrol.Scope("teams", "id", strconv.FormatInt(teamID, 10))
switch permission {
case team.PermissionTypeMember:
return []accesscontrol.Permission{{Action: "teams:read", Scope: teamIDScope}}
case team.PermissionTypeAdmin:
return []accesscontrol.Permission{
{Action: "teams:delete", Scope: teamIDScope},
{Action: "teams:read", Scope: teamIDScope},
{Action: "teams:write", Scope: teamIDScope},
{Action: "teams.permissions:read", Scope: teamIDScope},
{Action: "teams.permissions:write", Scope: teamIDScope},
}
default:
return []accesscontrol.Permission{}
}
}
func (p *teamPermissionMigrator) getUserRoleByOrgMapping() (map[int64]map[int64]string, error) {
var orgUsers []*org.OrgUserDTO
if err := p.sess.SQL(`SELECT * FROM org_user`).Cols("org_user.org_id", "org_user.user_id", "org_user.role").Find(&orgUsers); err != nil {
return nil, err
}
userRolesByOrg := map[int64]map[int64]string{}
// Loop through users and organise them by organization ID
for _, orgUser := range orgUsers {
orgRoles, initialized := userRolesByOrg[orgUser.OrgID]
if !initialized {
orgRoles = map[int64]string{}
}
orgRoles[orgUser.UserID] = orgUser.Role
userRolesByOrg[orgUser.OrgID] = orgRoles
}
return userRolesByOrg, nil
}
// migrateMemberships generate managed permissions for users based on their memberships to teams
func (p *teamPermissionMigrator) migrateMemberships() error {
// Fetch user roles in each org
userRolesByOrg, err := p.getUserRoleByOrgMapping()
if err != nil {
return err
}
// Fetch team memberships
teamMemberships := []*team.TeamMember{}
if err := p.sess.SQL("SELECT * FROM team_member").Find(&teamMemberships); err != nil {
return err
}
// No need to create any roles if there is no team members
if len(teamMemberships) == 0 {
return nil
}
// Loop through memberships and generate associated permissions
// Downgrade team permissions if needed - only admins or editors (when editorsCanAdmin option is enabled)
// can access team administration endpoints
userPermissionsByOrg, errGen := p.generateAssociatedPermissions(teamMemberships, userRolesByOrg)
if errGen != nil {
return errGen
}
// Sort roles that:
// * need to be created and assigned (rolesToCreate)
// * are already created and assigned (rolesByOrg)
rolesToCreate, rolesByOrg, errOrganizeRoles := p.sortRolesToAssign(userPermissionsByOrg)
if errOrganizeRoles != nil {
return errOrganizeRoles
}
// Create missing roles
createdRoles, errCreate := p.bulkCreateRoles(rolesToCreate)
if errCreate != nil {
return errCreate
}
// Populate rolesMap with the newly created roles
for i := range createdRoles {
rolesByOrg[createdRoles[i].OrgID][createdRoles[i].Name] = createdRoles[i]
}
// Assign newly created roles
if errAssign := p.bulkAssignRoles(createdRoles); errAssign != nil {
return errAssign
}
// Set all roles teams related permissions
return p.setRolePermissionsForOrgs(userPermissionsByOrg, rolesByOrg)
}
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 {
role, ok := rolesByOrg[orgID][fmt.Sprintf("managed:users:%d:permissions", userID)]
if !ok {
return &ErrUnknownRole{fmt.Sprintf("managed:users:%d:permissions", userID)}
}
if errSettingPerms := p.setRolePermissions(role.ID, permissions); errSettingPerms != nil {
return errSettingPerms
}
}
}
return nil
}
func (p *teamPermissionMigrator) sortRolesToAssign(userPermissionsByOrg map[int64]map[int64][]accesscontrol.Permission) ([]*accesscontrol.Role, map[int64]map[string]*accesscontrol.Role, error) {
var rolesToCreate []*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)
role, errFindingRoles := p.findRole(orgID, roleName)
if errFindingRoles != nil {
return nil, nil, errFindingRoles
}
if rolesByOrg[orgID] == nil {
rolesByOrg[orgID] = map[string]*accesscontrol.Role{}
}
if role.ID != 0 {
rolesByOrg[orgID][roleName] = &role
} else {
rolesToCreate = append(rolesToCreate, &accesscontrol.Role{Name: roleName, OrgID: orgID})
}
}
}
return rolesToCreate, rolesByOrg, nil
}
func (p *teamPermissionMigrator) generateAssociatedPermissions(teamMemberships []*team.TeamMember,
userRolesByOrg map[int64]map[int64]string) (map[int64]map[int64][]accesscontrol.Permission, error) {
userPermissionsByOrg := map[int64]map[int64][]accesscontrol.Permission{}
for _, m := range teamMemberships {
// Downgrade team permissions if needed:
// only admins or editors (when editorsCanAdmin option is enabled)
// can access team administration endpoints
if m.Permission == team.PermissionTypeAdmin {
if userRolesByOrg[m.OrgID][m.UserID] == string(org.RoleViewer) || (userRolesByOrg[m.OrgID][m.UserID] == string(org.RoleEditor)) {
m.Permission = 0
if _, err := p.sess.Cols("permission").Where("org_id=? and team_id=? and user_id=?", m.OrgID, m.TeamID, m.UserID).Update(m); err != nil {
return nil, err
}
}
}
userPermissions, initialized := userPermissionsByOrg[m.OrgID]
if !initialized {
userPermissions = map[int64][]accesscontrol.Permission{}
}
userPermissions[m.UserID] = append(userPermissions[m.UserID], p.mapPermissionToRBAC(m.Permission, m.TeamID)...)
userPermissionsByOrg[m.OrgID] = userPermissions
}
return userPermissionsByOrg, nil
}