mirror of https://github.com/grafana/grafana
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>pull/45094/head^2
parent
4caf5dbbd9
commit
4982ca3b1d
@ -0,0 +1,227 @@ |
||||
package guardian |
||||
|
||||
import ( |
||||
"context" |
||||
"strconv" |
||||
|
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/accesscontrol" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
) |
||||
|
||||
var permissionMap = map[string]models.PermissionType{ |
||||
"View": models.PERMISSION_VIEW, |
||||
"Edit": models.PERMISSION_EDIT, |
||||
"Admin": models.PERMISSION_ADMIN, |
||||
} |
||||
|
||||
var _ DashboardGuardian = new(AccessControlDashboardGuardian) |
||||
|
||||
func NewAccessControlDashboardGuardian( |
||||
ctx context.Context, dashboardId int64, user *models.SignedInUser, |
||||
store *sqlstore.SQLStore, ac accesscontrol.AccessControl, permissionsServices accesscontrol.PermissionsServices, |
||||
) *AccessControlDashboardGuardian { |
||||
return &AccessControlDashboardGuardian{ |
||||
ctx: ctx, |
||||
dashboardID: dashboardId, |
||||
user: user, |
||||
store: store, |
||||
ac: ac, |
||||
permissionServices: permissionsServices, |
||||
} |
||||
} |
||||
|
||||
type AccessControlDashboardGuardian struct { |
||||
ctx context.Context |
||||
dashboardID int64 |
||||
dashboard *models.Dashboard |
||||
user *models.SignedInUser |
||||
store *sqlstore.SQLStore |
||||
ac accesscontrol.AccessControl |
||||
permissionServices accesscontrol.PermissionsServices |
||||
} |
||||
|
||||
func (a *AccessControlDashboardGuardian) CanSave() (bool, error) { |
||||
if err := a.loadDashboard(); err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
if a.dashboard.IsFolder { |
||||
return a.ac.Evaluate(a.ctx, a.user, accesscontrol.EvalPermission(accesscontrol.ActionFoldersWrite, folderScope(a.dashboardID))) |
||||
} |
||||
|
||||
return a.ac.Evaluate(a.ctx, a.user, accesscontrol.EvalAny( |
||||
accesscontrol.EvalPermission(accesscontrol.ActionDashboardsWrite, dashboardScope(a.dashboard.Id)), |
||||
accesscontrol.EvalPermission(accesscontrol.ActionDashboardsWrite, folderScope(a.dashboard.FolderId)), |
||||
)) |
||||
} |
||||
|
||||
func (a *AccessControlDashboardGuardian) CanEdit() (bool, error) { |
||||
if err := a.loadDashboard(); err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
if setting.ViewersCanEdit { |
||||
return a.CanView() |
||||
} |
||||
|
||||
if a.dashboard.IsFolder { |
||||
return a.ac.Evaluate(a.ctx, a.user, accesscontrol.EvalPermission(accesscontrol.ActionFoldersWrite, folderScope(a.dashboardID))) |
||||
} |
||||
|
||||
return a.ac.Evaluate(a.ctx, a.user, accesscontrol.EvalAny( |
||||
accesscontrol.EvalPermission(accesscontrol.ActionDashboardsWrite, dashboardScope(a.dashboard.Id)), |
||||
accesscontrol.EvalPermission(accesscontrol.ActionDashboardsWrite, folderScope(a.dashboard.FolderId)), |
||||
)) |
||||
} |
||||
|
||||
func (a *AccessControlDashboardGuardian) CanView() (bool, error) { |
||||
if err := a.loadDashboard(); err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
if a.dashboard.IsFolder { |
||||
return a.ac.Evaluate(a.ctx, a.user, accesscontrol.EvalPermission(accesscontrol.ActionFoldersRead, folderScope(a.dashboardID))) |
||||
} |
||||
|
||||
return a.ac.Evaluate(a.ctx, a.user, accesscontrol.EvalAny( |
||||
accesscontrol.EvalPermission(accesscontrol.ActionDashboardsRead, dashboardScope(a.dashboard.Id)), |
||||
accesscontrol.EvalPermission(accesscontrol.ActionDashboardsRead, folderScope(a.dashboard.FolderId)), |
||||
)) |
||||
} |
||||
|
||||
func (a *AccessControlDashboardGuardian) CanAdmin() (bool, error) { |
||||
if err := a.loadDashboard(); err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
if a.dashboard.IsFolder { |
||||
return a.ac.Evaluate(a.ctx, a.user, accesscontrol.EvalAll( |
||||
accesscontrol.EvalPermission(accesscontrol.ActionFoldersPermissionsRead, folderScope(a.dashboard.Id)), |
||||
accesscontrol.EvalPermission(accesscontrol.ActionFoldersPermissionsWrite, folderScope(a.dashboard.Id)), |
||||
)) |
||||
} |
||||
|
||||
return a.ac.Evaluate(a.ctx, a.user, accesscontrol.EvalAny( |
||||
accesscontrol.EvalAll( |
||||
accesscontrol.EvalPermission(accesscontrol.ActionDashboardsPermissionsRead, dashboardScope(a.dashboard.Id)), |
||||
accesscontrol.EvalPermission(accesscontrol.ActionDashboardsPermissionsWrite, dashboardScope(a.dashboard.Id)), |
||||
), |
||||
accesscontrol.EvalAll( |
||||
accesscontrol.EvalPermission(accesscontrol.ActionDashboardsPermissionsRead, folderScope(a.dashboard.FolderId)), |
||||
accesscontrol.EvalPermission(accesscontrol.ActionDashboardsPermissionsWrite, folderScope(a.dashboard.FolderId)), |
||||
), |
||||
)) |
||||
} |
||||
|
||||
func (a *AccessControlDashboardGuardian) CanDelete() (bool, error) { |
||||
if err := a.loadDashboard(); err != nil { |
||||
return false, err |
||||
} |
||||
|
||||
if a.dashboard.IsFolder { |
||||
return a.ac.Evaluate(a.ctx, a.user, accesscontrol.EvalPermission(accesscontrol.ActionFoldersDelete, folderScope(a.dashboard.Id))) |
||||
} |
||||
|
||||
return a.ac.Evaluate(a.ctx, a.user, accesscontrol.EvalAny( |
||||
accesscontrol.EvalPermission(accesscontrol.ActionDashboardsDelete, dashboardScope(a.dashboard.Id)), |
||||
accesscontrol.EvalPermission(accesscontrol.ActionDashboardsDelete, folderScope(a.dashboard.FolderId)), |
||||
)) |
||||
} |
||||
|
||||
func (a *AccessControlDashboardGuardian) CanCreate(folderID int64, isFolder bool) (bool, error) { |
||||
if isFolder { |
||||
return a.ac.Evaluate(a.ctx, a.user, accesscontrol.EvalPermission(accesscontrol.ActionFoldersCreate)) |
||||
} |
||||
|
||||
return a.ac.Evaluate(a.ctx, a.user, accesscontrol.EvalPermission(accesscontrol.ActionDashboardsCreate, folderScope(folderID))) |
||||
} |
||||
|
||||
func (a *AccessControlDashboardGuardian) CheckPermissionBeforeUpdate(permission models.PermissionType, updatePermissions []*models.DashboardAcl) (bool, error) { |
||||
// always true for access control
|
||||
return true, nil |
||||
} |
||||
|
||||
// GetAcl translate access control permissions to dashboard acl info
|
||||
func (a *AccessControlDashboardGuardian) GetAcl() ([]*models.DashboardAclInfoDTO, error) { |
||||
if err := a.loadDashboard(); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
svc := a.permissionServices.GetDashboardService() |
||||
if a.dashboard.IsFolder { |
||||
svc = a.permissionServices.GetFolderService() |
||||
} |
||||
|
||||
permissions, err := svc.GetPermissions(a.ctx, a.user, strconv.FormatInt(a.dashboard.Id, 10)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
acl := make([]*models.DashboardAclInfoDTO, 0, len(permissions)) |
||||
for _, p := range permissions { |
||||
if !p.IsManaged() { |
||||
continue |
||||
} |
||||
|
||||
var role *models.RoleType |
||||
if p.BuiltInRole != "" { |
||||
tmp := models.RoleType(p.BuiltInRole) |
||||
role = &tmp |
||||
} |
||||
|
||||
acl = append(acl, &models.DashboardAclInfoDTO{ |
||||
OrgId: a.dashboard.OrgId, |
||||
DashboardId: a.dashboard.Id, |
||||
FolderId: a.dashboard.FolderId, |
||||
Created: p.Created, |
||||
Updated: p.Updated, |
||||
UserId: p.UserId, |
||||
UserLogin: p.UserLogin, |
||||
UserEmail: p.UserEmail, |
||||
TeamId: p.TeamId, |
||||
TeamEmail: p.TeamEmail, |
||||
Team: p.Team, |
||||
Role: role, |
||||
Permission: permissionMap[svc.MapActions(p)], |
||||
PermissionName: permissionMap[svc.MapActions(p)].String(), |
||||
Uid: a.dashboard.Uid, |
||||
Title: a.dashboard.Title, |
||||
Slug: a.dashboard.Slug, |
||||
IsFolder: a.dashboard.IsFolder, |
||||
Url: a.dashboard.GetUrl(), |
||||
Inherited: false, |
||||
}) |
||||
} |
||||
|
||||
return acl, nil |
||||
} |
||||
|
||||
func (a *AccessControlDashboardGuardian) GetACLWithoutDuplicates() ([]*models.DashboardAclInfoDTO, error) { |
||||
return a.GetAcl() |
||||
} |
||||
|
||||
func (a *AccessControlDashboardGuardian) GetHiddenACL(cfg *setting.Cfg) ([]*models.DashboardAcl, error) { |
||||
// not used with access control
|
||||
return nil, nil |
||||
} |
||||
|
||||
func (a *AccessControlDashboardGuardian) loadDashboard() error { |
||||
if a.dashboard == nil { |
||||
query := &models.GetDashboardQuery{Id: a.dashboardID, OrgId: a.user.OrgId} |
||||
if err := a.store.GetDashboard(a.ctx, query); err != nil { |
||||
return err |
||||
} |
||||
a.dashboard = query.Result |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func dashboardScope(dashboardID int64) string { |
||||
return accesscontrol.Scope("dashboards", "id", strconv.FormatInt(dashboardID, 10)) |
||||
} |
||||
|
||||
func folderScope(folderID int64) string { |
||||
return accesscontrol.Scope("folders", "id", strconv.FormatInt(folderID, 10)) |
||||
} |
@ -0,0 +1,557 @@ |
||||
package guardian |
||||
|
||||
import ( |
||||
"context" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing" |
||||
"github.com/grafana/grafana/pkg/components/simplejson" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/accesscontrol" |
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/database" |
||||
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" |
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol" |
||||
dashdb "github.com/grafana/grafana/pkg/services/dashboards/database" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
) |
||||
|
||||
type accessControlGuardianTestCase struct { |
||||
desc string |
||||
dashboardID int64 |
||||
permissions []*accesscontrol.Permission |
||||
viewersCanEdit bool |
||||
expected bool |
||||
} |
||||
|
||||
func TestAccessControlDashboardGuardian_CanSave(t *testing.T) { |
||||
tests := []accessControlGuardianTestCase{ |
||||
{ |
||||
desc: "should be able to save with dashboard wildcard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsWrite, |
||||
Scope: "dashboards:*", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to save with folder wildcard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsWrite, |
||||
Scope: "folders:*", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to save with dashboard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsWrite, |
||||
Scope: "dashboards:id:1", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to save with folder scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsWrite, |
||||
Scope: "folders:id:0", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should not be able to save with incorrect dashboard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsWrite, |
||||
Scope: "dashboards:id:10", |
||||
}, |
||||
}, |
||||
expected: false, |
||||
}, |
||||
{ |
||||
desc: "should not be able to save with incorrect folder scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsWrite, |
||||
Scope: "folders:id:10", |
||||
}, |
||||
}, |
||||
expected: false, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.desc, func(t *testing.T) { |
||||
guardian := setupAccessControlGuardianTest(t, tt.dashboardID, tt.permissions) |
||||
|
||||
can, err := guardian.CanSave() |
||||
require.NoError(t, err) |
||||
assert.Equal(t, tt.expected, can) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestAccessControlDashboardGuardian_CanEdit(t *testing.T) { |
||||
tests := []accessControlGuardianTestCase{ |
||||
{ |
||||
desc: "should be able to edit with dashboard wildcard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsWrite, |
||||
Scope: "dashboards:*", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to edit with folder wildcard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsWrite, |
||||
Scope: "folders:*", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to edit with dashboard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsWrite, |
||||
Scope: "dashboards:id:1", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to edit with folder scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsWrite, |
||||
Scope: "folders:id:0", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should not be able to edit with incorrect dashboard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsWrite, |
||||
Scope: "dashboards:id:10", |
||||
}, |
||||
}, |
||||
expected: false, |
||||
}, |
||||
{ |
||||
desc: "should not be able to edit with incorrect folder scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsWrite, |
||||
Scope: "folders:id:10", |
||||
}, |
||||
}, |
||||
expected: false, |
||||
}, |
||||
{ |
||||
desc: "should be able to edit with read action when viewer_can_edit is true", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsRead, |
||||
Scope: "dashboards:id:1", |
||||
}, |
||||
}, |
||||
viewersCanEdit: true, |
||||
expected: true, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.desc, func(t *testing.T) { |
||||
guardian := setupAccessControlGuardianTest(t, tt.dashboardID, tt.permissions) |
||||
|
||||
if tt.viewersCanEdit { |
||||
setting.ViewersCanEdit = true |
||||
defer func() { setting.ViewersCanEdit = false }() |
||||
} |
||||
|
||||
can, err := guardian.CanEdit() |
||||
require.NoError(t, err) |
||||
assert.Equal(t, tt.expected, can) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestAccessControlDashboardGuardian_CanView(t *testing.T) { |
||||
tests := []accessControlGuardianTestCase{ |
||||
{ |
||||
desc: "should be able to view with dashboard wildcard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsRead, |
||||
Scope: "dashboards:*", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to view with folder wildcard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsRead, |
||||
Scope: "folders:*", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to view with dashboard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsRead, |
||||
Scope: "dashboards:id:1", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to view with folder scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsRead, |
||||
Scope: "folders:id:0", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should not be able to view with incorrect dashboard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsRead, |
||||
Scope: "dashboards:id:10", |
||||
}, |
||||
}, |
||||
expected: false, |
||||
}, |
||||
{ |
||||
desc: "should not be able to view with incorrect folder scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsRead, |
||||
Scope: "folders:id:10", |
||||
}, |
||||
}, |
||||
expected: false, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.desc, func(t *testing.T) { |
||||
guardian := setupAccessControlGuardianTest(t, tt.dashboardID, tt.permissions) |
||||
|
||||
can, err := guardian.CanView() |
||||
require.NoError(t, err) |
||||
assert.Equal(t, tt.expected, can) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestAccessControlDashboardGuardian_CanAdmin(t *testing.T) { |
||||
tests := []accessControlGuardianTestCase{ |
||||
{ |
||||
desc: "should be able to admin with dashboard wildcard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsPermissionsRead, |
||||
Scope: "dashboards:*", |
||||
}, |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsPermissionsWrite, |
||||
Scope: "dashboards:*", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to admin with folder wildcard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsPermissionsRead, |
||||
Scope: "folders:*", |
||||
}, |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsPermissionsWrite, |
||||
Scope: "folders:*", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to admin with dashboard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsPermissionsRead, |
||||
Scope: "dashboards:id:1", |
||||
}, |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsPermissionsWrite, |
||||
Scope: "dashboards:id:1", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to admin with folder scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsPermissionsRead, |
||||
Scope: "folders:id:0", |
||||
}, |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsPermissionsWrite, |
||||
Scope: "folders:id:0", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should not be able to admin with incorrect dashboard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsPermissionsRead, |
||||
Scope: "dashboards:id:10", |
||||
}, |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsPermissionsWrite, |
||||
Scope: "dashboards:id:10", |
||||
}, |
||||
}, |
||||
expected: false, |
||||
}, |
||||
{ |
||||
desc: "should not be able to admin with incorrect folder scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsPermissionsRead, |
||||
Scope: "folders:id:10", |
||||
}, |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsPermissionsWrite, |
||||
Scope: "folders:id:10", |
||||
}, |
||||
}, |
||||
expected: false, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.desc, func(t *testing.T) { |
||||
guardian := setupAccessControlGuardianTest(t, tt.dashboardID, tt.permissions) |
||||
|
||||
can, err := guardian.CanAdmin() |
||||
require.NoError(t, err) |
||||
assert.Equal(t, tt.expected, can) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestAccessControlDashboardGuardian_CanDelete(t *testing.T) { |
||||
tests := []accessControlGuardianTestCase{ |
||||
{ |
||||
desc: "should be able to delete with dashboard wildcard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsDelete, |
||||
Scope: "dashboards:*", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to delete with folder wildcard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsDelete, |
||||
Scope: "folders:*", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to delete with dashboard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsDelete, |
||||
Scope: "dashboards:id:1", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to delete with folder scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsDelete, |
||||
Scope: "folders:id:0", |
||||
}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should not be able to delete with incorrect dashboard scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsDelete, |
||||
Scope: "dashboards:id:10", |
||||
}, |
||||
}, |
||||
expected: false, |
||||
}, |
||||
{ |
||||
desc: "should not be able to delete with incorrect folder scope", |
||||
dashboardID: 1, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{ |
||||
Action: accesscontrol.ActionDashboardsDelete, |
||||
Scope: "folders:id:10", |
||||
}, |
||||
}, |
||||
expected: false, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.desc, func(t *testing.T) { |
||||
guardian := setupAccessControlGuardianTest(t, tt.dashboardID, tt.permissions) |
||||
|
||||
can, err := guardian.CanDelete() |
||||
require.NoError(t, err) |
||||
assert.Equal(t, tt.expected, can) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
type accessControlGuardianCanCreateTestCase struct { |
||||
desc string |
||||
isFolder bool |
||||
folderID int64 |
||||
permissions []*accesscontrol.Permission |
||||
expected bool |
||||
} |
||||
|
||||
func TestAccessControlDashboardGuardian_CanCreate(t *testing.T) { |
||||
tests := []accessControlGuardianCanCreateTestCase{ |
||||
{ |
||||
desc: "should be able to create dashboard in folder 0", |
||||
isFolder: false, |
||||
folderID: 0, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{Action: accesscontrol.ActionDashboardsCreate, Scope: "folders:id:0"}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should be able to create dashboard in any folder", |
||||
isFolder: false, |
||||
folderID: 100, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{Action: accesscontrol.ActionDashboardsCreate, Scope: "folders:*"}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should not be able to create dashboard without permissions", |
||||
isFolder: false, |
||||
folderID: 100, |
||||
permissions: []*accesscontrol.Permission{}, |
||||
expected: false, |
||||
}, |
||||
{ |
||||
desc: "should be able to create folder with correct permissions", |
||||
isFolder: true, |
||||
folderID: 0, |
||||
permissions: []*accesscontrol.Permission{ |
||||
{Action: accesscontrol.ActionFoldersCreate}, |
||||
}, |
||||
expected: true, |
||||
}, |
||||
{ |
||||
desc: "should not be able to create folders without permissions", |
||||
isFolder: true, |
||||
folderID: 100, |
||||
permissions: []*accesscontrol.Permission{}, |
||||
expected: false, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.desc, func(t *testing.T) { |
||||
guardian := setupAccessControlGuardianTest(t, 0, tt.permissions) |
||||
|
||||
can, err := guardian.CanCreate(tt.folderID, tt.isFolder) |
||||
require.NoError(t, err) |
||||
assert.Equal(t, tt.expected, can) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func setupAccessControlGuardianTest(t *testing.T, dashID int64, permissions []*accesscontrol.Permission) *AccessControlDashboardGuardian { |
||||
t.Helper() |
||||
store := sqlstore.InitTestDB(t) |
||||
// seed dashboard
|
||||
_, err := dashdb.ProvideDashboardStore(store).SaveDashboard(models.SaveDashboardCommand{ |
||||
Dashboard: &simplejson.Json{}, |
||||
UserId: 1, |
||||
OrgId: 1, |
||||
FolderId: 0, |
||||
}) |
||||
require.NoError(t, err) |
||||
|
||||
ac := accesscontrolmock.New().WithPermissions(permissions) |
||||
services, err := ossaccesscontrol.ProvidePermissionsServices(routing.NewRouteRegister(), store, ac, database.ProvideService(store)) |
||||
require.NoError(t, err) |
||||
|
||||
return NewAccessControlDashboardGuardian(context.Background(), dashID, &models.SignedInUser{OrgId: 1}, store, ac, services) |
||||
} |
@ -0,0 +1,22 @@ |
||||
package guardian |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/accesscontrol" |
||||
"github.com/grafana/grafana/pkg/services/featuremgmt" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
) |
||||
|
||||
type Provider struct{} |
||||
|
||||
func ProvideService(store *sqlstore.SQLStore, ac accesscontrol.AccessControl, permissionsServices accesscontrol.PermissionsServices, features featuremgmt.FeatureToggles) *Provider { |
||||
if features.IsEnabled(featuremgmt.FlagAccesscontrol) { |
||||
// TODO: Fix this hack, see https://github.com/grafana/grafana-enterprise/issues/2935
|
||||
New = func(ctx context.Context, dashId int64, orgId int64, user *models.SignedInUser) DashboardGuardian { |
||||
return NewAccessControlDashboardGuardian(ctx, dashId, user, store, ac, permissionsServices) |
||||
} |
||||
} |
||||
return &Provider{} |
||||
} |
@ -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") |
||||
} |
Loading…
Reference in new issue