diff --git a/pkg/registry/apis/iam/legacy/delete_team_member_query.sql b/pkg/registry/apis/iam/legacy/delete_team_member_query.sql new file mode 100644 index 00000000000..9208243ba13 --- /dev/null +++ b/pkg/registry/apis/iam/legacy/delete_team_member_query.sql @@ -0,0 +1,2 @@ +DELETE FROM {{ .Ident .TeamMemberTable }} +WHERE uid = {{ .Arg .Command.UID }} diff --git a/pkg/registry/apis/iam/legacy/sql.go b/pkg/registry/apis/iam/legacy/sql.go index df6f9911645..4f913d6d459 100644 --- a/pkg/registry/apis/iam/legacy/sql.go +++ b/pkg/registry/apis/iam/legacy/sql.go @@ -38,6 +38,7 @@ type LegacyIdentityStore interface { ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error) ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error) UpdateTeamMember(ctx context.Context, ns claims.NamespaceInfo, cmd UpdateTeamMemberCommand) (*UpdateTeamMemberResult, error) + DeleteTeamMember(ctx context.Context, ns claims.NamespaceInfo, cmd DeleteTeamMemberCommand) error } var _ LegacyIdentityStore = (*legacySQLStore)(nil) diff --git a/pkg/registry/apis/iam/legacy/sql_test.go b/pkg/registry/apis/iam/legacy/sql_test.go index 4ad7d87c12b..c5c6fa42379 100644 --- a/pkg/registry/apis/iam/legacy/sql_test.go +++ b/pkg/registry/apis/iam/legacy/sql_test.go @@ -97,6 +97,12 @@ func TestIdentityQueries(t *testing.T) { return &v } + deleteTeamMember := func(q *DeleteTeamMemberCommand) sqltemplate.SQLTemplate { + v := newDeleteTeamMember(nodb, q) + v.SQLTemplate = mocks.NewTestingSQLTemplate() + return &v + } + deleteTeam := func(q *DeleteTeamCommand) sqltemplate.SQLTemplate { v := newDeleteTeam(nodb, q) v.SQLTemplate = mocks.NewTestingSQLTemplate() @@ -294,6 +300,14 @@ func TestIdentityQueries(t *testing.T) { }), }, }, + sqlDeleteTeamMemberQuery: { + { + Name: "delete_team_member_basic", + Data: deleteTeamMember(&DeleteTeamMemberCommand{ + UID: "team-member-1", + }), + }, + }, sqlQueryUserTeamsTemplate: { { Name: "team_1_members_page_1", diff --git a/pkg/registry/apis/iam/legacy/team_binding.go b/pkg/registry/apis/iam/legacy/team_binding.go index e3b89a8e0a9..2928b213ee2 100644 --- a/pkg/registry/apis/iam/legacy/team_binding.go +++ b/pkg/registry/apis/iam/legacy/team_binding.go @@ -368,6 +368,61 @@ func (s *legacySQLStore) UpdateTeamMember(ctx context.Context, ns claims.Namespa return &result, nil } +type DeleteTeamMemberCommand struct { + UID string +} + +var sqlDeleteTeamMemberQuery = mustTemplate("delete_team_member_query.sql") + +func newDeleteTeamMember(sql *legacysql.LegacyDatabaseHelper, cmd *DeleteTeamMemberCommand) deleteTeamMemberQuery { + return deleteTeamMemberQuery{ + SQLTemplate: sqltemplate.New(sql.DialectForDriver()), + TeamMemberTable: sql.Table("team_member"), + Command: cmd, + } +} + +type deleteTeamMemberQuery struct { + sqltemplate.SQLTemplate + TeamMemberTable string + Command *DeleteTeamMemberCommand +} + +func (r deleteTeamMemberQuery) Validate() error { + return nil +} + +func (s *legacySQLStore) DeleteTeamMember(ctx context.Context, ns claims.NamespaceInfo, cmd DeleteTeamMemberCommand) error { + sql, err := s.sql(ctx) + if err != nil { + return err + } + req := newDeleteTeamMember(sql, &cmd) + if err := req.Validate(); err != nil { + return err + } + + err = sql.DB.GetSqlxSession().WithTransaction(ctx, func(st *session.SessionTx) error { + teamMemberQuery, err := sqltemplate.Execute(sqlDeleteTeamMemberQuery, req) + if err != nil { + return fmt.Errorf("failed to execute team member template %q: %w", sqlDeleteTeamMemberQuery.Name(), err) + } + + _, err = st.Exec(ctx, teamMemberQuery, req.GetArgs()...) + if err != nil { + return fmt.Errorf("failed to delete team member: %w", err) + } + + return nil + }) + + if err != nil { + return err + } + + return nil +} + func scanMember(rows *sql.Rows) (TeamMember, error) { m := TeamMember{} err := rows.Scan(&m.ID, &m.UID, &m.TeamUID, &m.TeamID, &m.UserUID, &m.UserID, &m.Name, &m.Email, &m.Username, &m.External, &m.Created, &m.Updated, &m.Permission) diff --git a/pkg/registry/apis/iam/legacy/testdata/mysql--delete_team_member_query-delete_team_member_basic.sql b/pkg/registry/apis/iam/legacy/testdata/mysql--delete_team_member_query-delete_team_member_basic.sql new file mode 100755 index 00000000000..214769eb0ad --- /dev/null +++ b/pkg/registry/apis/iam/legacy/testdata/mysql--delete_team_member_query-delete_team_member_basic.sql @@ -0,0 +1,2 @@ +DELETE FROM `grafana`.`team_member` +WHERE uid = 'team-member-1' diff --git a/pkg/registry/apis/iam/legacy/testdata/postgres--delete_team_member_query-delete_team_member_basic.sql b/pkg/registry/apis/iam/legacy/testdata/postgres--delete_team_member_query-delete_team_member_basic.sql new file mode 100755 index 00000000000..faa6d01389b --- /dev/null +++ b/pkg/registry/apis/iam/legacy/testdata/postgres--delete_team_member_query-delete_team_member_basic.sql @@ -0,0 +1,2 @@ +DELETE FROM "grafana"."team_member" +WHERE uid = 'team-member-1' diff --git a/pkg/registry/apis/iam/legacy/testdata/sqlite--delete_team_member_query-delete_team_member_basic.sql b/pkg/registry/apis/iam/legacy/testdata/sqlite--delete_team_member_query-delete_team_member_basic.sql new file mode 100755 index 00000000000..faa6d01389b --- /dev/null +++ b/pkg/registry/apis/iam/legacy/testdata/sqlite--delete_team_member_query-delete_team_member_basic.sql @@ -0,0 +1,2 @@ +DELETE FROM "grafana"."team_member" +WHERE uid = 'team-member-1' diff --git a/pkg/registry/apis/iam/teambinding/store.go b/pkg/registry/apis/iam/teambinding/store.go index c1895558131..eaaa4766e39 100644 --- a/pkg/registry/apis/iam/teambinding/store.go +++ b/pkg/registry/apis/iam/teambinding/store.go @@ -125,7 +125,34 @@ func (l *LegacyBindingStore) Update(ctx context.Context, name string, objInfo re } func (l *LegacyBindingStore) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { - return nil, false, apierrors.NewMethodNotSupported(bindingResource.GroupResource(), "delete") + if !l.enableAuthnMutation { + return nil, false, apierrors.NewMethodNotSupported(bindingResource.GroupResource(), "delete") + } + + ns, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, false, err + } + + // Check if the team binding exists + _, err = l.Get(ctx, name, nil) + if err != nil { + return nil, false, err + } + + err = l.store.DeleteTeamMember(ctx, ns, legacy.DeleteTeamMemberCommand{ + UID: name, + }) + if err != nil { + return nil, false, err + } + + return &iamv0alpha1.TeamBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns.Value, + }, + }, true, nil } func (l *LegacyBindingStore) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) { diff --git a/pkg/tests/apis/iam/team_bindings_integration_test.go b/pkg/tests/apis/iam/team_bindings_integration_test.go index 669a69d6e3a..1b355296486 100644 --- a/pkg/tests/apis/iam/team_bindings_integration_test.go +++ b/pkg/tests/apis/iam/team_bindings_integration_test.go @@ -74,7 +74,7 @@ func TestIntegrationTeamBindings(t *testing.T) { } func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHelper, team *unstructured.Unstructured, user *unstructured.Unstructured) { - t.Run("should create/update/get team binding using the new APIs", func(t *testing.T) { + t.Run("should create/update/get/delete team binding using the new APIs", func(t *testing.T) { ctx := context.Background() teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{ @@ -137,6 +137,18 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel require.Equal(t, "member", fetchedSpec["permission"]) require.Equal(t, false, fetchedSpec["external"]) require.Equal(t, createdUID, fetched.GetName()) + + // Delete the team binding + err = teamBindingClient.Resource.Delete(ctx, createdUID, metav1.DeleteOptions{}) + require.NoError(t, err) + + // Verify the team binding is deleted + _, err = teamBindingClient.Resource.Get(ctx, createdUID, metav1.GetOptions{}) + require.Error(t, err) + var statusErr *errors.StatusError + require.ErrorAs(t, err, &statusErr) + require.Equal(t, int32(404), statusErr.ErrStatus.Code) + require.Contains(t, statusErr.ErrStatus.Message, "not found") }) t.Run("should not be able to create team binding when using a user with insufficient permissions", func(t *testing.T) {