mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 13:52:51 +00:00
# Which Problems Are Solved Flakiness in integration tests because of eventual consistentcy. # How the Problems Are Solved Split tests related to feature flags and other eventual consistent resources. # Additional Changes None # Additional Context None Co-authored-by: Marco A. <[email protected]>
2378 lines
73 KiB
Go
2378 lines
73 KiB
Go
//go:build integration
|
|
|
|
package user_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/muhlemmer/gu"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
|
|
"github.com/zitadel/zitadel/internal/integration"
|
|
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
|
v2 "github.com/zitadel/zitadel/pkg/grpc/metadata/v2"
|
|
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
|
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
|
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
|
)
|
|
|
|
func TestServer_GetUserByID(t *testing.T) {
|
|
orgResp := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), integration.Email())
|
|
type args struct {
|
|
ctx context.Context
|
|
req *user.GetUserByIDRequest
|
|
dep func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *user.GetUserByIDResponse
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "user by ID, no id provided",
|
|
args: args{
|
|
IamCTX,
|
|
&user.GetUserByIDRequest{
|
|
UserId: "",
|
|
},
|
|
func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr {
|
|
return nil
|
|
},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "user by ID, not found",
|
|
args: args{
|
|
IamCTX,
|
|
&user.GetUserByIDRequest{
|
|
UserId: "unknown",
|
|
},
|
|
func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr {
|
|
return nil
|
|
},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "user by ID, ok",
|
|
args: args{
|
|
IamCTX,
|
|
&user.GetUserByIDRequest{},
|
|
func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr {
|
|
info := createUser(ctx, Instance, orgResp.OrganizationId, false)
|
|
request.UserId = info.UserID
|
|
return &info
|
|
},
|
|
},
|
|
want: &user.GetUserByIDResponse{
|
|
User: &user.User{
|
|
State: user.UserState_USER_STATE_ACTIVE,
|
|
Username: "",
|
|
LoginNames: nil,
|
|
PreferredLoginName: "",
|
|
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(),
|
|
AvatarUrl: "",
|
|
},
|
|
Email: &user.HumanEmail{
|
|
IsVerified: true,
|
|
},
|
|
Phone: &user.HumanPhone{
|
|
IsVerified: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Details: &object.Details{
|
|
ChangeDate: timestamppb.Now(),
|
|
CreationDate: timestamppb.Now(),
|
|
ResourceOwner: orgResp.OrganizationId,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "user by ID, passwordChangeRequired, ok",
|
|
args: args{
|
|
IamCTX,
|
|
&user.GetUserByIDRequest{},
|
|
func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr {
|
|
info := createUser(ctx, Instance, orgResp.OrganizationId, true)
|
|
request.UserId = info.UserID
|
|
return &info
|
|
},
|
|
},
|
|
want: &user.GetUserByIDResponse{
|
|
User: &user.User{
|
|
State: user.UserState_USER_STATE_ACTIVE,
|
|
Username: "",
|
|
LoginNames: nil,
|
|
PreferredLoginName: "",
|
|
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(),
|
|
AvatarUrl: "",
|
|
},
|
|
Email: &user.HumanEmail{
|
|
IsVerified: true,
|
|
},
|
|
Phone: &user.HumanPhone{
|
|
IsVerified: true,
|
|
},
|
|
PasswordChangeRequired: true,
|
|
PasswordChanged: timestamppb.Now(),
|
|
},
|
|
},
|
|
},
|
|
Details: &object.Details{
|
|
ChangeDate: timestamppb.Now(),
|
|
CreationDate: timestamppb.Now(),
|
|
ResourceOwner: orgResp.OrganizationId,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
userAttr := tt.args.dep(IamCTX, tt.args.req)
|
|
|
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.args.ctx, time.Minute)
|
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
|
got, err := Client.GetUserByID(tt.args.ctx, tt.args.req)
|
|
if tt.wantErr {
|
|
assert.Error(ttt, err)
|
|
return
|
|
}
|
|
if !assert.NoError(ttt, err) {
|
|
return
|
|
}
|
|
|
|
tt.want.User.Details = userAttr.Details
|
|
tt.want.User.UserId = userAttr.UserID
|
|
tt.want.User.Username = userAttr.Username
|
|
tt.want.User.PreferredLoginName = userAttr.Username
|
|
tt.want.User.LoginNames = []string{userAttr.Username}
|
|
if human := tt.want.User.GetHuman(); human != nil {
|
|
human.Email.Email = userAttr.Username
|
|
human.Phone.Phone = userAttr.Phone
|
|
if tt.want.User.GetHuman().GetPasswordChanged() != nil {
|
|
human.PasswordChanged = userAttr.Changed
|
|
}
|
|
}
|
|
assert.EqualExportedValues(ttt, tt.want.User, got.User)
|
|
integration.AssertDetails(ttt, tt.want, got)
|
|
}, retryDuration, tick)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestServer_GetUserByID_Permission(t *testing.T) {
|
|
newOrgOwnerEmail := integration.Email()
|
|
newOrg := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), newOrgOwnerEmail)
|
|
newUserID := newOrg.CreatedAdmins[0].GetUserId()
|
|
type args struct {
|
|
ctx context.Context
|
|
req *user.GetUserByIDRequest
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want *user.GetUserByIDResponse
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "System, ok",
|
|
args: args{
|
|
SystemCTX,
|
|
&user.GetUserByIDRequest{
|
|
UserId: newUserID,
|
|
},
|
|
},
|
|
want: &user.GetUserByIDResponse{
|
|
User: &user.User{
|
|
State: user.UserState_USER_STATE_ACTIVE,
|
|
Username: "",
|
|
LoginNames: nil,
|
|
PreferredLoginName: "",
|
|
Type: &user.User_Human{
|
|
Human: &user.HumanUser{
|
|
Profile: &user.HumanProfile{
|
|
GivenName: "firstname",
|
|
FamilyName: "lastname",
|
|
NickName: gu.Ptr(""),
|
|
DisplayName: gu.Ptr("firstname lastname"),
|
|
PreferredLanguage: gu.Ptr("und"),
|
|
Gender: user.Gender_GENDER_UNSPECIFIED.Enum(),
|
|
AvatarUrl: "",
|
|
},
|
|
Email: &user.HumanEmail{
|
|
Email: newOrgOwnerEmail,
|
|
},
|
|
Phone: &user.HumanPhone{},
|
|
},
|
|
},
|
|
},
|
|
Details: &object.Details{
|
|
ChangeDate: timestamppb.Now(),
|
|
CreationDate: timestamppb.Now(),
|
|
ResourceOwner: newOrg.GetOrganizationId(),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Instance, ok",
|
|
args: args{
|
|
IamCTX,
|
|
&user.GetUserByIDRequest{
|
|
UserId: newUserID,
|
|
},
|
|
},
|
|
want: &user.GetUserByIDResponse{
|
|
User: &user.User{
|
|
State: user.UserState_USER_STATE_ACTIVE,
|
|
Username: "",
|
|
LoginNames: nil,
|
|
PreferredLoginName: "",
|
|
Type: &user.User_Human{
|
|
Human: &user.HumanUser{
|
|
Profile: &user.HumanProfile{
|
|
GivenName: "firstname",
|
|
FamilyName: "lastname",
|
|
NickName: gu.Ptr(""),
|
|
DisplayName: gu.Ptr("firstname lastname"),
|
|
PreferredLanguage: gu.Ptr("und"),
|
|
Gender: user.Gender_GENDER_UNSPECIFIED.Enum(),
|
|
AvatarUrl: "",
|
|
},
|
|
Email: &user.HumanEmail{
|
|
Email: newOrgOwnerEmail,
|
|
},
|
|
Phone: &user.HumanPhone{},
|
|
},
|
|
},
|
|
},
|
|
Details: &object.Details{
|
|
ChangeDate: timestamppb.Now(),
|
|
CreationDate: timestamppb.Now(),
|
|
ResourceOwner: newOrg.GetOrganizationId(),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Org, error",
|
|
args: args{
|
|
OrgCTX,
|
|
&user.GetUserByIDRequest{
|
|
UserId: newUserID,
|
|
},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "User, error",
|
|
args: args{
|
|
UserCTX,
|
|
&user.GetUserByIDRequest{
|
|
UserId: newUserID,
|
|
},
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.args.ctx, time.Minute)
|
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
|
got, err := Client.GetUserByID(tt.args.ctx, tt.args.req)
|
|
if tt.wantErr {
|
|
assert.Error(ttt, err)
|
|
return
|
|
}
|
|
if !assert.NoError(ttt, err) {
|
|
return
|
|
}
|
|
|
|
tt.want.User.UserId = tt.args.req.GetUserId()
|
|
tt.want.User.Username = newOrgOwnerEmail
|
|
tt.want.User.PreferredLoginName = newOrgOwnerEmail
|
|
tt.want.User.LoginNames = []string{newOrgOwnerEmail}
|
|
if human := tt.want.User.GetHuman(); human != nil {
|
|
human.Email.Email = newOrgOwnerEmail
|
|
}
|
|
// details tested in GetUserByID
|
|
tt.want.User.Details = got.User.GetDetails()
|
|
|
|
assert.Equal(ttt, tt.want.User, got.User)
|
|
}, retryDuration, tick, "timeout waiting for expected user result")
|
|
})
|
|
}
|
|
}
|
|
|
|
type userAttrs []userAttr
|
|
|
|
func (u userAttrs) userIDs() []string {
|
|
ids := make([]string, len(u))
|
|
for i := range u {
|
|
ids[i] = u[i].UserID
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func (u userAttrs) emails() []string {
|
|
emails := make([]string, len(u))
|
|
for i := range u {
|
|
emails[i] = u[i].Username
|
|
}
|
|
return emails
|
|
}
|
|
|
|
type userAttr struct {
|
|
UserID string
|
|
Username string
|
|
Phone string
|
|
Changed *timestamppb.Timestamp
|
|
Details *object.Details
|
|
}
|
|
|
|
func createUsers(ctx context.Context, instance *integration.Instance, orgID string, count int, passwordChangeRequired bool) userAttrs {
|
|
infos := make([]userAttr, count)
|
|
for i := 0; i < count; i++ {
|
|
infos[i] = createUser(ctx, instance, orgID, passwordChangeRequired)
|
|
}
|
|
slices.Reverse(infos)
|
|
return infos
|
|
}
|
|
|
|
func createUser(ctx context.Context, instance *integration.Instance, orgID string, passwordChangeRequired bool) userAttr {
|
|
username := integration.Email()
|
|
return createUserWithUserName(ctx, instance, username, orgID, passwordChangeRequired)
|
|
}
|
|
|
|
func createUserWithUserName(ctx context.Context, instance *integration.Instance, username string, orgID string, passwordChangeRequired bool) userAttr {
|
|
// used as default country prefix
|
|
phone := integration.Phone()
|
|
resp := instance.CreateHumanUserVerified(ctx, orgID, username, phone)
|
|
info := userAttr{resp.GetUserId(), username, phone, nil, resp.GetDetails()}
|
|
// as the change date of the creation is the creation date
|
|
resp.Details.CreationDate = resp.GetDetails().GetChangeDate()
|
|
if passwordChangeRequired {
|
|
details := instance.SetUserPassword(ctx, resp.GetUserId(), integration.UserPassword, true)
|
|
info.Changed = details.GetChangeDate()
|
|
}
|
|
return info
|
|
}
|
|
|
|
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())
|
|
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{
|
|
InstancePermissionV2.WithAuthorizationToken(OrgCTX, integration.UserTypeNoPermission),
|
|
&user.ListUsersRequest{},
|
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
|
info := createUser(ctx, InstancePermissionV2, 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(iamOwnerCtx, InstancePermissionV2, orgResp.OrganizationId, true)
|
|
// create session to get token
|
|
userID := info.UserID
|
|
createResp, err := InstancePermissionV2.Client.SessionV2.CreateSession(iamOwnerCtx, &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(iamOwnerCtx, 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{
|
|
iamOwnerCtx,
|
|
&user.ListUsersRequest{},
|
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
|
info := createUser(ctx, InstancePermissionV2, 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{
|
|
iamOwnerCtx,
|
|
&user.ListUsersRequest{},
|
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
|
info := createUser(ctx, InstancePermissionV2, 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{
|
|
iamOwnerCtx,
|
|
&user.ListUsersRequest{},
|
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
|
infos := createUsers(ctx, InstancePermissionV2, orgResp.OrganizationId, 3, false)
|
|
request.Queries = []*user.SearchQuery{}
|
|
request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId))
|
|
request.Queries = append(request.Queries, InUserIDsQuery(infos.userIDs()))
|
|
|
|
InstancePermissionV2.SetUserMetadata(ctx, infos[0].UserID, "my meta", "my value 1")
|
|
InstancePermissionV2.SetUserMetadata(ctx, infos[1].UserID, "my meta 2", "my value 3")
|
|
InstancePermissionV2.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,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "list user by username, ok",
|
|
args: args{
|
|
iamOwnerCtx,
|
|
&user.ListUsersRequest{},
|
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
|
info := createUser(ctx, InstancePermissionV2, 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{
|
|
iamOwnerCtx,
|
|
&user.ListUsersRequest{},
|
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
|
info := createUser(ctx, InstancePermissionV2, 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{
|
|
iamOwnerCtx,
|
|
&user.ListUsersRequest{},
|
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
|
infos := createUsers(ctx, InstancePermissionV2, orgResp.OrganizationId, 3, false)
|
|
request.Queries = []*user.SearchQuery{}
|
|
request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId))
|
|
request.Queries = append(request.Queries, InUserEmailsQuery(infos.emails()))
|
|
|
|
InstancePermissionV2.SetUserMetadata(ctx, infos[0].UserID, "my meta 1", "my value")
|
|
InstancePermissionV2.SetUserMetadata(ctx, infos[0].UserID, "my meta 2", "my value")
|
|
InstancePermissionV2.SetUserMetadata(ctx, infos[1].UserID, "my meta 2", "my value")
|
|
InstancePermissionV2.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,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "list user in emails no found, ok",
|
|
args: args{
|
|
iamOwnerCtx,
|
|
&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{
|
|
iamOwnerCtx,
|
|
&user.ListUsersRequest{},
|
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
|
info := createUser(ctx, InstancePermissionV2, 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{
|
|
iamOwnerCtx,
|
|
&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{
|
|
iamOwnerCtx,
|
|
&user.ListUsersRequest{},
|
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
|
orgResp := InstancePermissionV2.CreateOrganization(ctx, integration.OrganizationName(), integration.Email())
|
|
|
|
infos := createUsers(ctx, InstancePermissionV2, 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{
|
|
iamOwnerCtx,
|
|
&user.ListUsersRequest{},
|
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
|
orgRespForOrgTests := InstancePermissionV2.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
|
info := createUser(ctx, InstancePermissionV2, 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 InstancePermissionV2.CreateOrganization()
|
|
nil,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "list user with wrong org query",
|
|
args: args{
|
|
iamOwnerCtx,
|
|
&user.ListUsersRequest{},
|
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
|
orgRespForOrgTests := InstancePermissionV2.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
|
orgRespForOrgTests2 := InstancePermissionV2.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
|
createUser(ctx, InstancePermissionV2, 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 InstancePermissionV2.CreateOrganization()
|
|
nil,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "when no users matching meta key should return empty list",
|
|
args: args{
|
|
iamOwnerCtx,
|
|
&user.ListUsersRequest{},
|
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
|
createUser(ctx, InstancePermissionV2, 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(iamOwnerCtx, tc.args.req)
|
|
|
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.args.ctx, 20*time.Second)
|
|
require.EventuallyWithT(t1, func(ttt *assert.CollectT) {
|
|
got, err := InstancePermissionV2.Client.UserV2.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_SystemUsers_ListUsers(t *testing.T) {
|
|
|
|
org1 := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), integration.Email())
|
|
org2 := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), "[email protected]")
|
|
org3 := Instance.CreateOrganization(IamCTX, integration.OrganizationName(), integration.Email())
|
|
_ = createUserWithUserName(IamCTX, Instance, "[email protected]", org1.OrganizationId, false)
|
|
_ = createUserWithUserName(IamCTX, Instance, "[email protected]", org2.OrganizationId, false)
|
|
_ = createUserWithUserName(IamCTX, Instance, "[email protected]", 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{"[email protected]", "[email protected]", "[email protected]"},
|
|
},
|
|
{
|
|
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{"[email protected]", "[email protected]"},
|
|
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())
|
|
org2 := InstancePermissionV2.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), "[email protected]")
|
|
org3 := InstancePermissionV2.CreateOrganization(iamOwnerCtx, integration.OrganizationName(), integration.Email())
|
|
_ = createUserWithUserName(iamOwnerCtx, InstancePermissionV2, "[email protected]", org1.OrganizationId, false)
|
|
_ = createUserWithUserName(iamOwnerCtx, InstancePermissionV2, "[email protected]", org2.OrganizationId, false)
|
|
_ = createUserWithUserName(iamOwnerCtx, InstancePermissionV2, "[email protected]", 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{"[email protected]", "[email protected]", "[email protected]"},
|
|
},
|
|
{
|
|
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{"[email protected]", "[email protected]"},
|
|
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 := InstancePermissionV2.Client.UserV2.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 InUserIDsQuery(ids []string) *user.SearchQuery {
|
|
return &user.SearchQuery{
|
|
Query: &user.SearchQuery_InUserIdsQuery{
|
|
InUserIdsQuery: &user.InUserIDQuery{
|
|
UserIds: ids,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func InUserEmailsQuery(emails []string) *user.SearchQuery {
|
|
return &user.SearchQuery{
|
|
Query: &user.SearchQuery_InUserEmailsQuery{
|
|
InUserEmailsQuery: &user.InUserEmailsQuery{
|
|
UserEmails: emails,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func PhoneQuery(number string) *user.SearchQuery {
|
|
return &user.SearchQuery{
|
|
Query: &user.SearchQuery_PhoneQuery{
|
|
PhoneQuery: &user.PhoneQuery{
|
|
Number: number,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func UsernameQuery(username string) *user.SearchQuery {
|
|
return &user.SearchQuery{
|
|
Query: &user.SearchQuery_UserNameQuery{
|
|
UserNameQuery: &user.UserNameQuery{
|
|
UserName: username,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func OrganizationIdQuery(resourceowner string) *user.SearchQuery {
|
|
return &user.SearchQuery{
|
|
Query: &user.SearchQuery_OrganizationIdQuery{
|
|
OrganizationIdQuery: &user.OrganizationIdQuery{
|
|
OrganizationId: resourceowner,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func OrQuery(queries []*user.SearchQuery) *user.SearchQuery {
|
|
return &user.SearchQuery{
|
|
Query: &user.SearchQuery_OrQuery{
|
|
OrQuery: &user.OrQuery{
|
|
Queries: queries,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func MetadataKeyContainsQuery(metadataKey string) *user.SearchQuery {
|
|
return &user.SearchQuery{
|
|
Query: &user.SearchQuery_MetadataKeyFilter{
|
|
MetadataKeyFilter: &v2.MetadataKeyFilter{
|
|
Key: metadataKey,
|
|
Method: filter.TextFilterMethod_TEXT_FILTER_METHOD_STARTS_WITH},
|
|
},
|
|
}
|
|
}
|
|
|
|
func MetakeyEqualsQuery(metaKey string) *user.SearchQuery {
|
|
return &user.SearchQuery{
|
|
Query: &user.SearchQuery_MetadataKeyFilter{
|
|
MetadataKeyFilter: &v2.MetadataKeyFilter{
|
|
Key: metaKey,
|
|
Method: filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS},
|
|
},
|
|
}
|
|
}
|
|
|
|
func MetadataValueQuery(metaValue string) *user.SearchQuery {
|
|
return &user.SearchQuery{
|
|
Query: &user.SearchQuery_MetadataValueFilter{
|
|
MetadataValueFilter: &v2.MetadataValueFilter{
|
|
Value: []byte(base64.StdEncoding.EncodeToString([]byte(metaValue))),
|
|
Method: filter.ByteFilterMethod_BYTE_FILTER_METHOD_EQUALS,
|
|
},
|
|
},
|
|
}
|
|
}
|