From d0e840a34abcb81804a833ad5bbf87477b74dd62 Mon Sep 17 00:00:00 2001 From: Utsav Lal Date: Thu, 3 Apr 2025 00:53:03 -0400 Subject: [PATCH 01/12] ability to filter users based on login type --- coderd/database/modelqueries.go | 1 + coderd/database/queries.sql.go | 12 ++++++++++-- coderd/database/queries/users.sql | 6 ++++++ coderd/searchquery/search.go | 1 + coderd/users.go | 1 + 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 3c437cde293d3..1bf37ce0c09e6 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -395,6 +395,7 @@ func (q *sqlQuerier) GetAuthorizedUsers(ctx context.Context, arg GetUsersParams, arg.CreatedAfter, arg.IncludeSystem, arg.GithubComUserID, + pq.Array(arg.LoginType), arg.OffsetOpt, arg.LimitOpt, ) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index ebc4a0da439c0..5edc20ccd93b2 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -11928,16 +11928,22 @@ WHERE github_com_user_id = $10 ELSE true END + -- Filter by login_type + AND CASE + WHEN cardinality($11 :: login_type[]) > 0 THEN + login_type = ANY($11 :: login_type[]) + ELSE true + END -- End of filters -- Authorize Filter clause will be injected below in GetAuthorizedUsers -- @authorize_filter ORDER BY -- Deterministic and consistent ordering of all users. This is to ensure consistent pagination. - LOWER(username) ASC OFFSET $11 + LOWER(username) ASC OFFSET $12 LIMIT -- A null limit means "no limit", so 0 means return all - NULLIF($12 :: int, 0) + NULLIF($13 :: int, 0) ` type GetUsersParams struct { @@ -11951,6 +11957,7 @@ type GetUsersParams struct { CreatedAfter time.Time `db:"created_after" json:"created_after"` IncludeSystem bool `db:"include_system" json:"include_system"` GithubComUserID int64 `db:"github_com_user_id" json:"github_com_user_id"` + LoginType []LoginType `db:"login_type" json:"login_type"` OffsetOpt int32 `db:"offset_opt" json:"offset_opt"` LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } @@ -11990,6 +11997,7 @@ func (q *sqlQuerier) GetUsers(ctx context.Context, arg GetUsersParams) ([]GetUse arg.CreatedAfter, arg.IncludeSystem, arg.GithubComUserID, + pq.Array(arg.LoginType), arg.OffsetOpt, arg.LimitOpt, ) diff --git a/coderd/database/queries/users.sql b/coderd/database/queries/users.sql index c4304cfc3e60e..649f5ae41622e 100644 --- a/coderd/database/queries/users.sql +++ b/coderd/database/queries/users.sql @@ -237,6 +237,12 @@ WHERE github_com_user_id = @github_com_user_id ELSE true END + -- Filter by login_type + AND CASE + WHEN cardinality(@login_type :: login_type[]) > 0 THEN + login_type = ANY(@login_type :: login_type[]) + ELSE true + END -- End of filters -- Authorize Filter clause will be injected below in GetAuthorizedUsers diff --git a/coderd/searchquery/search.go b/coderd/searchquery/search.go index 938f725330cd0..6f4a1c337c535 100644 --- a/coderd/searchquery/search.go +++ b/coderd/searchquery/search.go @@ -88,6 +88,7 @@ func Users(query string) (database.GetUsersParams, []codersdk.ValidationError) { CreatedAfter: parser.Time3339Nano(values, time.Time{}, "created_after"), CreatedBefore: parser.Time3339Nano(values, time.Time{}, "created_before"), GithubComUserID: parser.Int64(values, 0, "github_com_user_id"), + LoginType: httpapi.ParseCustomList(parser, values, []database.LoginType{}, "login_type", httpapi.ParseEnum[database.LoginType]), } parser.ErrorExcessParams(values) return filter, parser.Errors diff --git a/coderd/users.go b/coderd/users.go index 069e1fc240302..eef609a40f607 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -306,6 +306,7 @@ func (api *API) GetUsers(rw http.ResponseWriter, r *http.Request) ([]database.Us CreatedAfter: params.CreatedAfter, CreatedBefore: params.CreatedBefore, GithubComUserID: params.GithubComUserID, + LoginType: params.LoginType, // #nosec G115 - Pagination offsets are small and fit in int32 OffsetOpt: int32(paginationParams.Offset), // #nosec G115 - Pagination limits are small and fit in int32 From 3016e401f183c21f276e2ffa7481167be5a682ce Mon Sep 17 00:00:00 2001 From: Utsav Lal Date: Thu, 3 Apr 2025 00:53:13 -0400 Subject: [PATCH 02/12] update documentation also --- docs/admin/users/index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/admin/users/index.md b/docs/admin/users/index.md index ed7fbdebd4c5f..cdc22e5877455 100644 --- a/docs/admin/users/index.md +++ b/docs/admin/users/index.md @@ -190,6 +190,8 @@ to use the Coder's filter query: `status:active last_seen_before:"2023-07-01T00:00:00Z"` - To find users who were created between January 1 and January 18, 2023: `created_before:"2023-01-18T00:00:00Z" created_after:"2023-01-01T23:59:59Z"` +- To find users who have login type as github and is a member: + `login_type:github role:member` The following filters are supported: @@ -203,3 +205,4 @@ The following filters are supported: the RFC3339Nano format. - `created_before` and `created_after` - The time a user was created. Uses the RFC3339Nano format. +- `login_type` - Represents the login type of the user. Refer here for all the roles [LoginType documentation](https://pkg.go.dev/github.com/coder/coder/v2/codersdk#LoginType) From de02f49b9ee88ca6c0b53cf26bf63f039f186a49 Mon Sep 17 00:00:00 2001 From: Utsav Lal Date: Thu, 3 Apr 2025 13:00:11 -0400 Subject: [PATCH 03/12] add tests --- coderd/searchquery/search_test.go | 88 +++++++++++++++++++++++-------- coderd/users.go | 2 +- coderd/users_test.go | 25 +++++++++ codersdk/users.go | 6 ++- 4 files changed, 98 insertions(+), 23 deletions(-) diff --git a/coderd/searchquery/search_test.go b/coderd/searchquery/search_test.go index 0a8e08e3d45fe..065937f389e4a 100644 --- a/coderd/searchquery/search_test.go +++ b/coderd/searchquery/search_test.go @@ -386,62 +386,69 @@ func TestSearchUsers(t *testing.T) { Name: "Empty", Query: "", Expected: database.GetUsersParams{ - Status: []database.UserStatus{}, - RbacRole: []string{}, + Status: []database.UserStatus{}, + RbacRole: []string{}, + LoginType: []database.LoginType{}, }, }, { Name: "Username", Query: "user-name", Expected: database.GetUsersParams{ - Search: "user-name", - Status: []database.UserStatus{}, - RbacRole: []string{}, + Search: "user-name", + Status: []database.UserStatus{}, + RbacRole: []string{}, + LoginType: []database.LoginType{}, }, }, { Name: "UsernameWithSpaces", Query: " user-name ", Expected: database.GetUsersParams{ - Search: "user-name", - Status: []database.UserStatus{}, - RbacRole: []string{}, + Search: "user-name", + Status: []database.UserStatus{}, + RbacRole: []string{}, + LoginType: []database.LoginType{}, }, }, { Name: "Username+Param", Query: "usEr-name stAtus:actiVe", Expected: database.GetUsersParams{ - Search: "user-name", - Status: []database.UserStatus{database.UserStatusActive}, - RbacRole: []string{}, + Search: "user-name", + Status: []database.UserStatus{database.UserStatusActive}, + RbacRole: []string{}, + LoginType: []database.LoginType{}, }, }, { Name: "OnlyParams", Query: "status:acTIve sEArch:User-Name role:Owner", Expected: database.GetUsersParams{ - Search: "user-name", - Status: []database.UserStatus{database.UserStatusActive}, - RbacRole: []string{codersdk.RoleOwner}, + Search: "user-name", + Status: []database.UserStatus{database.UserStatusActive}, + RbacRole: []string{codersdk.RoleOwner}, + LoginType: []database.LoginType{}, }, }, { Name: "QuotedParam", Query: `status:SuSpenDeD sEArch:"User Name" role:meMber`, Expected: database.GetUsersParams{ - Search: "user name", - Status: []database.UserStatus{database.UserStatusSuspended}, - RbacRole: []string{codersdk.RoleMember}, + Search: "user name", + Status: []database.UserStatus{database.UserStatusSuspended}, + RbacRole: []string{codersdk.RoleMember}, + LoginType: []database.LoginType{}, }, }, { Name: "QuotedKey", Query: `"status":acTIve "sEArch":User-Name "role":Owner`, Expected: database.GetUsersParams{ - Search: "user-name", - Status: []database.UserStatus{database.UserStatusActive}, - RbacRole: []string{codersdk.RoleOwner}, + Search: "user-name", + Status: []database.UserStatus{database.UserStatusActive}, + RbacRole: []string{codersdk.RoleOwner}, + LoginType: []database.LoginType{}, }, }, { @@ -449,9 +456,48 @@ func TestSearchUsers(t *testing.T) { Name: "QuotedSpecial", Query: `search:"user:name"`, Expected: database.GetUsersParams{ - Search: "user:name", + Search: "user:name", + Status: []database.UserStatus{}, + RbacRole: []string{}, + LoginType: []database.LoginType{}, + }, + }, + { + Name: "LoginType", + Query: "login_type:github", + Expected: database.GetUsersParams{ + Search: "", + Status: []database.UserStatus{}, + RbacRole: []string{}, + LoginType: []database.LoginType{database.LoginTypeGithub}, + }, + }, + { + Name: "MultipleLoginTypesWithSpaces", + Query: "login_type:github login_type:password", + Expected: database.GetUsersParams{ + Search: "", Status: []database.UserStatus{}, RbacRole: []string{}, + LoginType: []database.LoginType{ + database.LoginTypeGithub, + database.LoginTypePassword, + }, + }, + }, + { + Name: "MultipleLoginTypesWithCommas", + Query: "login_type:github,password,none,oidc", + Expected: database.GetUsersParams{ + Search: "", + Status: []database.UserStatus{}, + RbacRole: []string{}, + LoginType: []database.LoginType{ + database.LoginTypeGithub, + database.LoginTypePassword, + database.LoginTypeNone, + database.LoginTypeOIDC, + }, }, }, diff --git a/coderd/users.go b/coderd/users.go index eef609a40f607..3d63888bed65a 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -306,7 +306,7 @@ func (api *API) GetUsers(rw http.ResponseWriter, r *http.Request) ([]database.Us CreatedAfter: params.CreatedAfter, CreatedBefore: params.CreatedBefore, GithubComUserID: params.GithubComUserID, - LoginType: params.LoginType, + LoginType: params.LoginType, // #nosec G115 - Pagination offsets are small and fit in int32 OffsetOpt: int32(paginationParams.Offset), // #nosec G115 - Pagination limits are small and fit in int32 diff --git a/coderd/users_test.go b/coderd/users_test.go index c21eca85a5ee7..d2ada56123fc5 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1902,6 +1902,31 @@ func TestGetUsers(t *testing.T) { require.Len(t, res.Users, 1) require.Equal(t, res.Users[0].ID, first.UserID) }) + t.Run("LoginType", func(t *testing.T) { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + client, db := coderdtest.NewWithDatabase(t, nil) + first := coderdtest.CreateFirstUser(t, client) + _ = dbgen.User(t, db, database.User{ + Email: "test2@coder.com", + Username: "test2", + }) + // nolint:gocritic // Unit test + _, err := db.UpdateUserLoginType(dbauthz.AsSystemRestricted(ctx), database.UpdateUserLoginTypeParams{ + UserID: first.UserID, + NewLoginType: database.LoginTypeNone, + }) + require.NoError(t, err) + res, err := client.Users(ctx, codersdk.UsersRequest{ + LoginType: codersdk.LoginTypeNone, + }) + require.NoError(t, err) + require.Len(t, res.Users, 1) + require.Equal(t, res.Users[0].ID, first.UserID) + require.Equal(t, res.Users[0].LoginType, codersdk.LoginTypeGithub) + }) } func TestGetUsersPagination(t *testing.T) { diff --git a/codersdk/users.go b/codersdk/users.go index 31854731a0ae1..14428e15b4d21 100644 --- a/codersdk/users.go +++ b/codersdk/users.go @@ -28,7 +28,8 @@ type UsersRequest struct { // Filter users by status. Status UserStatus `json:"status,omitempty" typescript:"-"` // Filter users that have the given role. - Role string `json:"role,omitempty" typescript:"-"` + Role string `json:"role,omitempty" typescript:"-"` + LoginType LoginType `json:"login_type,omitempty" typescript:"-"` SearchQuery string `json:"q,omitempty"` Pagination @@ -723,6 +724,9 @@ func (c *Client) Users(ctx context.Context, req UsersRequest) (GetUsersResponse, if req.SearchQuery != "" { params = append(params, req.SearchQuery) } + if req.LoginType != "" { + params = append(params, "login_type:"+string(req.LoginType)) + } q.Set("q", strings.Join(params, " ")) r.URL.RawQuery = q.Encode() }, From 241c898a2034bbca5b0c54c22620737ee2cab877 Mon Sep 17 00:00:00 2001 From: Utsav Lal Date: Thu, 3 Apr 2025 23:41:06 -0400 Subject: [PATCH 04/12] change test case --- coderd/users_test.go | 58 ++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/coderd/users_test.go b/coderd/users_test.go index d2ada56123fc5..faf1567a5f5a4 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1902,31 +1902,43 @@ func TestGetUsers(t *testing.T) { require.Len(t, res.Users, 1) require.Equal(t, res.Users[0].ID, first.UserID) }) - t.Run("LoginType", func(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() +} - client, db := coderdtest.NewWithDatabase(t, nil) - first := coderdtest.CreateFirstUser(t, client) - _ = dbgen.User(t, db, database.User{ - Email: "test2@coder.com", - Username: "test2", - }) - // nolint:gocritic // Unit test - _, err := db.UpdateUserLoginType(dbauthz.AsSystemRestricted(ctx), database.UpdateUserLoginTypeParams{ - UserID: first.UserID, - NewLoginType: database.LoginTypeNone, - }) - require.NoError(t, err) - res, err := client.Users(ctx, codersdk.UsersRequest{ - LoginType: codersdk.LoginTypeNone, - }) - require.NoError(t, err) - require.Len(t, res.Users, 1) - require.Equal(t, res.Users[0].ID, first.UserID) - require.Equal(t, res.Users[0].LoginType, codersdk.LoginTypeGithub) +func TestGetUsersFilters(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + first := coderdtest.CreateFirstUser(t, client) + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + // Create a user with a specific role + _, err := client.User(ctx, first.UserID.String()) + require.NoError(t, err, "") + + _, err = client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ + Email: "alice@email.com", + Username: "alice", + Password: "MySecurePassword!", + OrganizationIDs: []uuid.UUID{first.OrganizationID}, + UserLoginType: codersdk.LoginTypePassword, + }) + require.NoError(t, err) + + _, err = client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ + Email: "alice123@email.com", + Username: "alice123", + OrganizationIDs: []uuid.UUID{first.OrganizationID}, + UserLoginType: codersdk.LoginTypeGithub, + }) + require.NoError(t, err) + + // Test filtering by role + res, err := client.Users(ctx, codersdk.UsersRequest{ + LoginType: "password", // Ensure we're filtering by login type }) + require.NoError(t, err, "should not error when filtering by role") + require.Len(t, res.Users, 1, "should find one user with the member role") + require.Equal(t, res.Users[0].Username, "alice", "should return the correct user with member role") } func TestGetUsersPagination(t *testing.T) { From 5ddc42af755e722d731f9c88eee4eb3651fd99aa Mon Sep 17 00:00:00 2001 From: Utsav Lal Date: Mon, 7 Apr 2025 01:06:02 -0400 Subject: [PATCH 05/12] filter by multiple login types --- codersdk/users.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/codersdk/users.go b/codersdk/users.go index 14428e15b4d21..7cbaaa49518d3 100644 --- a/codersdk/users.go +++ b/codersdk/users.go @@ -28,8 +28,8 @@ type UsersRequest struct { // Filter users by status. Status UserStatus `json:"status,omitempty" typescript:"-"` // Filter users that have the given role. - Role string `json:"role,omitempty" typescript:"-"` - LoginType LoginType `json:"login_type,omitempty" typescript:"-"` + Role string `json:"role,omitempty" typescript:"-"` + LoginType []LoginType `json:"login_type,omitempty" typescript:"-"` SearchQuery string `json:"q,omitempty"` Pagination @@ -724,8 +724,10 @@ func (c *Client) Users(ctx context.Context, req UsersRequest) (GetUsersResponse, if req.SearchQuery != "" { params = append(params, req.SearchQuery) } - if req.LoginType != "" { - params = append(params, "login_type:"+string(req.LoginType)) + if len(req.LoginType) > 0 { + for _, lt := range req.LoginType { + params = append(params, "login_type:"+string(lt)) + } } q.Set("q", strings.Join(params, " ")) r.URL.RawQuery = q.Encode() From f73e1f23c3816c8bc6a06a5d5eaad79d53d91277 Mon Sep 17 00:00:00 2001 From: Utsav Lal Date: Mon, 7 Apr 2025 01:06:28 -0400 Subject: [PATCH 06/12] add filter by logintype to mock db --- coderd/database/dbmem/dbmem.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index bfae69fa68b98..b82f3d03f1c27 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -6796,6 +6796,18 @@ func (q *FakeQuerier) GetUsers(_ context.Context, params database.GetUsersParams users = usersFilteredByRole } + if len(params.LoginType) > 0 { + usersFilteredByLoginType := make([]database.User, 0, len(users)) + for i, user := range users { + if slice.ContainsCompare(params.LoginType, user.LoginType, func(a, b database.LoginType) bool { + return strings.EqualFold(string(a), string(b)) + }) { + usersFilteredByLoginType = append(usersFilteredByLoginType, users[i]) + } + } + users = usersFilteredByLoginType + } + if !params.CreatedBefore.IsZero() { usersFilteredByCreatedAt := make([]database.User, 0, len(users)) for i, user := range users { From b649181eedd044785050dd0a2f4c2c751b93e4af Mon Sep 17 00:00:00 2001 From: Utsav Lal Date: Mon, 7 Apr 2025 01:06:39 -0400 Subject: [PATCH 07/12] update tests --- coderd/users_test.go | 121 ++++++++++++++++++++++++++++++++----------- 1 file changed, 92 insertions(+), 29 deletions(-) diff --git a/coderd/users_test.go b/coderd/users_test.go index faf1567a5f5a4..0ac1e5cbb99bc 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1902,43 +1902,106 @@ func TestGetUsers(t *testing.T) { require.Len(t, res.Users, 1) require.Equal(t, res.Users[0].ID, first.UserID) }) -} -func TestGetUsersFilters(t *testing.T) { - t.Parallel() - client := coderdtest.New(t, nil) - first := coderdtest.CreateFirstUser(t, client) - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() + t.Run("LoginTypeNoneFilter", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + first := coderdtest.CreateFirstUser(t, client) + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() - // Create a user with a specific role - _, err := client.User(ctx, first.UserID.String()) - require.NoError(t, err, "") + _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ + Email: "bob@email.com", + Username: "bob", + OrganizationIDs: []uuid.UUID{first.OrganizationID}, + UserLoginType: codersdk.LoginTypeNone, + }) + require.NoError(t, err) - _, err = client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ - Email: "alice@email.com", - Username: "alice", - Password: "MySecurePassword!", - OrganizationIDs: []uuid.UUID{first.OrganizationID}, - UserLoginType: codersdk.LoginTypePassword, + // Test filtering by role + res, err := client.Users(ctx, codersdk.UsersRequest{ + LoginType: []codersdk.LoginType{codersdk.LoginTypeNone}, + }) + require.NoError(t, err, "should not error when filtering by role") + require.Len(t, res.Users, 1, "should find one user with the member role") + require.Equal(t, res.Users[0].Username, "bob", "should return the correct user with member role") }) - require.NoError(t, err) - _, err = client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ - Email: "alice123@email.com", - Username: "alice123", - OrganizationIDs: []uuid.UUID{first.OrganizationID}, - UserLoginType: codersdk.LoginTypeGithub, + t.Run("LoginTypeMultipleFilter", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + first := coderdtest.CreateFirstUser(t, client) + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ + Email: "bob@email.com", + Username: "bob", + OrganizationIDs: []uuid.UUID{first.OrganizationID}, + UserLoginType: codersdk.LoginTypeNone, + }) + require.NoError(t, err) + + _, err = client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ + Email: "charlie@email.com", + Username: "charlie", + OrganizationIDs: []uuid.UUID{first.OrganizationID}, + UserLoginType: codersdk.LoginTypeGithub, + }) + require.NoError(t, err) + + // Test filtering by role + res, err := client.Users(ctx, codersdk.UsersRequest{ + LoginType: []codersdk.LoginType{codersdk.LoginTypeNone, codersdk.LoginTypeGithub}, // Ensure we're filtering by login type + }) + require.NoError(t, err, "should not error when filtering by role") + require.Len(t, res.Users, 2, "should find two users with the specified login types") + usernames := make(map[string]bool) + for _, user := range res.Users { + usernames[user.Username] = true + } + require.True(t, usernames["bob"], "should return the correct user with login type none") + require.True(t, usernames["charlie"], "should return the correct user with login type github") + // Ensure that the user with login type none is indeed in the result + for _, user := range res.Users { + if user.Username == "bob" { + require.Equal(t, user.LoginType, codersdk.LoginTypeNone, "bob should have login type none") + } + if user.Username == "charlie" { + require.Equal(t, user.LoginType, codersdk.LoginTypeGithub, "charlie should have login type github") + } + } }) - require.NoError(t, err) - // Test filtering by role - res, err := client.Users(ctx, codersdk.UsersRequest{ - LoginType: "password", // Ensure we're filtering by login type + t.Run("DormantUserWithLoginTypeNone", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + first := coderdtest.CreateFirstUser(t, client) + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ + Email: "bob@email.com", + Username: "bob", + OrganizationIDs: []uuid.UUID{first.OrganizationID}, + UserLoginType: codersdk.LoginTypeNone, + }) + require.NoError(t, err) + + _, err = client.UpdateUserStatus(ctx, "bob", codersdk.UserStatusSuspended) + require.NoError(t, err, "should set bob's status to dormant") + + // Test filtering by role + res, err := client.Users(ctx, codersdk.UsersRequest{ + Status: codersdk.UserStatusSuspended, + LoginType: []codersdk.LoginType{codersdk.LoginTypeNone, codersdk.LoginTypeGithub}, + }) + require.NoError(t, err, "should not error when filtering by role") + require.Len(t, res.Users, 1, "should find one dormant user with the specified login type") + require.Equal(t, res.Users[0].Username, "bob", "should return the correct dormant user with login type none") + require.Equal(t, res.Users[0].Status, codersdk.UserStatusSuspended, "bob should be in dormant status") + require.Equal(t, res.Users[0].LoginType, codersdk.LoginTypeNone, "bob should have login type none") }) - require.NoError(t, err, "should not error when filtering by role") - require.Len(t, res.Users, 1, "should find one user with the member role") - require.Equal(t, res.Users[0].Username, "alice", "should return the correct user with member role") } func TestGetUsersPagination(t *testing.T) { From 15407e0d3b89d5982424b399f144816cd88056f1 Mon Sep 17 00:00:00 2001 From: Utsav Lal Date: Mon, 7 Apr 2025 10:18:23 -0400 Subject: [PATCH 08/12] cleanup tests --- coderd/users_test.go | 59 ++++++++++++++++---------------------------- 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/coderd/users_test.go b/coderd/users_test.go index 0ac1e5cbb99bc..e09f4402a808f 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1907,8 +1907,7 @@ func TestGetUsers(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) first := coderdtest.CreateFirstUser(t, client) - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() + ctx := testutil.Context(t, testutil.WaitLong) _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ Email: "bob@email.com", @@ -1918,67 +1917,52 @@ func TestGetUsers(t *testing.T) { }) require.NoError(t, err) - // Test filtering by role res, err := client.Users(ctx, codersdk.UsersRequest{ LoginType: []codersdk.LoginType{codersdk.LoginTypeNone}, }) - require.NoError(t, err, "should not error when filtering by role") - require.Len(t, res.Users, 1, "should find one user with the member role") - require.Equal(t, res.Users[0].Username, "bob", "should return the correct user with member role") + require.NoError(t, err) + require.Len(t, res.Users, 1) + require.Equal(t, res.Users[0].LoginType, codersdk.LoginTypeNone) }) t.Run("LoginTypeMultipleFilter", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) first := coderdtest.CreateFirstUser(t, client) - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() + ctx := testutil.Context(t, testutil.WaitLong) + filtered := make([]codersdk.User, 0) - _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ + bob, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ Email: "bob@email.com", Username: "bob", OrganizationIDs: []uuid.UUID{first.OrganizationID}, UserLoginType: codersdk.LoginTypeNone, }) require.NoError(t, err) + filtered = append(filtered, bob) - _, err = client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ + charlie, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ Email: "charlie@email.com", Username: "charlie", OrganizationIDs: []uuid.UUID{first.OrganizationID}, UserLoginType: codersdk.LoginTypeGithub, }) require.NoError(t, err) + filtered = append(filtered, charlie) - // Test filtering by role res, err := client.Users(ctx, codersdk.UsersRequest{ - LoginType: []codersdk.LoginType{codersdk.LoginTypeNone, codersdk.LoginTypeGithub}, // Ensure we're filtering by login type + LoginType: []codersdk.LoginType{codersdk.LoginTypeNone, codersdk.LoginTypeGithub}, }) - require.NoError(t, err, "should not error when filtering by role") - require.Len(t, res.Users, 2, "should find two users with the specified login types") - usernames := make(map[string]bool) - for _, user := range res.Users { - usernames[user.Username] = true - } - require.True(t, usernames["bob"], "should return the correct user with login type none") - require.True(t, usernames["charlie"], "should return the correct user with login type github") - // Ensure that the user with login type none is indeed in the result - for _, user := range res.Users { - if user.Username == "bob" { - require.Equal(t, user.LoginType, codersdk.LoginTypeNone, "bob should have login type none") - } - if user.Username == "charlie" { - require.Equal(t, user.LoginType, codersdk.LoginTypeGithub, "charlie should have login type github") - } - } + require.NoError(t, err) + require.Len(t, res.Users, 2) + require.ElementsMatch(t, filtered, res.Users) }) t.Run("DormantUserWithLoginTypeNone", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, nil) first := coderdtest.CreateFirstUser(t, client) - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() + ctx := testutil.Context(t, testutil.WaitLong) _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ Email: "bob@email.com", @@ -1989,18 +1973,17 @@ func TestGetUsers(t *testing.T) { require.NoError(t, err) _, err = client.UpdateUserStatus(ctx, "bob", codersdk.UserStatusSuspended) - require.NoError(t, err, "should set bob's status to dormant") + require.NoError(t, err) - // Test filtering by role res, err := client.Users(ctx, codersdk.UsersRequest{ Status: codersdk.UserStatusSuspended, LoginType: []codersdk.LoginType{codersdk.LoginTypeNone, codersdk.LoginTypeGithub}, }) - require.NoError(t, err, "should not error when filtering by role") - require.Len(t, res.Users, 1, "should find one dormant user with the specified login type") - require.Equal(t, res.Users[0].Username, "bob", "should return the correct dormant user with login type none") - require.Equal(t, res.Users[0].Status, codersdk.UserStatusSuspended, "bob should be in dormant status") - require.Equal(t, res.Users[0].LoginType, codersdk.LoginTypeNone, "bob should have login type none") + require.NoError(t, err) + require.Len(t, res.Users, 1) + require.Equal(t, res.Users[0].Username, "bob") + require.Equal(t, res.Users[0].Status, codersdk.UserStatusSuspended) + require.Equal(t, res.Users[0].LoginType, codersdk.LoginTypeNone) }) } From 38825d8fc2987bcff714c1b5922a41585a75faba Mon Sep 17 00:00:00 2001 From: Utsav Lal Date: Tue, 8 Apr 2025 00:24:34 -0400 Subject: [PATCH 09/12] remove redundant len check --- codersdk/users.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/codersdk/users.go b/codersdk/users.go index 3e37c6c6b487b..e32978c5a839e 100644 --- a/codersdk/users.go +++ b/codersdk/users.go @@ -751,10 +751,8 @@ func (c *Client) Users(ctx context.Context, req UsersRequest) (GetUsersResponse, if req.SearchQuery != "" { params = append(params, req.SearchQuery) } - if len(req.LoginType) > 0 { - for _, lt := range req.LoginType { - params = append(params, "login_type:"+string(lt)) - } + for _, lt := range req.LoginType { + params = append(params, "login_type:"+string(lt)) } q.Set("q", strings.Join(params, " ")) r.URL.RawQuery = q.Encode() From 16e9e45aba2bfdb82e5de6b82f59ac0a9882f1e2 Mon Sep 17 00:00:00 2001 From: Utsav Lal Date: Tue, 8 Apr 2025 00:24:48 -0400 Subject: [PATCH 10/12] add test to filter 1 user among many --- coderd/users_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/coderd/users_test.go b/coderd/users_test.go index bb3583dd75c0e..e32b6d0c5b927 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -1985,6 +1985,43 @@ func TestGetUsers(t *testing.T) { require.Equal(t, res.Users[0].Status, codersdk.UserStatusSuspended) require.Equal(t, res.Users[0].LoginType, codersdk.LoginTypeNone) }) + + t.Run("LoginTypeOidcFromMultipleUser", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{ + OIDCConfig: &coderd.OIDCConfig{ + AllowSignups: true, + }, + }) + first := coderdtest.CreateFirstUser(t, client) + ctx := testutil.Context(t, testutil.WaitLong) + + _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ + Email: "bob@email.com", + Username: "bob", + OrganizationIDs: []uuid.UUID{first.OrganizationID}, + UserLoginType: codersdk.LoginTypeOIDC, + }) + require.NoError(t, err) + + for i := range 5 { + _, err := client.CreateUserWithOrgs(ctx, codersdk.CreateUserRequestWithOrgs{ + Email: fmt.Sprintf("%d@coder.com", i), + Username: fmt.Sprintf("user%d", i), + OrganizationIDs: []uuid.UUID{first.OrganizationID}, + UserLoginType: codersdk.LoginTypeNone, + }) + require.NoError(t, err) + } + + res, err := client.Users(ctx, codersdk.UsersRequest{ + LoginType: []codersdk.LoginType{codersdk.LoginTypeOIDC}, + }) + require.NoError(t, err) + require.Len(t, res.Users, 1) + require.Equal(t, res.Users[0].Username, "bob") + require.Equal(t, res.Users[0].LoginType, codersdk.LoginTypeOIDC) + }) } func TestGetUsersPagination(t *testing.T) { From f524144a1a660aa54bad825692d9bb9a15f67b53 Mon Sep 17 00:00:00 2001 From: Utsav Lal Date: Tue, 8 Apr 2025 09:17:45 -0400 Subject: [PATCH 11/12] doc update --- docs/admin/users/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/admin/users/index.md b/docs/admin/users/index.md index cdc22e5877455..01b53edbca6d3 100644 --- a/docs/admin/users/index.md +++ b/docs/admin/users/index.md @@ -190,8 +190,8 @@ to use the Coder's filter query: `status:active last_seen_before:"2023-07-01T00:00:00Z"` - To find users who were created between January 1 and January 18, 2023: `created_before:"2023-01-18T00:00:00Z" created_after:"2023-01-01T23:59:59Z"` -- To find users who have login type as github and is a member: - `login_type:github role:member` +- To find users who login using github: + `login_type:github` The following filters are supported: @@ -205,4 +205,4 @@ The following filters are supported: the RFC3339Nano format. - `created_before` and `created_after` - The time a user was created. Uses the RFC3339Nano format. -- `login_type` - Represents the login type of the user. Refer here for all the roles [LoginType documentation](https://pkg.go.dev/github.com/coder/coder/v2/codersdk#LoginType) +- `login_type` - Represents the login type of the user. Refer to the [LoginType documentation](https://pkg.go.dev/github.com/coder/coder/v2/codersdk#LoginType) for a list of supported values From 6f2fc597fe115c95dffe61d212cf42b787e175c5 Mon Sep 17 00:00:00 2001 From: Utsav Lal Date: Tue, 8 Apr 2025 09:58:34 -0400 Subject: [PATCH 12/12] capitalize correctly --- docs/admin/users/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/admin/users/index.md b/docs/admin/users/index.md index 01b53edbca6d3..af26f4bb62a2b 100644 --- a/docs/admin/users/index.md +++ b/docs/admin/users/index.md @@ -190,7 +190,7 @@ to use the Coder's filter query: `status:active last_seen_before:"2023-07-01T00:00:00Z"` - To find users who were created between January 1 and January 18, 2023: `created_before:"2023-01-18T00:00:00Z" created_after:"2023-01-01T23:59:59Z"` -- To find users who login using github: +- To find users who login using Github: `login_type:github` The following filters are supported: pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy