From 0e17d0005a98ccbf92139961ef702ef03208ffd3 Mon Sep 17 00:00:00 2001 From: Marco A Date: Wed, 10 Dec 2025 13:13:56 +0100 Subject: [PATCH] fix: Force v2 permission checks on user listing # Which Problems Are Solved When the feature flag for enabling permission checks v2 is disabled, a user without permission could list users across instances and get the total number of users available. # How the Problems Are Solved Disregard the state of the feature flag and always enforce permission checks v2 on v2 APIs. --------- Co-authored-by: Livio Spring (cherry picked from commit 826039c6208fe71df57b3a94c982b5ac5b0af12c) --- .../user/v2/integration_test/query_test.go | 1037 +---------------- .../v2beta/integration_test/query_test.go | 144 +-- internal/query/user.go | 15 +- internal/query/user_test.go | 124 -- 4 files changed, 44 insertions(+), 1276 deletions(-) diff --git a/internal/api/grpc/user/v2/integration_test/query_test.go b/internal/api/grpc/user/v2/integration_test/query_test.go index 077fb34532b..a10e2e4d9f3 100644 --- a/internal/api/grpc/user/v2/integration_test/query_test.go +++ b/internal/api/grpc/user/v2/integration_test/query_test.go @@ -5,7 +5,6 @@ package user_test import ( "context" "encoding/base64" - "fmt" "slices" "testing" "time" @@ -384,952 +383,6 @@ func createUserWithUserName(ctx context.Context, instance *integration.Instance, } func TestServer_ListUsers(t *testing.T) { - orgResp := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), integration.Email()) - type args struct { - ctx context.Context - req *user.ListUsersRequest - dep func(ctx context.Context, request *user.ListUsersRequest) userAttrs - } - tt := []struct { - name string - args args - want *user.ListUsersResponse - wantErr bool - }{ - { - name: "list user by id, no permission machine user", - args: args{ - UserCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - info := createUser(ctx, Instance, orgResp.OrganizationId, false) - request.Queries = append(request.Queries, InUserIDsQuery([]string{info.UserID})) - return []userAttr{} - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 0, - Timestamp: timestamppb.Now(), - }, - SortingColumn: 0, - Result: []*user.User{}, - }, - }, - { - name: "list user by id, no permission human user", - args: func() args { - info := createUser(IamCTX, Instance, orgResp.OrganizationId, true) - // create session to get token - userID := info.UserID - createResp, err := Instance.Client.SessionV2.CreateSession(IamCTX, &session.CreateSessionRequest{ - Checks: &session.Checks{ - User: &session.CheckUser{ - Search: &session.CheckUser_UserId{UserId: userID}, - }, - Password: &session.CheckPassword{ - Password: integration.UserPassword, - }, - }, - }) - if err != nil { - require.NoError(t, err) - } - // use token to get ctx - HumanCTX := integration.WithAuthorizationToken(IamCTX, createResp.GetSessionToken()) - return args{ - HumanCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - return []userAttr{info} - }, - } - }(), - want: &user.ListUsersResponse{ // human user should return itself when calling ListUsers() even if it has no permissions - Details: &object.ListDetails{ - TotalResult: 1, - Timestamp: timestamppb.Now(), - }, - SortingColumn: 0, - Result: []*user.User{ - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - PasswordChangeRequired: true, - PasswordChanged: timestamppb.Now(), - }, - }, - }, - }, - }, - }, - { - name: "list user by id, ok", - args: args{ - IamCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - info := createUser(ctx, Instance, orgResp.OrganizationId, false) - request.Queries = []*user.SearchQuery{} - request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId)) - request.Queries = append(request.Queries, InUserIDsQuery([]string{info.UserID})) - return []userAttr{info} - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 1, - Timestamp: timestamppb.Now(), - }, - SortingColumn: 0, - Result: []*user.User{ - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, - }, - }, - }, - { - name: "list user by id, passwordChangeRequired, ok", - args: args{ - IamCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - info := createUser(ctx, Instance, orgResp.OrganizationId, true) - request.Queries = []*user.SearchQuery{} - request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId)) - request.Queries = append(request.Queries, InUserIDsQuery([]string{info.UserID})) - return []userAttr{info} - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 1, - Timestamp: timestamppb.Now(), - }, - SortingColumn: 0, - Result: []*user.User{ - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - PasswordChangeRequired: true, - PasswordChanged: timestamppb.Now(), - }, - }, - }, - }, - }, - }, - { - name: "list user by id and meta key multiple, ok", - args: args{ - IamCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - infos := createUsers(ctx, Instance, orgResp.OrganizationId, 3, false) - request.Queries = []*user.SearchQuery{} - request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId)) - request.Queries = append(request.Queries, InUserIDsQuery(infos.userIDs())) - - Instance.SetUserMetadata(ctx, infos[0].UserID, "my meta", "my value 1") - Instance.SetUserMetadata(ctx, infos[1].UserID, "my meta 2", "my value 3") - Instance.SetUserMetadata(ctx, infos[2].UserID, "my meta", "my value 2") - - request.Queries = append(request.Queries, MetadataKeyContainsQuery("my meta")) - request.SortingColumn = user.UserFieldName_USER_FIELD_NAME_CREATION_DATE - return infos - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 3, - Timestamp: timestamppb.Now(), - }, - SortingColumn: user.UserFieldName_USER_FIELD_NAME_CREATION_DATE, - Result: []*user.User{ - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, - }, - }, - }, - { - // https://github.com/zitadel/zitadel/issues/10825 - name: "list user with metadata by IDs with offset and limit, ok", - args: args{ - IamCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - infos := createUsers(ctx, Instance, orgResp.OrganizationId, 3, false) - request.Queries = []*user.SearchQuery{} - request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId)) - request.Queries = append(request.Queries, InUserIDsQuery(infos.userIDs())) - - // With the original bug, this would multiply the "TotalResult" by 2. - for _, user := range infos { - for i := 0; i < 2; i++ { - Instance.SetUserMetadata(ctx, user.UserID, fmt.Sprintf("key-%d", i), fmt.Sprintf("value-%d", i)) - } - } - infos = infos[1:] // prevent panic in user ID setting below - - request.SortingColumn = user.UserFieldName_USER_FIELD_NAME_CREATION_DATE - request.Query = &object.ListQuery{ - Offset: 1, - Limit: 2, - } - return infos - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 3, - Timestamp: timestamppb.Now(), - }, - SortingColumn: user.UserFieldName_USER_FIELD_NAME_CREATION_DATE, - Result: []*user.User{ - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, - }, - }, - }, - { - name: "list user by username, ok", - args: args{ - IamCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - info := createUser(ctx, Instance, orgResp.OrganizationId, false) - request.Queries = []*user.SearchQuery{} - request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId)) - request.Queries = append(request.Queries, UsernameQuery(info.Username)) - - request.SortingColumn = user.UserFieldName_USER_FIELD_NAME_CREATION_DATE - return []userAttr{info} - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 1, - Timestamp: timestamppb.Now(), - }, - SortingColumn: user.UserFieldName_USER_FIELD_NAME_CREATION_DATE, - Result: []*user.User{ - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, - }, - }, - }, - { - name: "list user in emails, ok", - args: args{ - IamCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - info := createUser(ctx, Instance, orgResp.OrganizationId, false) - request.Queries = []*user.SearchQuery{} - request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId)) - request.Queries = append(request.Queries, InUserEmailsQuery([]string{info.Username})) - request.SortingColumn = user.UserFieldName_USER_FIELD_NAME_CREATION_DATE - return []userAttr{info} - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 1, - Timestamp: timestamppb.Now(), - }, - SortingColumn: user.UserFieldName_USER_FIELD_NAME_CREATION_DATE, - Result: []*user.User{ - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, - }, - }, - }, - { - name: "list user by emails and meta value multiple, ok", - args: args{ - IamCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - infos := createUsers(ctx, Instance, orgResp.OrganizationId, 3, false) - request.Queries = []*user.SearchQuery{} - request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId)) - request.Queries = append(request.Queries, InUserEmailsQuery(infos.emails())) - - Instance.SetUserMetadata(ctx, infos[0].UserID, "my meta 1", "my value") - Instance.SetUserMetadata(ctx, infos[0].UserID, "my meta 2", "my value") - Instance.SetUserMetadata(ctx, infos[1].UserID, "my meta 2", "my value") - Instance.SetUserMetadata(ctx, infos[2].UserID, "my meta", "my value") - - request.Queries = append(request.Queries, MetadataValueQuery("my value")) - request.SortingColumn = user.UserFieldName_USER_FIELD_NAME_CREATION_DATE - - return infos - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 3, - Timestamp: timestamppb.Now(), - }, - SortingColumn: user.UserFieldName_USER_FIELD_NAME_CREATION_DATE, - Result: []*user.User{ - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, - }, - }, - }, - { - // https://github.com/zitadel/zitadel/issues/10825 - name: "list user with metadata by emails with offset and limit, ok", - args: args{ - IamCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - infos := createUsers(ctx, Instance, orgResp.OrganizationId, 3, false) - request.Queries = []*user.SearchQuery{} - request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId)) - request.Queries = append(request.Queries, InUserEmailsQuery(infos.emails())) - - // With the original bug, this would multiply the "TotalResult" by 2. - for _, user := range infos { - for i := 0; i < 2; i++ { - Instance.SetUserMetadata(ctx, user.UserID, fmt.Sprintf("key-%d", i), fmt.Sprintf("value-%d", i)) - } - } - infos = infos[1:] // prevent panic in user ID setting below - - request.SortingColumn = user.UserFieldName_USER_FIELD_NAME_CREATION_DATE - request.Query = &object.ListQuery{ - Offset: 1, - Limit: 2, - } - return infos - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 3, - Timestamp: timestamppb.Now(), - }, - SortingColumn: user.UserFieldName_USER_FIELD_NAME_CREATION_DATE, - Result: []*user.User{ - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, - }, - }, - }, - { - name: "list user in emails no found, ok", - args: args{ - IamCTX, - &user.ListUsersRequest{ - Queries: []*user.SearchQuery{ - OrganizationIdQuery(orgResp.OrganizationId), - InUserEmailsQuery([]string{"notfound"}), - }, - }, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - return []userAttr{} - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 0, - Timestamp: timestamppb.Now(), - }, - SortingColumn: 0, - Result: []*user.User{}, - }, - }, - { - name: "list user phone, ok", - args: args{ - IamCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - info := createUser(ctx, Instance, orgResp.OrganizationId, false) - request.Queries = []*user.SearchQuery{} - request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId)) - request.Queries = append(request.Queries, PhoneQuery(info.Phone)) - return []userAttr{info} - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 1, - Timestamp: timestamppb.Now(), - }, - SortingColumn: 0, - Result: []*user.User{ - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, - }, - }, - }, - { - name: "list user in emails no found, ok", - args: args{ - IamCTX, - &user.ListUsersRequest{ - Queries: []*user.SearchQuery{ - OrganizationIdQuery(orgResp.OrganizationId), - InUserEmailsQuery([]string{"notfound"}), - }, - }, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - return []userAttr{} - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 0, - Timestamp: timestamppb.Now(), - }, - SortingColumn: 0, - Result: []*user.User{}, - }, - }, - { - name: "list user resourceowner multiple, ok", - args: args{ - IamCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - orgResp := Instance.CreateOrganization(ctx, integration.OrganizationName(), integration.Email()) - - infos := createUsers(ctx, Instance, orgResp.OrganizationId, 3, false) - request.Queries = []*user.SearchQuery{} - request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId)) - request.Queries = append(request.Queries, InUserEmailsQuery(infos.emails())) - request.SortingColumn = user.UserFieldName_USER_FIELD_NAME_CREATION_DATE - return infos - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 3, - Timestamp: timestamppb.Now(), - }, - SortingColumn: user.UserFieldName_USER_FIELD_NAME_CREATION_DATE, - Result: []*user.User{ - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, - }, - }, - }, - { - name: "list user with org query", - args: args{ - IamCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - orgRespForOrgTests := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), integration.Email()) - info := createUser(ctx, Instance, orgRespForOrgTests.OrganizationId, false) - request.Queries = []*user.SearchQuery{} - request.Queries = append(request.Queries, OrganizationIdQuery(orgRespForOrgTests.OrganizationId)) - request.SortingColumn = user.UserFieldName_USER_FIELD_NAME_CREATION_DATE - return []userAttr{info, {}} - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 2, - Timestamp: timestamppb.Now(), - }, - SortingColumn: user.UserFieldName_USER_FIELD_NAME_CREATION_DATE, - Result: []*user.User{ - { - State: user.UserState_USER_STATE_ACTIVE, - Type: &user.User_Human{ - Human: &user.HumanUser{ - Profile: &user.HumanProfile{ - GivenName: "Mickey", - FamilyName: "Mouse", - NickName: gu.Ptr("Mickey"), - DisplayName: gu.Ptr("Mickey Mouse"), - PreferredLanguage: gu.Ptr("nl"), - Gender: user.Gender_GENDER_MALE.Enum(), - }, - Email: &user.HumanEmail{ - IsVerified: true, - }, - Phone: &user.HumanPhone{ - IsVerified: true, - }, - }, - }, - }, - // this is the admin of the org craated in Instance.CreateOrganization() - nil, - }, - }, - }, - { - name: "list user with wrong org query", - args: args{ - IamCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - orgRespForOrgTests := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), integration.Email()) - orgRespForOrgTests2 := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), integration.Email()) - createUser(ctx, Instance, orgRespForOrgTests.OrganizationId, false) - request.Queries = []*user.SearchQuery{} - request.Queries = append(request.Queries, OrganizationIdQuery(orgRespForOrgTests2.OrganizationId)) - return []userAttr{{}} - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 0, - Timestamp: timestamppb.Now(), - }, - SortingColumn: 0, - Result: []*user.User{ - // this is the admin of the org craated in Instance.CreateOrganization() - nil, - }, - }, - }, - { - name: "when no users matching meta key should return empty list", - args: args{ - IamCTX, - &user.ListUsersRequest{}, - func(ctx context.Context, request *user.ListUsersRequest) userAttrs { - createUser(ctx, Instance, orgResp.OrganizationId, false) - request.Queries = []*user.SearchQuery{} - request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId)) - request.Queries = append(request.Queries, MetadataKeyContainsQuery("some non-existent meta")) - - request.SortingColumn = user.UserFieldName_USER_FIELD_NAME_CREATION_DATE - return []userAttr{} - }, - }, - want: &user.ListUsersResponse{ - Details: &object.ListDetails{ - TotalResult: 0, - Timestamp: timestamppb.Now(), - }, - SortingColumn: user.UserFieldName_USER_FIELD_NAME_CREATION_DATE, - Result: []*user.User{}, - }, - }, - } - for _, tc := range tt { - t.Run(tc.name, func(t1 *testing.T) { - infos := tc.args.dep(IamCTX, tc.args.req) - - retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.args.ctx, time.Minute) - require.EventuallyWithT(t1, func(ttt *assert.CollectT) { - got, err := Client.ListUsers(tc.args.ctx, tc.args.req) - if tc.wantErr { - require.Error(ttt, err) - return - } - require.NoError(ttt, err) - - // always only give back dependency infos which are required for the response - require.Len(ttt, tc.want.Result, len(infos)) - if assert.Len(ttt, got.Result, len(tc.want.Result)) { - tc.want.Details.TotalResult = got.Details.TotalResult - - // fill in userid and username as it is generated - for i := range infos { - if tc.want.Result[i] == nil { - continue - } - tc.want.Result[i].UserId = infos[i].UserID - tc.want.Result[i].Username = infos[i].Username - tc.want.Result[i].PreferredLoginName = infos[i].Username - tc.want.Result[i].LoginNames = []string{infos[i].Username} - if human := tc.want.Result[i].GetHuman(); human != nil { - human.Email.Email = infos[i].Username - human.Phone.Phone = infos[i].Phone - if tc.want.Result[i].GetHuman().GetPasswordChanged() != nil { - human.PasswordChanged = infos[i].Changed - } - } - tc.want.Result[i].Details = infos[i].Details - } - for i := range tc.want.Result { - if tc.want.Result[i] == nil { - continue - } - assert.EqualExportedValues(ttt, got.Result[i], tc.want.Result[i]) - } - } - integration.AssertListDetails(ttt, tc.want, got) - }, retryDuration, tick, "timeout waiting for expected user result") - }) - } -} - -func TestServer_ListUsers_PermissionsV2(t *testing.T) { - ensureFeaturePermissionV2Enabled(t, InstancePermissionV2) iamOwnerCtx := InstancePermissionV2.WithAuthorizationToken(OrgCTX, integration.UserTypeIAMOwner) orgResp := InstancePermissionV2.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email()) @@ -2033,12 +1086,12 @@ func TestServer_ListUsers_PermissionsV2(t *testing.T) { }, want: &user.ListUsersResponse{ Details: &object.ListDetails{ - TotalResult: 0, + TotalResult: 1, Timestamp: timestamppb.Now(), }, SortingColumn: 0, Result: []*user.User{ - // this is the admin of the org craated in InstancePermissionV2.CreateOrganization() + // this is the admin of the org created in InstancePermissionV2.CreateOrganization() nil, }, }, @@ -2084,8 +1137,6 @@ func TestServer_ListUsers_PermissionsV2(t *testing.T) { // always only give back dependency infos which are required for the response require.Len(ttt, tc.want.Result, len(infos)) if assert.Len(ttt, got.Result, len(tc.want.Result)) { - tc.want.Details.TotalResult = got.Details.TotalResult - // fill in userid and username as it is generated for i := range infos { if tc.want.Result[i] == nil { @@ -2118,90 +1169,6 @@ func TestServer_ListUsers_PermissionsV2(t *testing.T) { } func TestServer_SystemUsers_ListUsers(t *testing.T) { - - org1 := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), integration.Email()) - org2 := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), "org2@zitadel.com") - org3 := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), integration.Email()) - _ = createUserWithUserName(IamCTX, Instance, "Test_SystemUsers_ListUser1@zitadel.com", org1.OrganizationId, false) - _ = createUserWithUserName(IamCTX, Instance, "Test_SystemUsers_ListUser2@zitadel.com", org2.OrganizationId, false) - _ = createUserWithUserName(IamCTX, Instance, "Test_SystemUsers_ListUser3@zitadel.com", org3.OrganizationId, false) - - tests := []struct { - name string - ctx context.Context - req *user.ListUsersRequest - expectedFoundUsernames []string - checkNumberOfUsersReturned bool - }{ - { - name: "list users with neccessary permissions", - ctx: SystemCTX, - req: &user.ListUsersRequest{}, - // the number of users returned will vary from test run to test run, - // so just check the system user gets back users from different orgs whcih it is not a memeber of - checkNumberOfUsersReturned: false, - expectedFoundUsernames: []string{"Test_SystemUsers_ListUser1@zitadel.com", "Test_SystemUsers_ListUser2@zitadel.com", "Test_SystemUsers_ListUser3@zitadel.com"}, - }, - { - name: "list users without neccessary permissions", - ctx: SystemUserWithNoPermissionsCTX, - req: &user.ListUsersRequest{}, - // check no users returned - checkNumberOfUsersReturned: true, - }, - { - name: "list users with neccessary permissions specifying org", - req: &user.ListUsersRequest{ - Queries: []*user.SearchQuery{OrganizationIdQuery(org2.OrganizationId)}, - }, - ctx: SystemCTX, - expectedFoundUsernames: []string{"Test_SystemUsers_ListUser2@zitadel.com", "org2@zitadel.com"}, - checkNumberOfUsersReturned: true, - }, - { - name: "list users without neccessary permissions specifying org", - req: &user.ListUsersRequest{ - Queries: []*user.SearchQuery{OrganizationIdQuery(org2.OrganizationId)}, - }, - ctx: SystemUserWithNoPermissionsCTX, - // check no users returned - checkNumberOfUsersReturned: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - - retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.ctx, 1*time.Minute) - require.EventuallyWithT(t, func(ttt *assert.CollectT) { - got, err := Client.ListUsers(tt.ctx, tt.req) - require.NoError(ttt, err) - - if tt.checkNumberOfUsersReturned { - require.Equal(t, len(tt.expectedFoundUsernames), len(got.Result)) - } - - if tt.expectedFoundUsernames != nil { - for _, user := range got.Result { - for i, username := range tt.expectedFoundUsernames { - if username == user.Username { - tt.expectedFoundUsernames = tt.expectedFoundUsernames[i+1:] - break - } - } - if len(tt.expectedFoundUsernames) == 0 { - return - } - } - require.FailNow(t, "unable to find all users with specified usernames") - } - }, retryDuration, tick, "timeout waiting for expected user result") - }) - } -} - -func TestServer_SystemUsers_ListUsers_PermissionsV2(t *testing.T) { - ensureFeaturePermissionV2Enabled(t, InstancePermissionV2) iamOwnerCtx := InstancePermissionV2.WithAuthorizationToken(OrgCTX, integration.UserTypeIAMOwner) org1 := InstancePermissionV2.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email()) diff --git a/internal/api/grpc/user/v2beta/integration_test/query_test.go b/internal/api/grpc/user/v2beta/integration_test/query_test.go index 2d3fc92270c..f49a0018402 100644 --- a/internal/api/grpc/user/v2beta/integration_test/query_test.go +++ b/internal/api/grpc/user/v2beta/integration_test/query_test.go @@ -4,7 +4,6 @@ package user_test import ( "context" - "errors" "slices" "testing" "time" @@ -15,7 +14,6 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "github.com/zitadel/zitadel/internal/integration" - "github.com/zitadel/zitadel/pkg/grpc/feature/v2" "github.com/zitadel/zitadel/pkg/grpc/object/v2" object_v2beta "github.com/zitadel/zitadel/pkg/grpc/object/v2beta" "github.com/zitadel/zitadel/pkg/grpc/session/v2" @@ -30,55 +28,6 @@ func detailsV2ToV2beta(obj *object.Details) *object_v2beta.Details { } } -var ( - permissionCheckV2SetFlagInital bool - permissionCheckV2SetFlag bool -) - -type permissionCheckV2SettingsStruct struct { - TestNamePrependString string - SetFlag bool -} - -var permissionCheckV2Settings []permissionCheckV2SettingsStruct = []permissionCheckV2SettingsStruct{ - { - SetFlag: false, - TestNamePrependString: "permission_check_v2 IS NOT SET" + " ", - }, - { - SetFlag: true, - TestNamePrependString: "permission_check_v2 IS SET" + " ", - }, -} - -func setPermissionCheckV2Flag(t *testing.T, setFlag bool) { - if permissionCheckV2SetFlagInital && permissionCheckV2SetFlag == setFlag { - return - } - - _, err := Instance.Client.FeatureV2.SetInstanceFeatures(IamCTX, &feature.SetInstanceFeaturesRequest{ - PermissionCheckV2: &setFlag, - }) - require.NoError(t, err) - - var flagSet bool - for i := 0; !flagSet || i < 6; i++ { - res, err := Instance.Client.FeatureV2.GetInstanceFeatures(IamCTX, &feature.GetInstanceFeaturesRequest{}) - require.NoError(t, err) - if res.PermissionCheckV2.Enabled == setFlag { - flagSet = true - continue - } - time.Sleep(10 * time.Second) - } - - if !flagSet { - require.NoError(t, errors.New("unable to set permission_check_v2 flag")) - } - permissionCheckV2SetFlagInital = true - permissionCheckV2SetFlag = setFlag -} - func TestServer_GetUserByID(t *testing.T) { orgResp := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), integration.Email()) type args struct { @@ -431,11 +380,6 @@ func createUser(ctx context.Context, orgID string, passwordChangeRequired bool) } func TestServer_ListUsers(t *testing.T) { - t.Cleanup(func() { - _, err := Instance.Client.FeatureV2.ResetInstanceFeatures(IamCTX, &feature.ResetInstanceFeaturesRequest{}) - require.NoError(t, err) - }) - orgResp := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), integration.Email()) type args struct { ctx context.Context @@ -1111,7 +1055,6 @@ func TestServer_ListUsers(t *testing.T) { func(ctx context.Context, request *user_v2beta.ListUsersRequest) userAttrs { orgRespForOrgTests := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), integration.Email()) orgRespForOrgTests2 := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), integration.Email()) - // info := createUser(ctx, orgRespForOrgTests.OrganizationId, false) createUser(ctx, orgRespForOrgTests.OrganizationId, false) request.Queries = []*user_v2beta.SearchQuery{} request.Queries = append(request.Queries, OrganizationIdQuery(orgRespForOrgTests2.OrganizationId)) @@ -1120,7 +1063,7 @@ func TestServer_ListUsers(t *testing.T) { }, want: &user_v2beta.ListUsersResponse{ Details: &object_v2beta.ListDetails{ - TotalResult: 0, + TotalResult: 1, Timestamp: timestamppb.Now(), }, SortingColumn: 0, @@ -1131,58 +1074,51 @@ func TestServer_ListUsers(t *testing.T) { }, }, } - for _, f := range permissionCheckV2Settings { - for _, tc := range tt { - t.Run(f.TestNamePrependString+tc.name, func(t1 *testing.T) { - setPermissionCheckV2Flag(t1, f.SetFlag) - infos := tc.args.dep(IamCTX, tc.args.req) + for _, tc := range tt { + t.Run(tc.name, func(t1 *testing.T) { + infos := tc.args.dep(IamCTX, tc.args.req) - // retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.args.ctx, 10*time.Minute) - retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.args.ctx, time.Minute) - require.EventuallyWithT(t1, func(ttt *assert.CollectT) { - got, err := Client.ListUsers(tc.args.ctx, tc.args.req) - if tc.wantErr { - require.Error(ttt, err) - return - } - require.NoError(ttt, err) + retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.args.ctx, time.Minute) + require.EventuallyWithT(t1, func(ttt *assert.CollectT) { + got, err := Client.ListUsers(tc.args.ctx, tc.args.req) + if tc.wantErr { + require.Error(ttt, err) + return + } + require.NoError(ttt, err) - // always only give back dependency infos which are required for the response - require.Len(ttt, tc.want.Result, len(infos)) - // always first check length, otherwise its failed anyway - if assert.Len(ttt, got.Result, len(tc.want.Result)) { - // totalResult is unrelated to the tests here so gets carried over, can vary from the count of results due to permissions - tc.want.Details.TotalResult = got.Details.TotalResult - - // fill in userid and username as it is generated - for i := range infos { - if tc.want.Result[i] == nil { - continue - } - tc.want.Result[i].UserId = infos[i].UserID - tc.want.Result[i].Username = infos[i].Username - tc.want.Result[i].PreferredLoginName = infos[i].Username - tc.want.Result[i].LoginNames = []string{infos[i].Username} - if human := tc.want.Result[i].GetHuman(); human != nil { - human.Email.Email = infos[i].Username - human.Phone.Phone = infos[i].Phone - if tc.want.Result[i].GetHuman().GetPasswordChanged() != nil { - human.PasswordChanged = infos[i].Changed - } - } - tc.want.Result[i].Details = detailsV2ToV2beta(infos[i].Details) + // always only give back dependency infos which are required for the response + require.Len(ttt, tc.want.Result, len(infos)) + // always first check length, otherwise its failed anyway + if assert.Len(ttt, got.Result, len(tc.want.Result)) { + // fill in userid and username as it is generated + for i := range infos { + if tc.want.Result[i] == nil { + continue } - for i := range tc.want.Result { - if tc.want.Result[i] == nil { - continue + tc.want.Result[i].UserId = infos[i].UserID + tc.want.Result[i].Username = infos[i].Username + tc.want.Result[i].PreferredLoginName = infos[i].Username + tc.want.Result[i].LoginNames = []string{infos[i].Username} + if human := tc.want.Result[i].GetHuman(); human != nil { + human.Email.Email = infos[i].Username + human.Phone.Phone = infos[i].Phone + if tc.want.Result[i].GetHuman().GetPasswordChanged() != nil { + human.PasswordChanged = infos[i].Changed } - assert.EqualExportedValues(ttt, got.Result[i], tc.want.Result[i]) } + tc.want.Result[i].Details = detailsV2ToV2beta(infos[i].Details) } - integration.AssertListDetails(ttt, tc.want, got) - }, retryDuration, tick, "timeout waiting for expected user result") - }) - } + for i := range tc.want.Result { + if tc.want.Result[i] == nil { + continue + } + assert.EqualExportedValues(ttt, got.Result[i], tc.want.Result[i]) + } + } + integration.AssertListDetails(ttt, tc.want, got) + }, retryDuration, tick, "timeout waiting for expected user result") + }) } } diff --git a/internal/query/user.go b/internal/query/user.go index 742f82cc111..ed18e48a245 100644 --- a/internal/query/user.go +++ b/internal/query/user.go @@ -5,7 +5,6 @@ import ( "database/sql" _ "embed" "errors" - "slices" "strings" "time" @@ -124,14 +123,6 @@ type NotifyUser struct { PasswordSet bool } -func usersCheckPermission(ctx context.Context, users *Users, permissionCheck domain.PermissionCheck) { - users.Users = slices.DeleteFunc(users.Users, - func(user *User) bool { - return userCheckPermission(ctx, user.ResourceOwner, user.ID, permissionCheck) != nil - }, - ) -} - func userPermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabled bool, filters []SearchQuery) sq.SelectBuilder { return userPermissionCheckV2WithCustomColumns(ctx, query, enabled, filters, UserResourceOwnerCol, UserIDCol) } @@ -619,14 +610,12 @@ func (q *Queries) CountUsers(ctx context.Context, queries *UserSearchQueries) (c } func (q *Queries) SearchUsers(ctx context.Context, queries *UserSearchQueries, permissionCheck domain.PermissionCheck) (*Users, error) { - permissionCheckV2 := PermissionV2(ctx, permissionCheck) + permissionCheckV2 := permissionCheck != nil users, err := q.searchUsers(ctx, queries, permissionCheckV2) if err != nil { return nil, err } - if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 { - usersCheckPermission(ctx, users, permissionCheck) - } + return users, nil } diff --git a/internal/query/user_test.go b/internal/query/user_test.go index e70bd98185c..7a32ee12dac 100644 --- a/internal/query/user_test.go +++ b/internal/query/user_test.go @@ -11,7 +11,6 @@ import ( sq "github.com/Masterminds/squirrel" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "golang.org/x/text/language" "github.com/zitadel/zitadel/internal/api/authz" @@ -20,129 +19,6 @@ import ( "github.com/zitadel/zitadel/internal/zerrors" ) -func TestUser_usersCheckPermission(t *testing.T) { - type want struct { - users []*User - } - tests := []struct { - name string - want want - users *Users - permissions []string - }{ - { - "permissions for all users", - want{ - users: []*User{ - {ID: "first"}, {ID: "second"}, {ID: "third"}, - }, - }, - &Users{ - Users: []*User{ - {ID: "first"}, {ID: "second"}, {ID: "third"}, - }, - }, - []string{"first", "second", "third"}, - }, - { - "permissions for one user, first", - want{ - users: []*User{ - {ID: "first"}, - }, - }, - &Users{ - Users: []*User{ - {ID: "first"}, {ID: "second"}, {ID: "third"}, - }, - }, - []string{"first"}, - }, - { - "permissions for one user, second", - want{ - users: []*User{ - {ID: "second"}, - }, - }, - &Users{ - Users: []*User{ - {ID: "first"}, {ID: "second"}, {ID: "third"}, - }, - }, - []string{"second"}, - }, - { - "permissions for one user, third", - want{ - users: []*User{ - {ID: "third"}, - }, - }, - &Users{ - Users: []*User{ - {ID: "first"}, {ID: "second"}, {ID: "third"}, - }, - }, - []string{"third"}, - }, - { - "permissions for two users, first", - want{ - users: []*User{ - {ID: "first"}, {ID: "third"}, - }, - }, - &Users{ - Users: []*User{ - {ID: "first"}, {ID: "second"}, {ID: "third"}, - }, - }, - []string{"first", "third"}, - }, - { - "permissions for two users, second", - want{ - users: []*User{ - {ID: "second"}, {ID: "third"}, - }, - }, - &Users{ - Users: []*User{ - {ID: "first"}, {ID: "second"}, {ID: "third"}, - }, - }, - []string{"second", "third"}, - }, - { - "no permissions", - want{ - users: []*User{}, - }, - &Users{ - Users: []*User{ - {ID: "first"}, {ID: "second"}, {ID: "third"}, - }, - }, - []string{}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - checkPermission := func(ctx context.Context, permission, orgID, resourceID string) (err error) { - for _, perm := range tt.permissions { - if resourceID == perm { - return nil - } - } - return errors.New("failed") - } - usersCheckPermission(context.Background(), tt.users, checkPermission) - require.Equal(t, tt.want.users, tt.users.Users) - }) - } -} - func TestUser_userCheckPermission(t *testing.T) { type args struct { ctxData string