diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 04ded71f1242a..1511d92305a75 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5993,16 +5993,23 @@ WHERE organization_id = $1 ELSE true END + -- Filter by system type + AND CASE + WHEN $2::bool THEN TRUE + ELSE + is_system = false + END ORDER BY -- Deterministic and consistent ordering of all users. This is to ensure consistent pagination. - LOWER(username) ASC OFFSET $2 + LOWER(username) ASC OFFSET $3 LIMIT -- A null limit means "no limit", so 0 means return all - NULLIF($3 :: int, 0) + NULLIF($4 :: int, 0) ` type PaginatedOrganizationMembersParams struct { OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + IncludeSystem bool `db:"include_system" json:"include_system"` OffsetOpt int32 `db:"offset_opt" json:"offset_opt"` LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } @@ -6018,7 +6025,12 @@ type PaginatedOrganizationMembersRow struct { } func (q *sqlQuerier) PaginatedOrganizationMembers(ctx context.Context, arg PaginatedOrganizationMembersParams) ([]PaginatedOrganizationMembersRow, error) { - rows, err := q.db.QueryContext(ctx, paginatedOrganizationMembers, arg.OrganizationID, arg.OffsetOpt, arg.LimitOpt) + rows, err := q.db.QueryContext(ctx, paginatedOrganizationMembers, + arg.OrganizationID, + arg.IncludeSystem, + arg.OffsetOpt, + arg.LimitOpt, + ) if err != nil { return nil, err } diff --git a/coderd/database/queries/organizationmembers.sql b/coderd/database/queries/organizationmembers.sql index 9d570bc1c49ee..692280415f1ea 100644 --- a/coderd/database/queries/organizationmembers.sql +++ b/coderd/database/queries/organizationmembers.sql @@ -89,6 +89,12 @@ WHERE organization_id = @organization_id ELSE true END + -- Filter by system type + AND CASE + WHEN @include_system::bool THEN TRUE + ELSE + is_system = false + END ORDER BY -- Deterministic and consistent ordering of all users. This is to ensure consistent pagination. LOWER(username) ASC OFFSET @offset_opt diff --git a/coderd/members.go b/coderd/members.go index 5a031fe7eab90..db4a81a31a8bb 100644 --- a/coderd/members.go +++ b/coderd/members.go @@ -161,7 +161,7 @@ func (api *API) listMembers(rw http.ResponseWriter, r *http.Request) { members, err := api.Database.OrganizationMembers(ctx, database.OrganizationMembersParams{ OrganizationID: organization.ID, UserID: uuid.Nil, - IncludeSystem: false, + IncludeSystem: true, }) if httpapi.Is404Error(err) { httpapi.ResourceNotFound(rw) @@ -203,6 +203,7 @@ func (api *API) paginatedMembers(rw http.ResponseWriter, r *http.Request) { paginatedMemberRows, err := api.Database.PaginatedOrganizationMembers(ctx, database.PaginatedOrganizationMembersParams{ OrganizationID: organization.ID, + IncludeSystem: true, // #nosec G115 - Pagination limits are small and fit in int32 LimitOpt: int32(paginationParams.Limit), // #nosec G115 - Pagination offsets are small and fit in int32 diff --git a/coderd/members_test.go b/coderd/members_test.go index bc892bb0679d4..5e7032ba20b1c 100644 --- a/coderd/members_test.go +++ b/coderd/members_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" @@ -62,9 +63,9 @@ func TestListMembers(t *testing.T) { ctx := testutil.Context(t, testutil.WaitShort) members, err := client.OrganizationMembers(ctx, first.OrganizationID) require.NoError(t, err) - require.Len(t, members, 2) + require.Len(t, members, 3) require.ElementsMatch(t, - []uuid.UUID{first.UserID, user.ID}, + []uuid.UUID{first.UserID, user.ID, database.PrebuildsSystemUserID}, db2sdk.List(members, onlyIDs)) }) } diff --git a/enterprise/coderd/groups.go b/enterprise/coderd/groups.go index 89671e00bd65c..f12241f7f0b30 100644 --- a/enterprise/coderd/groups.go +++ b/enterprise/coderd/groups.go @@ -156,7 +156,7 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) { currentMembers, err := api.Database.GetGroupMembersByGroupID(ctx, database.GetGroupMembersByGroupIDParams{ GroupID: group.ID, - IncludeSystem: false, + IncludeSystem: true, }) if err != nil { httpapi.InternalServerError(rw, err) @@ -180,7 +180,7 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) { _, err := database.ExpectOne(api.Database.OrganizationMembers(ctx, database.OrganizationMembersParams{ OrganizationID: group.OrganizationID, UserID: uuid.MustParse(id), - IncludeSystem: false, + IncludeSystem: true, })) if errors.Is(err, sql.ErrNoRows) { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ @@ -296,7 +296,7 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) { patchedMembers, err := api.Database.GetGroupMembersByGroupID(ctx, database.GetGroupMembersByGroupIDParams{ GroupID: group.ID, - IncludeSystem: false, + IncludeSystem: true, }) if err != nil { httpapi.InternalServerError(rw, err) @@ -307,7 +307,7 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) { memberCount, err := api.Database.GetGroupMembersCountByGroupID(ctx, database.GetGroupMembersCountByGroupIDParams{ GroupID: group.ID, - IncludeSystem: false, + IncludeSystem: true, }) if err != nil { httpapi.InternalServerError(rw, err) @@ -353,7 +353,7 @@ func (api *API) deleteGroup(rw http.ResponseWriter, r *http.Request) { groupMembers, getMembersErr := api.Database.GetGroupMembersByGroupID(ctx, database.GetGroupMembersByGroupIDParams{ GroupID: group.ID, - IncludeSystem: false, + IncludeSystem: true, }) if getMembersErr != nil { httpapi.InternalServerError(rw, getMembersErr) @@ -407,7 +407,7 @@ func (api *API) group(rw http.ResponseWriter, r *http.Request) { users, err := api.Database.GetGroupMembersByGroupID(ctx, database.GetGroupMembersByGroupIDParams{ GroupID: group.ID, - IncludeSystem: false, + IncludeSystem: true, }) if err != nil && !errors.Is(err, sql.ErrNoRows) { httpapi.InternalServerError(rw, err) @@ -416,7 +416,7 @@ func (api *API) group(rw http.ResponseWriter, r *http.Request) { memberCount, err := api.Database.GetGroupMembersCountByGroupID(ctx, database.GetGroupMembersCountByGroupIDParams{ GroupID: group.ID, - IncludeSystem: false, + IncludeSystem: true, }) if err != nil { httpapi.InternalServerError(rw, err) @@ -512,7 +512,7 @@ func (api *API) groups(rw http.ResponseWriter, r *http.Request) { for _, group := range groups { members, err := api.Database.GetGroupMembersByGroupID(ctx, database.GetGroupMembersByGroupIDParams{ GroupID: group.Group.ID, - IncludeSystem: false, + IncludeSystem: true, }) if err != nil { httpapi.InternalServerError(rw, err) @@ -520,7 +520,7 @@ func (api *API) groups(rw http.ResponseWriter, r *http.Request) { } memberCount, err := api.Database.GetGroupMembersCountByGroupID(ctx, database.GetGroupMembersCountByGroupIDParams{ GroupID: group.Group.ID, - IncludeSystem: false, + IncludeSystem: true, }) if err != nil { httpapi.InternalServerError(rw, err) diff --git a/enterprise/coderd/groups_test.go b/enterprise/coderd/groups_test.go index 568825adcd0ea..9b294ee3d7094 100644 --- a/enterprise/coderd/groups_test.go +++ b/enterprise/coderd/groups_test.go @@ -862,12 +862,12 @@ func TestGroup(t *testing.T) { // The 'Everyone' group always has an ID that matches the organization ID. group, err := userAdminClient.Group(ctx, user.OrganizationID) require.NoError(t, err) - require.Len(t, group.Members, 4) + require.Len(t, group.Members, 5) require.Equal(t, "Everyone", group.Name) require.Equal(t, user.OrganizationID, group.OrganizationID) require.Contains(t, group.Members, user1.ReducedUser) require.Contains(t, group.Members, user2.ReducedUser) - require.NotContains(t, group.Members, prebuildsUser.ReducedUser) + require.Contains(t, group.Members, prebuildsUser.ReducedUser) }) } diff --git a/enterprise/coderd/roles_test.go b/enterprise/coderd/roles_test.go index 70c432755f7fa..79dbf4ed2c98a 100644 --- a/enterprise/coderd/roles_test.go +++ b/enterprise/coderd/roles_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" @@ -360,9 +361,9 @@ func TestCustomOrganizationRole(t *testing.T) { // Verify members have the custom role originalMembers, err := orgAdmin.OrganizationMembers(ctx, first.OrganizationID) require.NoError(t, err) - require.Len(t, originalMembers, 5) // 3 members + org admin + owner + require.Len(t, originalMembers, 6) // 3 members + org admin + owner + prebuilds system user for _, member := range originalMembers { - if member.UserID == orgAdminUser.ID || member.UserID == first.UserID { + if member.UserID == orgAdminUser.ID || member.UserID == first.UserID || member.UserID == database.PrebuildsSystemUserID { continue } @@ -377,7 +378,7 @@ func TestCustomOrganizationRole(t *testing.T) { // Verify the role was removed from all members members, err := orgAdmin.OrganizationMembers(ctx, first.OrganizationID) require.NoError(t, err) - require.Len(t, members, 5) // 3 members + org admin + owner + require.Len(t, members, 6) // 3 members + org admin + owner + prebuilds system user for _, member := range members { require.False(t, slices.ContainsFunc(member.Roles, func(role codersdk.SlimRole) bool { return role.Name == customRoleIdentifier.Name