From 922b9465ecab95a78da2a87ba4dd916b913fae00 Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Mon, 7 Feb 2022 17:04:32 +0100 Subject: [PATCH] Access Control: Add function to set several permissions on a resource in one transaction (#44768) --- pkg/services/accesscontrol/accesscontrol.go | 2 + .../accesscontrol/database/database_test.go | 7 +- .../database/resource_permissions.go | 155 +++++++++++------- .../resource_permissions_bench_test.go | 7 +- .../database/resource_permissions_test.go | 92 +++++++++-- pkg/services/accesscontrol/models.go | 15 +- .../resourcepermissions/service.go | 95 ++++++++--- .../resourcepermissions/service_test.go | 71 ++++++++ .../resourcepermissions/types/hook.go | 6 + .../resourcepermissions/types/models.go | 25 +++ 10 files changed, 368 insertions(+), 107 deletions(-) create mode 100644 pkg/services/accesscontrol/resourcepermissions/types/models.go diff --git a/pkg/services/accesscontrol/accesscontrol.go b/pkg/services/accesscontrol/accesscontrol.go index 3bf514d6371..2d29e8347cb 100644 --- a/pkg/services/accesscontrol/accesscontrol.go +++ b/pkg/services/accesscontrol/accesscontrol.go @@ -42,6 +42,8 @@ type ResourcePermissionsService interface { SetTeamPermission(ctx context.Context, orgID, teamID int64, resourceID, permission string) (*ResourcePermission, error) // SetBuiltInRolePermission sets permission on resource for a built-in role (Admin, Editor, Viewer) SetBuiltInRolePermission(ctx context.Context, orgID int64, builtInRole string, resourceID string, permission string) (*ResourcePermission, error) + // SetPermissions sets several permissions on resource for either built-in role, team or user + SetPermissions(ctx context.Context, orgID int64, resourceID string, commands ...SetResourcePermissionCommand) ([]ResourcePermission, error) } type User struct { diff --git a/pkg/services/accesscontrol/database/database_test.go b/pkg/services/accesscontrol/database/database_test.go index 441b0deea29..ba0bd22f041 100644 --- a/pkg/services/accesscontrol/database/database_test.go +++ b/pkg/services/accesscontrol/database/database_test.go @@ -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, diff --git a/pkg/services/accesscontrol/database/resource_permissions.go b/pkg/services/accesscontrol/database/resource_permissions.go index 651fad1ee12..68c266db351 100644 --- a/pkg/services/accesscontrol/database/resource_permissions.go +++ b/pkg/services/accesscontrol/database/resource_permissions.go @@ -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) + `) ` diff --git a/pkg/services/accesscontrol/database/resource_permissions_bench_test.go b/pkg/services/accesscontrol/database/resource_permissions_bench_test.go index 27047433870..b82520533ec 100644 --- a/pkg/services/accesscontrol/database/resource_permissions_bench_test.go +++ b/pkg/services/accesscontrol/database/resource_permissions_bench_test.go @@ -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)), diff --git a/pkg/services/accesscontrol/database/resource_permissions_test.go b/pkg/services/accesscontrol/database/resource_permissions_test.go index 00d6a5bfae9..aa2ac0e35be 100644 --- a/pkg/services/accesscontrol/database/resource_permissions_test.go +++ b/pkg/services/accesscontrol/database/resource_permissions_test.go @@ -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, diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index a2925aefda5..e3e71970519 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -235,17 +235,10 @@ func (p *ResourcePermission) Contains(targetActions []string) bool { } type SetResourcePermissionCommand struct { - Actions []string - Resource string - ResourceID string - Permission string -} - -type GetResourcesPermissionsQuery struct { - Actions []string - Resource string - ResourceIDs []string - OnlyManaged bool + UserID int64 + TeamID int64 + BuiltinRole string + Permission string } type SQLFilter struct { diff --git a/pkg/services/accesscontrol/resourcepermissions/service.go b/pkg/services/accesscontrol/resourcepermissions/service.go index ca5a587baa9..191799af0c5 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service.go +++ b/pkg/services/accesscontrol/resourcepermissions/service.go @@ -18,26 +18,32 @@ type Store interface { SetUserResourcePermission( ctx context.Context, orgID int64, user accesscontrol.User, - cmd accesscontrol.SetResourcePermissionCommand, + cmd types.SetResourcePermissionCommand, hook types.UserResourceHookFunc, ) (*accesscontrol.ResourcePermission, error) // SetTeamResourcePermission sets permission for managed team role on a resource SetTeamResourcePermission( ctx context.Context, orgID, teamID int64, - cmd accesscontrol.SetResourcePermissionCommand, + cmd types.SetResourcePermissionCommand, hook types.TeamResourceHookFunc, ) (*accesscontrol.ResourcePermission, error) // SetBuiltInResourcePermission sets permissions for managed builtin role on a resource SetBuiltInResourcePermission( ctx context.Context, orgID int64, builtinRole string, - cmd accesscontrol.SetResourcePermissionCommand, + cmd types.SetResourcePermissionCommand, hook types.BuiltinResourceHookFunc, ) (*accesscontrol.ResourcePermission, error) + SetResourcePermissions( + ctx context.Context, orgID int64, + commands []types.SetResourcePermissionsCommand, + hooks types.ResourceHooks, + ) ([]accesscontrol.ResourcePermission, error) + // GetResourcesPermissions will return all permission for all supplied resource ids - GetResourcesPermissions(ctx context.Context, orgID int64, query accesscontrol.GetResourcesPermissionsQuery) ([]accesscontrol.ResourcePermission, error) + GetResourcesPermissions(ctx context.Context, orgID int64, query types.GetResourcesPermissionsQuery) ([]accesscontrol.ResourcePermission, error) } func New(options Options, router routing.RouteRegister, ac accesscontrol.AccessControl, store Store, sqlStore *sqlstore.SQLStore) (*Service, error) { @@ -93,7 +99,7 @@ type Service struct { } func (s *Service) GetPermissions(ctx context.Context, orgID int64, resourceID string) ([]accesscontrol.ResourcePermission, error) { - return s.store.GetResourcesPermissions(ctx, orgID, accesscontrol.GetResourcesPermissionsQuery{ + return s.store.GetResourcesPermissions(ctx, orgID, types.GetResourcesPermissionsQuery{ Actions: s.actions, Resource: s.options.Resource, ResourceIDs: []string{resourceID}, @@ -102,10 +108,6 @@ func (s *Service) GetPermissions(ctx context.Context, orgID int64, resourceID st } func (s *Service) SetUserPermission(ctx context.Context, orgID int64, user accesscontrol.User, resourceID, permission string) (*accesscontrol.ResourcePermission, error) { - if !s.options.Assignments.Users { - return nil, ErrInvalidAssignment - } - actions, err := s.mapPermission(permission) if err != nil { return nil, err @@ -119,7 +121,7 @@ func (s *Service) SetUserPermission(ctx context.Context, orgID int64, user acces return nil, err } - return s.store.SetUserResourcePermission(ctx, orgID, user, accesscontrol.SetResourcePermissionCommand{ + return s.store.SetUserResourcePermission(ctx, orgID, user, types.SetResourcePermissionCommand{ Actions: actions, Permission: permission, ResourceID: resourceID, @@ -128,10 +130,6 @@ func (s *Service) SetUserPermission(ctx context.Context, orgID int64, user acces } func (s *Service) SetTeamPermission(ctx context.Context, orgID, teamID int64, resourceID, permission string) (*accesscontrol.ResourcePermission, error) { - if !s.options.Assignments.Teams { - return nil, ErrInvalidAssignment - } - actions, err := s.mapPermission(permission) if err != nil { return nil, err @@ -145,7 +143,7 @@ func (s *Service) SetTeamPermission(ctx context.Context, orgID, teamID int64, re return nil, err } - return s.store.SetTeamResourcePermission(ctx, orgID, teamID, accesscontrol.SetResourcePermissionCommand{ + return s.store.SetTeamResourcePermission(ctx, orgID, teamID, types.SetResourcePermissionCommand{ Actions: actions, Permission: permission, ResourceID: resourceID, @@ -154,10 +152,6 @@ func (s *Service) SetTeamPermission(ctx context.Context, orgID, teamID int64, re } func (s *Service) SetBuiltInRolePermission(ctx context.Context, orgID int64, builtInRole, resourceID, permission string) (*accesscontrol.ResourcePermission, error) { - if !s.options.Assignments.BuiltInRoles { - return nil, ErrInvalidAssignment - } - actions, err := s.mapPermission(permission) if err != nil { return nil, err @@ -171,7 +165,7 @@ func (s *Service) SetBuiltInRolePermission(ctx context.Context, orgID int64, bui return nil, err } - return s.store.SetBuiltInResourcePermission(ctx, orgID, builtInRole, accesscontrol.SetResourcePermissionCommand{ + return s.store.SetBuiltInResourcePermission(ctx, orgID, builtInRole, types.SetResourcePermissionCommand{ Actions: actions, Permission: permission, ResourceID: resourceID, @@ -179,6 +173,55 @@ func (s *Service) SetBuiltInRolePermission(ctx context.Context, orgID int64, bui }, s.options.OnSetBuiltInRole) } +func (s *Service) SetPermissions( + ctx context.Context, orgID int64, resourceID string, + commands ...accesscontrol.SetResourcePermissionCommand, +) ([]accesscontrol.ResourcePermission, error) { + if err := s.validateResource(ctx, orgID, resourceID); err != nil { + return nil, err + } + + dbCommands := make([]types.SetResourcePermissionsCommand, 0, len(commands)) + for _, cmd := range commands { + if cmd.UserID != 0 { + if err := s.validateUser(ctx, orgID, cmd.UserID); err != nil { + return nil, err + } + } else if cmd.TeamID != 0 { + if err := s.validateTeam(ctx, orgID, cmd.TeamID); err != nil { + return nil, err + } + } else { + if err := s.validateBuiltinRole(ctx, cmd.BuiltinRole); err != nil { + return nil, err + } + } + + actions, err := s.mapPermission(cmd.Permission) + if err != nil { + return nil, err + } + + dbCommands = append(dbCommands, types.SetResourcePermissionsCommand{ + User: accesscontrol.User{ID: cmd.UserID}, + TeamID: cmd.TeamID, + BuiltinRole: cmd.BuiltinRole, + SetResourcePermissionCommand: types.SetResourcePermissionCommand{ + Actions: actions, + Resource: s.options.Resource, + ResourceID: resourceID, + Permission: cmd.Permission, + }, + }) + } + + return s.store.SetResourcePermissions(ctx, orgID, dbCommands, types.ResourceHooks{ + User: s.options.OnSetUser, + Team: s.options.OnSetTeam, + BuiltInRole: s.options.OnSetBuiltInRole, + }) +} + func (s *Service) mapActions(permission accesscontrol.ResourcePermission) string { for _, p := range s.permissions { if permission.Contains(s.options.PermissionsToActions[p]) { @@ -209,6 +252,10 @@ func (s *Service) validateResource(ctx context.Context, orgID int64, resourceID } func (s *Service) validateUser(ctx context.Context, orgID, userID int64) error { + if !s.options.Assignments.Users { + return ErrInvalidAssignment + } + if err := s.sqlStore.GetSignedInUser(ctx, &models.GetSignedInUserQuery{OrgId: orgID, UserId: userID}); err != nil { return err } @@ -216,6 +263,10 @@ func (s *Service) validateUser(ctx context.Context, orgID, userID int64) error { } func (s *Service) validateTeam(ctx context.Context, orgID, teamID int64) error { + if !s.options.Assignments.Teams { + return ErrInvalidAssignment + } + if err := s.sqlStore.GetTeamById(ctx, &models.GetTeamByIdQuery{OrgId: orgID, Id: teamID}); err != nil { return err } @@ -223,6 +274,10 @@ func (s *Service) validateTeam(ctx context.Context, orgID, teamID int64) error { } func (s *Service) validateBuiltinRole(ctx context.Context, builtinRole string) error { + if !s.options.Assignments.BuiltInRoles { + return ErrInvalidAssignment + } + if err := accesscontrol.ValidateBuiltInRoles([]string{builtinRole}); err != nil { return err } diff --git a/pkg/services/accesscontrol/resourcepermissions/service_test.go b/pkg/services/accesscontrol/resourcepermissions/service_test.go index cec11542dd4..5b9cd4957e7 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/service_test.go @@ -143,6 +143,77 @@ func TestService_SetBuiltInRolePermission(t *testing.T) { } } +type setPermissionsTest struct { + desc string + options Options + commands []accesscontrol.SetResourcePermissionCommand + expectErr bool +} + +func TestService_SetPermissions(t *testing.T) { + tests := []setPermissionsTest{ + { + desc: "should set all permissions", + options: Options{ + Resource: "dashboards", + Assignments: Assignments{ + Users: true, + Teams: true, + BuiltInRoles: true, + }, + PermissionsToActions: map[string][]string{ + "View": {"dashboards:read"}, + }, + }, + commands: []accesscontrol.SetResourcePermissionCommand{ + {UserID: 1, Permission: "View"}, + {TeamID: 1, Permission: "View"}, + {BuiltinRole: "Editor", Permission: "View"}, + }, + }, + { + desc: "should return error for invalid permission", + options: Options{ + Resource: "dashboards", + Assignments: Assignments{ + Users: true, + Teams: true, + BuiltInRoles: true, + }, + PermissionsToActions: map[string][]string{ + "View": {"dashboards:read"}, + }, + }, + commands: []accesscontrol.SetResourcePermissionCommand{ + {UserID: 1, Permission: "View"}, + {TeamID: 1, Permission: "View"}, + {BuiltinRole: "Editor", Permission: "Not real permission"}, + }, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + service, sql := setupTestEnvironment(t, []*accesscontrol.Permission{}, tt.options) + + // seed user + _, err := sql.CreateUser(context.Background(), models.CreateUserCommand{Login: "user", OrgId: 1}) + require.NoError(t, err) + _, err = sql.CreateTeam("team", "", 1) + require.NoError(t, err) + + permissions, err := service.SetPermissions(context.Background(), 1, "1", tt.commands...) + if tt.expectErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Len(t, permissions, len(tt.commands)) + } + }) + } +} + func setupTestEnvironment(t *testing.T, permissions []*accesscontrol.Permission, ops Options) (*Service, *sqlstore.SQLStore) { t.Helper() diff --git a/pkg/services/accesscontrol/resourcepermissions/types/hook.go b/pkg/services/accesscontrol/resourcepermissions/types/hook.go index b389e8d101c..47e0d9d2590 100644 --- a/pkg/services/accesscontrol/resourcepermissions/types/hook.go +++ b/pkg/services/accesscontrol/resourcepermissions/types/hook.go @@ -5,6 +5,12 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore" ) +type ResourceHooks struct { + User UserResourceHookFunc + Team TeamResourceHookFunc + BuiltInRole BuiltinResourceHookFunc +} + type UserResourceHookFunc func(session *sqlstore.DBSession, orgID int64, user accesscontrol.User, resourceID, permission string) error type TeamResourceHookFunc func(session *sqlstore.DBSession, orgID, teamID int64, resourceID, permission string) error type BuiltinResourceHookFunc func(session *sqlstore.DBSession, orgID int64, builtInRole, resourceID, permission string) error diff --git a/pkg/services/accesscontrol/resourcepermissions/types/models.go b/pkg/services/accesscontrol/resourcepermissions/types/models.go new file mode 100644 index 00000000000..36f4072dde0 --- /dev/null +++ b/pkg/services/accesscontrol/resourcepermissions/types/models.go @@ -0,0 +1,25 @@ +package types + +import "github.com/grafana/grafana/pkg/services/accesscontrol" + +type SetResourcePermissionCommand struct { + Actions []string + Resource string + ResourceID string + Permission string +} + +type SetResourcePermissionsCommand struct { + User accesscontrol.User + TeamID int64 + BuiltinRole string + + SetResourcePermissionCommand +} + +type GetResourcesPermissionsQuery struct { + Actions []string + Resource string + ResourceIDs []string + OnlyManaged bool +}