mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-15 09:01:31 +00:00
fix: emit required IAM list members when nothing matches
ListUsers, ListRoles, ListInstanceProfiles and ListGroupsForUser built their results from a nil slice when the filtering loop matched nothing. xmlutil drops a nil slice entirely rather than emitting an empty element, so each response omitted a member its output shape marks required. Each operation's empty-bucket shortcut already returned an empty non-nil slice, so only the filtering path was affected. Start those slices empty and non-nil too. ListAttachedGroupPolicies, ListAttachedRolePolicies, ListPolicies and ListAttachedUserPolicies declare no required output members, so their nil slices are legal and are left alone.
This commit is contained in:
@@ -277,7 +277,9 @@ func (s *IAMServiceImpl) ListGroupsForUser(accountID string, input *iam.ListGrou
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var groups []*iam.Group
|
||||
// Non-nil: Groups is a required member, and a nil slice marshals to no
|
||||
// element at all rather than an empty one.
|
||||
groups := []*iam.Group{}
|
||||
for _, name := range user.Groups {
|
||||
group, err := s.getGroup(ctx, accountID, name)
|
||||
if err != nil {
|
||||
|
||||
@@ -111,7 +111,9 @@ func (s *IAMServiceImpl) ListInstanceProfiles(accountID string, input *iam.ListI
|
||||
}
|
||||
|
||||
keyPrefix := accountID + "."
|
||||
var profiles []*iam.InstanceProfile
|
||||
// Non-nil: InstanceProfiles is a required member, and a nil slice marshals
|
||||
// to no element at all rather than an empty one.
|
||||
profiles := []*iam.InstanceProfile{}
|
||||
for _, key := range keys {
|
||||
if key == utils.VersionKey {
|
||||
continue
|
||||
|
||||
@@ -131,7 +131,9 @@ func (s *IAMServiceImpl) ListRoles(accountID string, input *iam.ListRolesInput)
|
||||
}
|
||||
|
||||
keyPrefix := accountID + "."
|
||||
var roles []*iam.Role
|
||||
// Non-nil: Roles is a required member, and a nil slice marshals to no
|
||||
// element at all rather than an empty one.
|
||||
roles := []*iam.Role{}
|
||||
for _, key := range keys {
|
||||
if key == utils.VersionKey {
|
||||
continue
|
||||
|
||||
@@ -416,7 +416,9 @@ func (s *IAMServiceImpl) ListUsers(accountID string, input *iam.ListUsersInput)
|
||||
}
|
||||
|
||||
keyPrefix := accountID + "."
|
||||
var users []*iam.User
|
||||
// Non-nil: Users is a required member, and a nil slice marshals to no
|
||||
// element at all rather than an empty one.
|
||||
users := []*iam.User{}
|
||||
for _, key := range keys {
|
||||
if key == utils.VersionKey {
|
||||
continue
|
||||
|
||||
@@ -140,6 +140,96 @@ func TestListUsers_Empty(t *testing.T) {
|
||||
assert.Empty(t, out.Users)
|
||||
}
|
||||
|
||||
// TestListRequiredMembersAreEmptyNotNil covers every IAM list operation whose
|
||||
// output shape marks the list member required. Each seeds a record that cannot
|
||||
// match, so the filtering loop runs rather than the no-keys shortcut, and the
|
||||
// result must come back empty but non-nil.
|
||||
func TestListRequiredMembersAreEmptyNotNil(t *testing.T) {
|
||||
const otherAccountID = "999988887777"
|
||||
|
||||
tests := []struct {
|
||||
operation string
|
||||
member string
|
||||
// seed writes a record the listing must filter out, so an empty result
|
||||
// is reached through the loop rather than the empty-bucket shortcut.
|
||||
seed func(t *testing.T, svc *IAMServiceImpl)
|
||||
list func(t *testing.T, svc *IAMServiceImpl) any
|
||||
}{
|
||||
{
|
||||
operation: "ListUsers",
|
||||
member: "Users",
|
||||
seed: func(t *testing.T, svc *IAMServiceImpl) {
|
||||
_, err := svc.CreateUser(otherAccountID, &iam.CreateUserInput{UserName: aws.String("otheruser")})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
list: func(t *testing.T, svc *IAMServiceImpl) any {
|
||||
out, err := svc.ListUsers(testAccountID, &iam.ListUsersInput{})
|
||||
require.NoError(t, err)
|
||||
return out.Users
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "ListRoles",
|
||||
member: "Roles",
|
||||
seed: func(t *testing.T, svc *IAMServiceImpl) {
|
||||
_, err := svc.CreateRole(otherAccountID, &iam.CreateRoleInput{
|
||||
RoleName: aws.String("otherrole"),
|
||||
AssumeRolePolicyDocument: aws.String(validTrustPolicy()),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
list: func(t *testing.T, svc *IAMServiceImpl) any {
|
||||
out, err := svc.ListRoles(testAccountID, &iam.ListRolesInput{})
|
||||
require.NoError(t, err)
|
||||
return out.Roles
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "ListInstanceProfiles",
|
||||
member: "InstanceProfiles",
|
||||
seed: func(t *testing.T, svc *IAMServiceImpl) {
|
||||
_, err := svc.CreateInstanceProfile(otherAccountID, &iam.CreateInstanceProfileInput{
|
||||
InstanceProfileName: aws.String("otherprofile"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
},
|
||||
list: func(t *testing.T, svc *IAMServiceImpl) any {
|
||||
out, err := svc.ListInstanceProfiles(testAccountID, &iam.ListInstanceProfilesInput{})
|
||||
require.NoError(t, err)
|
||||
return out.InstanceProfiles
|
||||
},
|
||||
},
|
||||
{
|
||||
operation: "ListGroupsForUser",
|
||||
member: "Groups",
|
||||
// Membership is read off the user, so a user belonging to no group
|
||||
// is the empty case; a foreign record would not reach the loop.
|
||||
seed: func(t *testing.T, svc *IAMServiceImpl) {
|
||||
createTestUser(t, svc, "grouplessuser")
|
||||
createTestGroup(t, svc, "unrelatedgroup")
|
||||
},
|
||||
list: func(t *testing.T, svc *IAMServiceImpl) any {
|
||||
out, err := svc.ListGroupsForUser(testAccountID, &iam.ListGroupsForUserInput{
|
||||
UserName: aws.String("grouplessuser"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return out.Groups
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.operation, func(t *testing.T) {
|
||||
svc := setupTestIAMService(t)
|
||||
tt.seed(t, svc)
|
||||
|
||||
got := tt.list(t, svc)
|
||||
assert.Empty(t, got)
|
||||
assert.NotNil(t, got, emptyRequiredListMsg(tt.operation, tt.member))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListUsers_PathFilter(t *testing.T) {
|
||||
svc := setupTestIAMService(t)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user