mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
* 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>
245 lines
8.0 KiB
Go
245 lines
8.0 KiB
Go
package accesscontrol
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
"xorm.io/xorm"
|
|
|
|
"github.com/grafana/grafana/pkg/models"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
|
)
|
|
|
|
const (
|
|
TeamsMigrationID = "teams permissions migration"
|
|
)
|
|
|
|
func AddTeamMembershipMigrations(mg *migrator.Migrator) {
|
|
mg.AddMigration(TeamsMigrationID, &teamPermissionMigrator{editorsCanAdmin: mg.Cfg.EditorsCanAdmin})
|
|
}
|
|
|
|
var _ migrator.CodeMigration = new(teamPermissionMigrator)
|
|
|
|
type teamPermissionMigrator struct {
|
|
permissionMigrator
|
|
editorsCanAdmin bool
|
|
}
|
|
|
|
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
|
|
var newPermissions []accesscontrol.Permission
|
|
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
|
|
}
|
|
|
|
// mapPermissionToFGAC translates the legacy membership (Member or Admin) into FGAC permissions
|
|
func (p *teamPermissionMigrator) mapPermissionToFGAC(permission models.PermissionType, teamID int64) []accesscontrol.Permission {
|
|
teamIDScope := accesscontrol.Scope("teams", "id", strconv.FormatInt(teamID, 10))
|
|
switch permission {
|
|
case 0:
|
|
return []accesscontrol.Permission{{Action: "teams:read", Scope: teamIDScope}}
|
|
case models.PERMISSION_ADMIN:
|
|
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 []*models.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 := []*models.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, assignments)
|
|
// * are already created and assigned (rolesByOrg)
|
|
rolesToCreate, assignments, 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(rolesByOrg, assignments); 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]struct{}, map[int64]map[string]*accesscontrol.Role, error) {
|
|
var rolesToCreate []*accesscontrol.Role
|
|
|
|
assignments := map[int64]map[string]struct{}{}
|
|
|
|
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, nil, errFindingRoles
|
|
}
|
|
|
|
if rolesByOrg[orgID] == nil {
|
|
rolesByOrg[orgID] = map[string]*accesscontrol.Role{}
|
|
}
|
|
|
|
if role.ID != 0 {
|
|
rolesByOrg[orgID][roleName] = &role
|
|
} else {
|
|
roleToCreate := &accesscontrol.Role{
|
|
Name: roleName,
|
|
OrgID: orgID,
|
|
}
|
|
rolesToCreate = append(rolesToCreate, roleToCreate)
|
|
|
|
userAssignments, initialized := assignments[orgID]
|
|
if !initialized {
|
|
userAssignments = map[string]struct{}{}
|
|
}
|
|
|
|
userAssignments[roleName] = struct{}{}
|
|
assignments[orgID] = userAssignments
|
|
}
|
|
}
|
|
}
|
|
|
|
return rolesToCreate, assignments, rolesByOrg, nil
|
|
}
|
|
|
|
func (p *teamPermissionMigrator) generateAssociatedPermissions(teamMemberships []*models.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 == models.PERMISSION_ADMIN {
|
|
if userRolesByOrg[m.OrgId][m.UserId] == string(models.ROLE_VIEWER) || (userRolesByOrg[m.OrgId][m.UserId] == string(models.ROLE_EDITOR) && !p.editorsCanAdmin) {
|
|
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.mapPermissionToFGAC(m.Permission, m.TeamId)...)
|
|
userPermissionsByOrg[m.OrgId] = userPermissions
|
|
}
|
|
|
|
return userPermissionsByOrg, nil
|
|
}
|