IAM: Implement the delete method for team bindings (#112844)

* implement the delete method for team bindings

* add integration test

* remove team binding search from legacy store
This commit is contained in:
Mihai Doarna
2025-10-24 11:58:13 +03:00
committed by GitHub
parent 5f9ed73f82
commit 4bdee91501
9 changed files with 119 additions and 2 deletions
@@ -0,0 +1,2 @@
DELETE FROM {{ .Ident .TeamMemberTable }}
WHERE uid = {{ .Arg .Command.UID }}
+1
View File
@@ -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)
+14
View File
@@ -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",
@@ -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)
@@ -0,0 +1,2 @@
DELETE FROM `grafana`.`team_member`
WHERE uid = 'team-member-1'
@@ -0,0 +1,2 @@
DELETE FROM "grafana"."team_member"
WHERE uid = 'team-member-1'
@@ -0,0 +1,2 @@
DELETE FROM "grafana"."team_member"
WHERE uid = 'team-member-1'
+28 -1
View File
@@ -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) {
@@ -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) {