diff --git a/pkg/services/accesscontrol/accesscontrol.go b/pkg/services/accesscontrol/accesscontrol.go index 754066ae707..04f7766a590 100644 --- a/pkg/services/accesscontrol/accesscontrol.go +++ b/pkg/services/accesscontrol/accesscontrol.go @@ -96,6 +96,8 @@ type PermissionsService interface { SetPermissions(ctx context.Context, orgID int64, resourceID string, commands ...SetResourcePermissionCommand) ([]ResourcePermission, error) // MapActions will map actions for a ResourcePermissions to it's "friendly" name configured in PermissionsToActions map. MapActions(permission ResourcePermission) string + // DeleteResourcePermissions removes all permissions for a resource + DeleteResourcePermissions(ctx context.Context, orgID int64, resourceID string) error } type User struct { diff --git a/pkg/services/accesscontrol/actest/fake.go b/pkg/services/accesscontrol/actest/fake.go index e0e5dc44e89..85a039b1bd8 100644 --- a/pkg/services/accesscontrol/actest/fake.go +++ b/pkg/services/accesscontrol/actest/fake.go @@ -140,6 +140,10 @@ func (f *FakePermissionsService) SetPermissions(ctx context.Context, orgID int64 return f.ExpectedPermissions, f.ExpectedErr } +func (f *FakePermissionsService) DeleteResourcePermissions(ctx context.Context, orgID int64, resourceID string) error { + return f.ExpectedErr +} + func (f *FakePermissionsService) MapActions(permission accesscontrol.ResourcePermission) string { return f.ExpectedMappedAction } diff --git a/pkg/services/accesscontrol/mock/service_mock.go b/pkg/services/accesscontrol/mock/service_mock.go index e6550f23655..0701bc1218c 100644 --- a/pkg/services/accesscontrol/mock/service_mock.go +++ b/pkg/services/accesscontrol/mock/service_mock.go @@ -44,6 +44,11 @@ func (m *MockPermissionsService) SetPermissions(ctx context.Context, orgID int64 return mockedArgs.Get(0).([]accesscontrol.ResourcePermission), mockedArgs.Error(1) } +func (m *MockPermissionsService) DeleteResourcePermissions(ctx context.Context, orgID int64, resourceID string) error { + mockedArgs := m.Called(ctx, orgID, resourceID) + return mockedArgs.Error(1) +} + func (m *MockPermissionsService) MapActions(permission accesscontrol.ResourcePermission) string { mockedArgs := m.Called(permission) return mockedArgs.Get(0).(string) diff --git a/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go b/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go index 9aa79fe51fb..79b075b1366 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/permissions_services.go @@ -273,6 +273,11 @@ func (e DatasourcePermissionsService) SetPermissions(ctx context.Context, orgID return nil, nil } +func (e DatasourcePermissionsService) DeleteResourcePermissions(ctx context.Context, orgID int64, resourceID string) error { + // TODO: implement + return nil +} + func (e DatasourcePermissionsService) MapActions(permission accesscontrol.ResourcePermission) string { return "" } diff --git a/pkg/services/accesscontrol/resourcepermissions/service.go b/pkg/services/accesscontrol/resourcepermissions/service.go index af56564f4a1..4dd6503ddc7 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service.go +++ b/pkg/services/accesscontrol/resourcepermissions/service.go @@ -46,6 +46,9 @@ type Store interface { // GetResourcePermissions will return all permission for supplied resource id GetResourcePermissions(ctx context.Context, orgID int64, query GetResourcePermissionsQuery) ([]accesscontrol.ResourcePermission, error) + + // DeleteResourcePermissions will delete all permissions for supplied resource id + DeleteResourcePermissions(ctx context.Context, orgID int64, cmd *DeleteResourcePermissionsCmd) error } func New( @@ -264,6 +267,14 @@ func (s *Service) MapActions(permission accesscontrol.ResourcePermission) string return "" } +func (s *Service) DeleteResourcePermissions(ctx context.Context, orgID int64, resourceID string) error { + return s.store.DeleteResourcePermissions(ctx, orgID, &DeleteResourcePermissionsCmd{ + Resource: s.options.Resource, + ResourceAttribute: s.options.ResourceAttribute, + ResourceID: resourceID, + }) +} + func (s *Service) mapPermission(permission string) ([]string, error) { if permission == "" { return []string{}, nil diff --git a/pkg/services/accesscontrol/resourcepermissions/store.go b/pkg/services/accesscontrol/resourcepermissions/store.go index 3c619fab5dc..7f7784c5f02 100644 --- a/pkg/services/accesscontrol/resourcepermissions/store.go +++ b/pkg/services/accesscontrol/resourcepermissions/store.go @@ -48,6 +48,33 @@ func (p *flatResourcePermission) IsInherited(scope string) bool { return strings.HasPrefix(p.RoleName, accesscontrol.ManagedRolePrefix) && p.Scope != scope } +type DeleteResourcePermissionsCmd struct { + Resource string + ResourceAttribute string + ResourceID string +} + +func (s *store) DeleteResourcePermissions(ctx context.Context, orgID int64, cmd *DeleteResourcePermissionsCmd) error { + scope := accesscontrol.Scope(cmd.Resource, cmd.ResourceAttribute, cmd.ResourceID) + + err := s.sql.WithTransactionalDbSession(ctx, func(sess *db.Session) error { + var permissionIDs []int64 + err := sess.SQL( + "SELECT permission.id FROM permission INNER JOIN role ON permission.role_id = role.id WHERE permission.scope = ? AND role.org_id = ?", + scope, orgID).Find(&permissionIDs) + if err != nil { + return err + } + + if err := deletePermissions(sess, permissionIDs); err != nil { + return err + } + return err + }) + + return err +} + func (s *store) SetUserResourcePermission( ctx context.Context, orgID int64, usr accesscontrol.User, cmd SetResourcePermissionCommand, diff --git a/pkg/services/accesscontrol/resourcepermissions/store_test.go b/pkg/services/accesscontrol/resourcepermissions/store_test.go index 1a89f5f29a0..d0447303d0b 100644 --- a/pkg/services/accesscontrol/resourcepermissions/store_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/store_test.go @@ -577,3 +577,140 @@ func TestStore_IsInherited(t *testing.T) { }) } } + +type orgPermission struct { + OrgID int64 `xorm:"org_id"` + Action string `json:"action"` + Scope string `json:"scope"` +} + +func TestIntegrationStore_DeleteResourcePermissions(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + type deleteResourcePermissionsTest struct { + desc string + orgID int64 + resourceAttribute string + command DeleteResourcePermissionsCmd + shouldExist []orgPermission + shouldNotExist []orgPermission + } + + tests := []deleteResourcePermissionsTest{ + { + desc: "should delete all permissions for resource id in org 1", + orgID: 1, + resourceAttribute: "uid", + command: DeleteResourcePermissionsCmd{ + Resource: "datasources", + ResourceID: "1", + ResourceAttribute: "uid", + }, + shouldExist: []orgPermission{ + { + OrgID: 2, + Action: "datasources:query", + Scope: "datasources:uid:1", + }, { + OrgID: 2, + Action: "datasources:write", + Scope: "datasources:uid:1", + }, + { + OrgID: 1, + Action: "datasources:query", + Scope: "datasources:uid:2", + }, { + OrgID: 1, + Action: "datasources:write", + Scope: "datasources:uid:2", + }}, + shouldNotExist: []orgPermission{ + { + OrgID: 1, + Action: "datasources:query", + Scope: "datasources:uid:1", + }, { + OrgID: 1, + Action: "datasources:write", + Scope: "datasources:uid:1", + }}, + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + store, _ := setupTestEnv(t) + + _, err := store.SetResourcePermissions(context.Background(), 1, []SetResourcePermissionsCommand{ + { + User: accesscontrol.User{ID: 1}, + SetResourcePermissionCommand: SetResourcePermissionCommand{ + Actions: []string{"datasources:query", "datasources:write"}, + Resource: "datasources", + ResourceID: "1", + ResourceAttribute: "uid", + }, + }, + }, ResourceHooks{}) + require.NoError(t, err) + + _, err = store.SetResourcePermissions(context.Background(), 1, []SetResourcePermissionsCommand{ + { + User: accesscontrol.User{ID: 1}, + SetResourcePermissionCommand: SetResourcePermissionCommand{ + Actions: []string{"datasources:query", "datasources:write"}, + Resource: "datasources", + ResourceID: "2", + ResourceAttribute: "uid", + }, + }, + }, ResourceHooks{}) + require.NoError(t, err) + + _, err = store.SetResourcePermissions(context.Background(), 2, []SetResourcePermissionsCommand{ + { + User: accesscontrol.User{ID: 1}, + SetResourcePermissionCommand: SetResourcePermissionCommand{ + Actions: []string{"datasources:query", "datasources:write"}, + Resource: "datasources", + ResourceID: "1", + ResourceAttribute: "uid", + }, + }, + }, ResourceHooks{}) + require.NoError(t, err) + + err = store.DeleteResourcePermissions(context.Background(), tt.orgID, &tt.command) + require.NoError(t, err) + + permissions := retrievePermissionsHelper(store, t) + + for _, p := range tt.shouldExist { + assert.Contains(t, permissions, p) + } + + for _, p := range tt.shouldNotExist { + assert.NotContains(t, permissions, p) + } + }) + } +} + +func retrievePermissionsHelper(store *store, t *testing.T) []orgPermission { + permissions := []orgPermission{} + err := store.sql.WithDbSession(context.Background(), func(sess *db.Session) error { + err := sess.SQL(` + SELECT permission.*, role.org_id + FROM permission + INNER JOIN role ON permission.role_id = role.id +`).Find(&permissions) + require.NoError(t, err) + return nil + }) + + require.NoError(t, err) + return permissions +}