Access Control: Add function to set several permissions on a resource in one transaction (#44768)

This commit is contained in:
Karl Persson
2022-02-07 17:04:32 +01:00
committed by GitHub
parent 178193c84b
commit 922b9465ec
10 changed files with 368 additions and 107 deletions
@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions/types"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
@@ -80,7 +81,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
user, team := createUserAndTeam(t, sql, tt.orgID)
for _, id := range tt.userPermissions {
_, err := store.SetUserResourcePermission(context.Background(), tt.orgID, accesscontrol.User{ID: user.Id}, accesscontrol.SetResourcePermissionCommand{
_, err := store.SetUserResourcePermission(context.Background(), tt.orgID, accesscontrol.User{ID: user.Id}, types.SetResourcePermissionCommand{
Actions: []string{"dashboards:write"},
Resource: "dashboards",
ResourceID: id,
@@ -89,7 +90,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
}
for _, id := range tt.teamPermissions {
_, err := store.SetTeamResourcePermission(context.Background(), tt.orgID, team.Id, accesscontrol.SetResourcePermissionCommand{
_, err := store.SetTeamResourcePermission(context.Background(), tt.orgID, team.Id, types.SetResourcePermissionCommand{
Actions: []string{"dashboards:read"},
Resource: "dashboards",
ResourceID: id,
@@ -98,7 +99,7 @@ func TestAccessControlStore_GetUserPermissions(t *testing.T) {
}
for _, id := range tt.builtinPermissions {
_, err := store.SetBuiltInResourcePermission(context.Background(), tt.orgID, "Admin", accesscontrol.SetResourcePermissionCommand{
_, err := store.SetBuiltInResourcePermission(context.Background(), tt.orgID, "Admin", types.SetResourcePermissionCommand{
Actions: []string{"dashboards:read"},
Resource: "dashboards",
ResourceID: id,
@@ -35,7 +35,7 @@ func (p *flatResourcePermission) Managed() bool {
func (s *AccessControlStore) SetUserResourcePermission(
ctx context.Context, orgID int64, user accesscontrol.User,
cmd accesscontrol.SetResourcePermissionCommand,
cmd types.SetResourcePermissionCommand,
hook types.UserResourceHookFunc,
) (*accesscontrol.ResourcePermission, error) {
if user.ID == 0 {
@@ -45,24 +45,34 @@ func (s *AccessControlStore) SetUserResourcePermission(
var err error
var permission *accesscontrol.ResourcePermission
err = s.sql.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
permission, err = s.setResourcePermission(sess, orgID, managedUserRoleName(user.ID), s.userAdder(sess, orgID, user.ID), cmd)
if err == nil && hook != nil {
return hook(sess, orgID, user, cmd.ResourceID, cmd.Permission)
}
permission, err = s.setUserResourcePermission(sess, orgID, user, cmd, hook)
return err
})
return permission, err
}
func (s *AccessControlStore) setUserResourcePermission(
sess *sqlstore.DBSession, orgID int64, user accesscontrol.User,
cmd types.SetResourcePermissionCommand,
hook types.UserResourceHookFunc,
) (*accesscontrol.ResourcePermission, error) {
permission, err := s.setResourcePermission(sess, orgID, managedUserRoleName(user.ID), s.userAdder(sess, orgID, user.ID), cmd)
if err != nil {
return nil, err
}
if hook != nil {
if err := hook(sess, orgID, user, cmd.ResourceID, cmd.Permission); err != nil {
return nil, err
}
}
return permission, nil
}
func (s *AccessControlStore) SetTeamResourcePermission(
ctx context.Context, orgID, teamID int64,
cmd accesscontrol.SetResourcePermissionCommand,
cmd types.SetResourcePermissionCommand,
hook types.TeamResourceHookFunc,
) (*accesscontrol.ResourcePermission, error) {
if teamID == 0 {
@@ -73,24 +83,35 @@ func (s *AccessControlStore) SetTeamResourcePermission(
var permission *accesscontrol.ResourcePermission
err = s.sql.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
permission, err = s.setResourcePermission(sess, orgID, managedTeamRoleName(teamID), s.teamAdder(sess, orgID, teamID), cmd)
if err == nil && hook != nil {
return hook(sess, orgID, teamID, cmd.ResourceID, cmd.Permission)
}
permission, err = s.setTeamResourcePermission(sess, orgID, teamID, cmd, hook)
return err
})
return permission, err
}
func (s *AccessControlStore) setTeamResourcePermission(
sess *sqlstore.DBSession, orgID, teamID int64,
cmd types.SetResourcePermissionCommand,
hook types.TeamResourceHookFunc,
) (*accesscontrol.ResourcePermission, error) {
permission, err := s.setResourcePermission(sess, orgID, managedTeamRoleName(teamID), s.teamAdder(sess, orgID, teamID), cmd)
if err != nil {
return nil, err
}
if hook != nil {
if err := hook(sess, orgID, teamID, cmd.ResourceID, cmd.Permission); err != nil {
return nil, err
}
}
return permission, nil
}
func (s *AccessControlStore) SetBuiltInResourcePermission(
ctx context.Context, orgID int64, builtInRole string,
cmd accesscontrol.SetResourcePermissionCommand,
cmd types.SetResourcePermissionCommand,
hook types.BuiltinResourceHookFunc,
) (*accesscontrol.ResourcePermission, error) {
if !models.RoleType(builtInRole).IsValid() || builtInRole == accesscontrol.RoleGrafanaAdmin {
@@ -101,10 +122,7 @@ func (s *AccessControlStore) SetBuiltInResourcePermission(
var permission *accesscontrol.ResourcePermission
err = s.sql.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
permission, err = s.setResourcePermission(sess, orgID, managedBuiltInRoleName(builtInRole), s.builtInRoleAdder(sess, orgID, builtInRole), cmd)
if err == nil && hook != nil {
return hook(sess, orgID, builtInRole, cmd.ResourceID, cmd.Permission)
}
permission, err = s.setBuiltInResourcePermission(sess, orgID, builtInRole, cmd, hook)
return err
})
@@ -115,10 +133,61 @@ func (s *AccessControlStore) SetBuiltInResourcePermission(
return permission, nil
}
func (s *AccessControlStore) setBuiltInResourcePermission(
sess *sqlstore.DBSession, orgID int64, builtInRole string,
cmd types.SetResourcePermissionCommand,
hook types.BuiltinResourceHookFunc,
) (*accesscontrol.ResourcePermission, error) {
permission, err := s.setResourcePermission(sess, orgID, managedBuiltInRoleName(builtInRole), s.builtInRoleAdder(sess, orgID, builtInRole), cmd)
if err != nil {
return nil, err
}
if hook != nil {
if err := hook(sess, orgID, builtInRole, cmd.ResourceID, cmd.Permission); err != nil {
return nil, err
}
}
return permission, nil
}
func (s *AccessControlStore) SetResourcePermissions(
ctx context.Context, orgID int64,
commands []types.SetResourcePermissionsCommand,
hooks types.ResourceHooks,
) ([]accesscontrol.ResourcePermission, error) {
var err error
var permissions []accesscontrol.ResourcePermission
err = s.sql.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
for _, cmd := range commands {
var p *accesscontrol.ResourcePermission
if cmd.User.ID != 0 {
p, err = s.setUserResourcePermission(sess, orgID, cmd.User, cmd.SetResourcePermissionCommand, hooks.User)
} else if cmd.TeamID != 0 {
p, err = s.setTeamResourcePermission(sess, orgID, cmd.TeamID, cmd.SetResourcePermissionCommand, hooks.Team)
} else if models.RoleType(cmd.BuiltinRole).IsValid() || cmd.BuiltinRole == accesscontrol.RoleGrafanaAdmin {
p, err = s.setBuiltInResourcePermission(sess, orgID, cmd.BuiltinRole, cmd.SetResourcePermissionCommand, hooks.BuiltInRole)
}
if err != nil {
return err
}
if p != nil {
permissions = append(permissions, *p)
}
}
return nil
})
return permissions, err
}
type roleAdder func(roleID int64) error
func (s *AccessControlStore) setResourcePermission(
sess *sqlstore.DBSession, orgID int64, roleName string, adder roleAdder, cmd accesscontrol.SetResourcePermissionCommand,
sess *sqlstore.DBSession, orgID int64, roleName string, adder roleAdder, cmd types.SetResourcePermissionCommand,
) (*accesscontrol.ResourcePermission, error) {
role, err := s.getOrCreateManagedRole(sess, orgID, roleName, adder)
if err != nil {
@@ -159,22 +228,20 @@ func (s *AccessControlStore) setResourcePermission(
return nil, err
}
var permissions []flatResourcePermission
for action := range missing {
p, err := s.createResourcePermission(sess, role.ID, action, cmd.Resource, cmd.ResourceID)
id, err := s.createResourcePermission(sess, role.ID, action, cmd.Resource, cmd.ResourceID)
if err != nil {
return nil, err
}
permissions = append(permissions, *p)
keep = append(keep, id)
}
keptPermissions, err := s.getResourcePermissions(sess, cmd.ResourceID, keep)
permissions, err := s.getResourcePermissions(sess, cmd.ResourceID, keep)
if err != nil {
return nil, err
}
permission := flatPermissionsToResourcePermission(append(permissions, keptPermissions...))
permission := flatPermissionsToResourcePermission(permissions)
if permission == nil {
return &accesscontrol.ResourcePermission{}, nil
}
@@ -182,7 +249,7 @@ func (s *AccessControlStore) setResourcePermission(
return permission, nil
}
func (s *AccessControlStore) GetResourcesPermissions(ctx context.Context, orgID int64, query accesscontrol.GetResourcesPermissionsQuery) ([]accesscontrol.ResourcePermission, error) {
func (s *AccessControlStore) GetResourcesPermissions(ctx context.Context, orgID int64, query types.GetResourcesPermissionsQuery) ([]accesscontrol.ResourcePermission, error) {
var result []accesscontrol.ResourcePermission
err := s.sql.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
@@ -194,45 +261,19 @@ func (s *AccessControlStore) GetResourcesPermissions(ctx context.Context, orgID
return result, err
}
func (s *AccessControlStore) createResourcePermission(sess *sqlstore.DBSession, roleID int64, action, resource string, resourceID string) (*flatResourcePermission, error) {
func (s *AccessControlStore) createResourcePermission(sess *sqlstore.DBSession, roleID int64, action, resource string, resourceID string) (int64, error) {
permission := managedPermission(action, resource, resourceID)
permission.RoleID = roleID
permission.Created = time.Now()
permission.Updated = time.Now()
if _, err := sess.Insert(&permission); err != nil {
return nil, err
return 0, err
}
rawSql := `
SELECT
p.*,
? AS resource_id,
ur.user_id AS user_id,
u.login AS user_login,
u.email AS user_email,
tr.team_id AS team_id,
t.name AS team,
t.email AS team_email,
r.name as role_name
FROM permission p
LEFT JOIN role r ON p.role_id = r.id
LEFT JOIN team_role tr ON r.id = tr.role_id
LEFT JOIN team t ON tr.team_id = t.id
LEFT JOIN user_role ur ON r.id = ur.role_id
LEFT JOIN ` + s.sql.Dialect.Quote("user") + ` u ON ur.user_id = u.id
WHERE p.id = ?
`
p := &flatResourcePermission{}
if _, err := sess.SQL(rawSql, resourceID, permission.ID).Get(p); err != nil {
return nil, err
}
return p, nil
return permission.ID, nil
}
func (s *AccessControlStore) getResourcesPermissions(sess *sqlstore.DBSession, orgID int64, query accesscontrol.GetResourcesPermissionsQuery) ([]accesscontrol.ResourcePermission, error) {
func (s *AccessControlStore) getResourcesPermissions(sess *sqlstore.DBSession, orgID int64, query types.GetResourcesPermissionsQuery) ([]accesscontrol.ResourcePermission, error) {
if len(query.Actions) == 0 {
return nil, nil
}
@@ -544,13 +585,15 @@ func (s *AccessControlStore) getResourcePermissions(sess *sqlstore.DBSession, re
tr.team_id AS team_id,
t.name AS team,
t.email AS team_email,
r.name as role_name
r.name as role_name,
br.role AS built_in_role
FROM permission p
INNER JOIN role r ON p.role_id = r.id
LEFT JOIN team_role tr ON r.id = tr.role_id
LEFT JOIN team t ON tr.team_id = t.id
LEFT JOIN user_role ur ON r.id = ur.role_id
LEFT JOIN ` + s.sql.Dialect.Quote("user") + ` u ON ur.user_id = u.id
LEFT JOIN builtin_role br ON r.id = br.role_id
WHERE p.id IN (?` + strings.Repeat(",?", len(ids)-1) + `)
`
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions/types"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
@@ -54,7 +55,7 @@ func benchmarkDSPermissions(b *testing.B, dsNum, usersNum int) {
func getDSPermissions(b *testing.B, store *AccessControlStore, dataSources []int64) {
dsId := dataSources[0]
permissions, err := store.GetResourcesPermissions(context.Background(), accesscontrol.GlobalOrgID, accesscontrol.GetResourcesPermissionsQuery{
permissions, err := store.GetResourcesPermissions(context.Background(), accesscontrol.GlobalOrgID, types.GetResourcesPermissionsQuery{
Actions: []string{dsAction},
Resource: dsResource,
ResourceIDs: []string{strconv.Itoa(int(dsId))},
@@ -94,7 +95,7 @@ func GenerateDatasourcePermissions(b *testing.B, db *sqlstore.SQLStore, ac *Acce
context.Background(),
accesscontrol.GlobalOrgID,
accesscontrol.User{ID: userIds[i]},
accesscontrol.SetResourcePermissionCommand{
types.SetResourcePermissionCommand{
Actions: []string{dsAction},
Resource: dsResource,
ResourceID: strconv.Itoa(int(dsID)),
@@ -111,7 +112,7 @@ func GenerateDatasourcePermissions(b *testing.B, db *sqlstore.SQLStore, ac *Acce
context.Background(),
accesscontrol.GlobalOrgID,
teamIds[i],
accesscontrol.SetResourcePermissionCommand{
types.SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: strconv.Itoa(int(dsID)),
@@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions/types"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
@@ -21,7 +22,7 @@ type setUserResourcePermissionTest struct {
actions []string
resource string
resourceID string
seeds []accesscontrol.SetResourcePermissionCommand
seeds []types.SetResourcePermissionCommand
}
func TestAccessControlStore_SetUserResourcePermission(t *testing.T) {
@@ -40,7 +41,7 @@ func TestAccessControlStore_SetUserResourcePermission(t *testing.T) {
actions: []string{},
resource: "datasources",
resourceID: "1",
seeds: []accesscontrol.SetResourcePermissionCommand{
seeds: []types.SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
@@ -55,7 +56,7 @@ func TestAccessControlStore_SetUserResourcePermission(t *testing.T) {
actions: []string{"datasources:query", "datasources:write"},
resource: "datasources",
resourceID: "1",
seeds: []accesscontrol.SetResourcePermissionCommand{
seeds: []types.SetResourcePermissionCommand{
{
Actions: []string{"datasources:write"},
Resource: "datasources",
@@ -74,7 +75,7 @@ func TestAccessControlStore_SetUserResourcePermission(t *testing.T) {
require.NoError(t, err)
}
added, err := store.SetUserResourcePermission(context.Background(), test.userID, accesscontrol.User{ID: test.userID}, accesscontrol.SetResourcePermissionCommand{
added, err := store.SetUserResourcePermission(context.Background(), test.userID, accesscontrol.User{ID: test.userID}, types.SetResourcePermissionCommand{
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
@@ -98,7 +99,7 @@ type setTeamResourcePermissionTest struct {
actions []string
resource string
resourceID string
seeds []accesscontrol.SetResourcePermissionCommand
seeds []types.SetResourcePermissionCommand
}
func TestAccessControlStore_SetTeamResourcePermission(t *testing.T) {
@@ -118,7 +119,7 @@ func TestAccessControlStore_SetTeamResourcePermission(t *testing.T) {
actions: []string{"datasources:query", "datasources:write"},
resource: "datasources",
resourceID: "1",
seeds: []accesscontrol.SetResourcePermissionCommand{
seeds: []types.SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
@@ -133,7 +134,7 @@ func TestAccessControlStore_SetTeamResourcePermission(t *testing.T) {
actions: []string{},
resource: "datasources",
resourceID: "1",
seeds: []accesscontrol.SetResourcePermissionCommand{
seeds: []types.SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
@@ -152,7 +153,7 @@ func TestAccessControlStore_SetTeamResourcePermission(t *testing.T) {
require.NoError(t, err)
}
added, err := store.SetTeamResourcePermission(context.Background(), test.orgID, test.teamID, accesscontrol.SetResourcePermissionCommand{
added, err := store.SetTeamResourcePermission(context.Background(), test.orgID, test.teamID, types.SetResourcePermissionCommand{
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
@@ -176,7 +177,7 @@ type setBuiltInResourcePermissionTest struct {
actions []string
resource string
resourceID string
seeds []accesscontrol.SetResourcePermissionCommand
seeds []types.SetResourcePermissionCommand
}
func TestAccessControlStore_SetBuiltInResourcePermission(t *testing.T) {
@@ -196,7 +197,7 @@ func TestAccessControlStore_SetBuiltInResourcePermission(t *testing.T) {
actions: []string{"datasources:query", "datasources:write"},
resource: "datasources",
resourceID: "1",
seeds: []accesscontrol.SetResourcePermissionCommand{
seeds: []types.SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
@@ -211,7 +212,7 @@ func TestAccessControlStore_SetBuiltInResourcePermission(t *testing.T) {
actions: []string{},
resource: "datasources",
resourceID: "1",
seeds: []accesscontrol.SetResourcePermissionCommand{
seeds: []types.SetResourcePermissionCommand{
{
Actions: []string{"datasources:query"},
Resource: "datasources",
@@ -230,7 +231,7 @@ func TestAccessControlStore_SetBuiltInResourcePermission(t *testing.T) {
require.NoError(t, err)
}
added, err := store.SetBuiltInResourcePermission(context.Background(), test.orgID, test.builtInRole, accesscontrol.SetResourcePermissionCommand{
added, err := store.SetBuiltInResourcePermission(context.Background(), test.orgID, test.builtInRole, types.SetResourcePermissionCommand{
Actions: test.actions,
Resource: test.resource,
ResourceID: test.resourceID,
@@ -247,6 +248,69 @@ func TestAccessControlStore_SetBuiltInResourcePermission(t *testing.T) {
}
}
type setResourcePermissionsTest struct {
desc string
orgID int64
commands []types.SetResourcePermissionsCommand
}
func TestAccessControlStore_SetResourcePermissions(t *testing.T) {
tests := []setResourcePermissionsTest{
{
desc: "should set all permissions provided",
orgID: 1,
commands: []types.SetResourcePermissionsCommand{
{
User: accesscontrol.User{ID: 1},
SetResourcePermissionCommand: types.SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
},
},
{
TeamID: 3,
SetResourcePermissionCommand: types.SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
},
},
{
BuiltinRole: "Admin",
SetResourcePermissionCommand: types.SetResourcePermissionCommand{
Actions: []string{"datasources:query"},
Resource: "datasources",
ResourceID: "1",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.desc, func(t *testing.T) {
store, _ := setupTestEnv(t)
permissions, err := store.SetResourcePermissions(context.Background(), tt.orgID, tt.commands, types.ResourceHooks{})
require.NoError(t, err)
require.Len(t, permissions, len(tt.commands))
for i, c := range tt.commands {
if len(c.Actions) == 0 {
assert.Equal(t, accesscontrol.ResourcePermission{}, permissions[i])
} else {
assert.Len(t, permissions[i].Actions, len(c.Actions))
assert.Equal(t, c.TeamID, permissions[i].TeamId)
assert.Equal(t, c.User.ID, permissions[i].UserId)
assert.Equal(t, c.BuiltinRole, permissions[i].BuiltInRole)
assert.Equal(t, accesscontrol.GetResourceScope(c.Resource, c.ResourceID), permissions[i].Scope)
}
}
})
}
}
type getResourcesPermissionsTest struct {
desc string
numUsers int
@@ -318,7 +382,7 @@ func TestAccessControlStore_GetResourcesPermissions(t *testing.T) {
seedResourcePermissions(t, store, sql, test.actions, test.resource, id, test.numUsers)
}
permissions, err := store.GetResourcesPermissions(context.Background(), 1, accesscontrol.GetResourcesPermissionsQuery{
permissions, err := store.GetResourcesPermissions(context.Background(), 1, types.GetResourcesPermissionsQuery{
Actions: test.actions,
Resource: test.resource,
ResourceIDs: test.resourceIDs,
@@ -352,7 +416,7 @@ func seedResourcePermissions(t *testing.T, store *AccessControlStore, sql *sqlst
})
require.NoError(t, err)
_, err = store.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: u.Id}, accesscontrol.SetResourcePermissionCommand{
_, err = store.SetUserResourcePermission(context.Background(), 1, accesscontrol.User{ID: u.Id}, types.SetResourcePermissionCommand{
Actions: actions,
Resource: resource,
ResourceID: resourceID,