chore: move converter methods users v2 to separate converter package + add tests (#10567)

# Which Problems Are Solved

As requested by @adlerhurst in
https://github.com/zitadel/zitadel/pull/10415#discussion_r2298087711 , I
am moving the refactoring of v2 user converter methods to a separate PR

# How the Problems Are Solved

Cherry-pick 648c234caf

# Additional Context

Parent of https://github.com/zitadel/zitadel/pull/10415

(cherry picked from commit b604615cab)
This commit is contained in:
Marco A.
2025-08-27 13:08:13 +02:00
committed by Livio Spring
parent a3dac4d5cd
commit df0f033880
12 changed files with 1624 additions and 306 deletions

View File

@@ -39,7 +39,7 @@ func ListQueryToQuery(query *object.ListQuery) (offset, limit uint64, asc bool)
if query == nil {
return 0, 0, false
}
return query.Offset, uint64(query.Limit), query.Asc
return query.Offset, uint64(query.GetLimit()), query.GetAsc()
}
func ResourceOwnerFromReq(ctx context.Context, req *object.RequestContext) string {

View File

@@ -0,0 +1,61 @@
package convert
import (
"github.com/muhlemmer/gu"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
)
func humanToPb(userQ *query.Human, assetPrefix, owner string) *user.HumanUser {
if userQ == nil {
return nil
}
var passwordChanged, mfaInitSkipped *timestamppb.Timestamp
if !userQ.PasswordChanged.IsZero() {
passwordChanged = timestamppb.New(userQ.PasswordChanged)
}
if !userQ.MFAInitSkipped.IsZero() {
mfaInitSkipped = timestamppb.New(userQ.MFAInitSkipped)
}
return &user.HumanUser{
Profile: &user.HumanProfile{
GivenName: userQ.FirstName,
FamilyName: userQ.LastName,
NickName: gu.Ptr(userQ.NickName),
DisplayName: gu.Ptr(userQ.DisplayName),
PreferredLanguage: gu.Ptr(userQ.PreferredLanguage.String()),
Gender: gu.Ptr(genderToPb(userQ.Gender)),
AvatarUrl: domain.AvatarURL(assetPrefix, owner, userQ.AvatarKey),
},
Email: &user.HumanEmail{
Email: string(userQ.Email),
IsVerified: userQ.IsEmailVerified,
},
Phone: &user.HumanPhone{
Phone: string(userQ.Phone),
IsVerified: userQ.IsPhoneVerified,
},
PasswordChangeRequired: userQ.PasswordChangeRequired,
PasswordChanged: passwordChanged,
MfaInitSkipped: mfaInitSkipped,
}
}
func genderToPb(gender domain.Gender) user.Gender {
switch gender {
case domain.GenderDiverse:
return user.Gender_GENDER_DIVERSE
case domain.GenderFemale:
return user.Gender_GENDER_FEMALE
case domain.GenderMale:
return user.Gender_GENDER_MALE
case domain.GenderUnspecified:
return user.Gender_GENDER_UNSPECIFIED
default:
return user.Gender_GENDER_UNSPECIFIED
}
}

View File

@@ -0,0 +1,179 @@
package convert
import (
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/require"
"golang.org/x/text/language"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
)
func Test_genderToPb(t *testing.T) {
t.Parallel()
tt := []struct {
name string
input domain.Gender
expect user.Gender
}{
{
name: "Diverse",
input: domain.GenderDiverse,
expect: user.Gender_GENDER_DIVERSE,
},
{
name: "Female",
input: domain.GenderFemale,
expect: user.Gender_GENDER_FEMALE,
},
{
name: "Male",
input: domain.GenderMale,
expect: user.Gender_GENDER_MALE,
},
{
name: "Unspecified",
input: domain.GenderUnspecified,
expect: user.Gender_GENDER_UNSPECIFIED,
},
{
name: "Unknown value",
input: domain.Gender(999),
expect: user.Gender_GENDER_UNSPECIFIED,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := genderToPb(tc.input)
require.Equal(t, tc.expect, got)
})
}
}
func Test_humanToPb(t *testing.T) {
t.Parallel()
now := time.Now().UTC()
assetPrefix := "prefix"
owner := "owner"
avatarKey := "avatar-key"
tt := []struct {
name string
input *query.Human
expected *user.HumanUser
}{
{
name: "all fields set",
input: &query.Human{
FirstName: "John",
LastName: "Doe",
NickName: "JD",
DisplayName: "Johnny",
PreferredLanguage: language.English,
Gender: domain.GenderMale,
AvatarKey: avatarKey,
Email: "john.doe@example.com",
IsEmailVerified: true,
Phone: "+123456789",
IsPhoneVerified: true,
PasswordChangeRequired: true,
PasswordChanged: now,
MFAInitSkipped: now,
},
expected: &user.HumanUser{
Profile: &user.HumanProfile{
GivenName: "John",
FamilyName: "Doe",
NickName: gu.Ptr("JD"),
DisplayName: gu.Ptr("Johnny"),
PreferredLanguage: gu.Ptr("en"),
Gender: gu.Ptr(user.Gender_GENDER_MALE),
AvatarUrl: domain.AvatarURL(assetPrefix, owner, avatarKey),
},
Email: &user.HumanEmail{
Email: "john.doe@example.com",
IsVerified: true,
},
Phone: &user.HumanPhone{
Phone: "+123456789",
IsVerified: true,
},
PasswordChangeRequired: true,
PasswordChanged: timestamppb.New(now),
MfaInitSkipped: timestamppb.New(now),
},
},
{
name: "zero times, not verified, unspecified gender",
input: &query.Human{
FirstName: "Jane",
LastName: "Smith",
NickName: "",
DisplayName: "",
PreferredLanguage: language.German,
Gender: domain.GenderUnspecified,
AvatarKey: "",
Email: "jane.smith@example.com",
IsEmailVerified: false,
Phone: "",
IsPhoneVerified: false,
PasswordChangeRequired: false,
PasswordChanged: time.Time{},
MFAInitSkipped: time.Time{},
},
expected: &user.HumanUser{
Profile: &user.HumanProfile{
GivenName: "Jane",
FamilyName: "Smith",
NickName: gu.Ptr(""),
DisplayName: gu.Ptr(""),
PreferredLanguage: gu.Ptr("de"),
Gender: gu.Ptr(user.Gender_GENDER_UNSPECIFIED),
AvatarUrl: domain.AvatarURL(assetPrefix, owner, ""),
},
Email: &user.HumanEmail{
Email: "jane.smith@example.com",
IsVerified: false,
},
Phone: &user.HumanPhone{
Phone: "",
IsVerified: false,
},
PasswordChangeRequired: false,
PasswordChanged: nil,
MfaInitSkipped: nil,
},
},
{
name: "nil input",
input: nil,
expected: nil,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
if tc.input == nil {
require.Nil(t, humanToPb(nil, assetPrefix, owner))
return
}
got := humanToPb(tc.input, assetPrefix, owner)
require.Equal(t, tc.expected.Profile, got.Profile)
require.Equal(t, tc.expected.Email, got.Email)
require.Equal(t, tc.expected.Phone, got.Phone)
require.Equal(t, tc.expected.PasswordChangeRequired, got.PasswordChangeRequired)
require.Equal(t, tc.expected.PasswordChanged, got.PasswordChanged)
require.Equal(t, tc.expected.MfaInitSkipped, got.MfaInitSkipped)
})
}
}

View File

@@ -0,0 +1,27 @@
package convert
import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
)
func machineToPb(userQ *query.Machine) *user.MachineUser {
return &user.MachineUser{
Name: userQ.Name,
Description: userQ.Description,
HasSecret: userQ.EncodedSecret != "",
AccessTokenType: accessTokenTypeToPb(userQ.AccessTokenType),
}
}
func accessTokenTypeToPb(accessTokenType domain.OIDCTokenType) user.AccessTokenType {
switch accessTokenType {
case domain.OIDCTokenTypeBearer:
return user.AccessTokenType_ACCESS_TOKEN_TYPE_BEARER
case domain.OIDCTokenTypeJWT:
return user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT
default:
return user.AccessTokenType_ACCESS_TOKEN_TYPE_BEARER
}
}

View File

@@ -0,0 +1,108 @@
package convert
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
)
func Test_accessTokenTypeToPb(t *testing.T) {
t.Parallel()
tt := []struct {
name string
input domain.OIDCTokenType
expected user.AccessTokenType
}{
{
name: "Bearer token type",
input: domain.OIDCTokenTypeBearer,
expected: user.AccessTokenType_ACCESS_TOKEN_TYPE_BEARER,
},
{
name: "JWT token type",
input: domain.OIDCTokenTypeJWT,
expected: user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT,
},
{
name: "Unknown token type returns Bearer",
input: domain.OIDCTokenType(2),
expected: user.AccessTokenType_ACCESS_TOKEN_TYPE_BEARER,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := accessTokenTypeToPb(tc.input)
require.Equal(t, tc.expected, got)
})
}
}
func Test_machineToPb(t *testing.T) {
t.Parallel()
tt := []struct {
name string
input *query.Machine
expected *user.MachineUser
}{
{
name: "All fields set, Bearer token, HasSecret true",
input: &query.Machine{
Name: "machine1",
Description: "desc",
EncodedSecret: "secret",
AccessTokenType: domain.OIDCTokenTypeBearer,
},
expected: &user.MachineUser{
Name: "machine1",
Description: "desc",
HasSecret: true,
AccessTokenType: user.AccessTokenType_ACCESS_TOKEN_TYPE_BEARER,
},
},
{
name: "No secret, JWT token",
input: &query.Machine{
Name: "machine2",
Description: "desc2",
EncodedSecret: "",
AccessTokenType: domain.OIDCTokenTypeJWT,
},
expected: &user.MachineUser{
Name: "machine2",
Description: "desc2",
HasSecret: false,
AccessTokenType: user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT,
},
},
{
name: "Unknown token type, HasSecret false",
input: &query.Machine{
Name: "machine3",
Description: "desc3",
EncodedSecret: "",
AccessTokenType: domain.OIDCTokenType(99),
},
expected: &user.MachineUser{
Name: "machine3",
Description: "desc3",
HasSecret: false,
AccessTokenType: user.AccessTokenType_ACCESS_TOKEN_TYPE_BEARER,
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := machineToPb(tc.input)
require.Equal(t, tc.expected, got)
})
}
}

View File

@@ -0,0 +1,191 @@
package convert
import (
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors"
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
)
func ListUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueries, error) {
offset, limit, asc := object.ListQueryToQuery(req.GetQuery())
queries, err := userQueriesToQuery(req.GetQueries(), 0 /*start from level 0*/)
if err != nil {
return nil, err
}
return &query.UserSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: userFieldNameToSortingColumn(req.GetSortingColumn()),
},
Queries: queries,
}, nil
}
func UsersToPb(users []*query.User, assetPrefix string) []*user.User {
u := make([]*user.User, len(users))
for i, user := range users {
u[i] = UserToPb(user, assetPrefix)
}
return u
}
func UserToPb(userQ *query.User, assetPrefix string) *user.User {
if userQ == nil {
return nil
}
return &user.User{
UserId: userQ.ID,
Details: object.DomainToDetailsPb(&domain.ObjectDetails{
Sequence: userQ.Sequence,
EventDate: userQ.ChangeDate,
ResourceOwner: userQ.ResourceOwner,
CreationDate: userQ.CreationDate,
}),
State: userStateToPb(userQ.State),
Username: userQ.Username,
LoginNames: userQ.LoginNames,
PreferredLoginName: userQ.PreferredLoginName,
Type: userTypeToPb(userQ, assetPrefix),
}
}
func userTypeToPb(userQ *query.User, assetPrefix string) user.UserType {
if userQ == nil {
return nil
}
if userQ.Human != nil {
return &user.User_Human{
Human: humanToPb(userQ.Human, assetPrefix, userQ.ResourceOwner),
}
}
if userQ.Machine != nil {
return &user.User_Machine{
Machine: machineToPb(userQ.Machine),
}
}
return nil
}
func userStateToPb(state domain.UserState) user.UserState {
switch state {
case domain.UserStateActive:
return user.UserState_USER_STATE_ACTIVE
case domain.UserStateInactive:
return user.UserState_USER_STATE_INACTIVE
case domain.UserStateDeleted:
return user.UserState_USER_STATE_DELETED
case domain.UserStateInitial:
return user.UserState_USER_STATE_INITIAL
case domain.UserStateLocked:
return user.UserState_USER_STATE_LOCKED
case domain.UserStateUnspecified:
return user.UserState_USER_STATE_UNSPECIFIED
case domain.UserStateSuspend:
return user.UserState_USER_STATE_UNSPECIFIED
default:
return user.UserState_USER_STATE_UNSPECIFIED
}
}
func userFieldNameToSortingColumn(field user.UserFieldName) query.Column {
switch field {
case user.UserFieldName_USER_FIELD_NAME_EMAIL:
return query.HumanEmailCol
case user.UserFieldName_USER_FIELD_NAME_FIRST_NAME:
return query.HumanFirstNameCol
case user.UserFieldName_USER_FIELD_NAME_LAST_NAME:
return query.HumanLastNameCol
case user.UserFieldName_USER_FIELD_NAME_DISPLAY_NAME:
return query.HumanDisplayNameCol
case user.UserFieldName_USER_FIELD_NAME_USER_NAME:
return query.UserUsernameCol
case user.UserFieldName_USER_FIELD_NAME_STATE:
return query.UserStateCol
case user.UserFieldName_USER_FIELD_NAME_TYPE:
return query.UserTypeCol
case user.UserFieldName_USER_FIELD_NAME_NICK_NAME:
return query.HumanNickNameCol
case user.UserFieldName_USER_FIELD_NAME_CREATION_DATE:
return query.UserCreationDateCol
case user.UserFieldName_USER_FIELD_NAME_UNSPECIFIED:
return query.UserIDCol
default:
return query.UserIDCol
}
}
func userQueriesToQuery(queries []*user.SearchQuery, level uint8) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
q[i], err = userQueryToQuery(query, level)
if err != nil {
return nil, err
}
}
return q, nil
}
func userQueryToQuery(sq *user.SearchQuery, level uint8) (query.SearchQuery, error) {
if level > 20 {
// can't go deeper than 20 levels of nesting.
return nil, zerrors.ThrowInvalidArgument(nil, "USER-zsQ97", "Errors.Query.TooManyNestingLevels")
}
switch q := sq.Query.(type) {
case *user.SearchQuery_UserNameQuery:
return query.NewUserUsernameSearchQuery(q.UserNameQuery.GetUserName(), object.TextMethodToQuery(q.UserNameQuery.GetMethod()))
case *user.SearchQuery_FirstNameQuery:
return query.NewUserFirstNameSearchQuery(q.FirstNameQuery.GetFirstName(), object.TextMethodToQuery(q.FirstNameQuery.GetMethod()))
case *user.SearchQuery_LastNameQuery:
return query.NewUserLastNameSearchQuery(q.LastNameQuery.GetLastName(), object.TextMethodToQuery(q.LastNameQuery.GetMethod()))
case *user.SearchQuery_NickNameQuery:
return query.NewUserNickNameSearchQuery(q.NickNameQuery.GetNickName(), object.TextMethodToQuery(q.NickNameQuery.GetMethod()))
case *user.SearchQuery_DisplayNameQuery:
return query.NewUserDisplayNameSearchQuery(q.DisplayNameQuery.GetDisplayName(), object.TextMethodToQuery(q.DisplayNameQuery.GetMethod()))
case *user.SearchQuery_EmailQuery:
return query.NewUserEmailSearchQuery(q.EmailQuery.GetEmailAddress(), object.TextMethodToQuery(q.EmailQuery.GetMethod()))
case *user.SearchQuery_PhoneQuery:
return query.NewUserPhoneSearchQuery(q.PhoneQuery.GetNumber(), object.TextMethodToQuery(q.PhoneQuery.GetMethod()))
case *user.SearchQuery_StateQuery:
return query.NewUserStateSearchQuery(q.StateQuery.GetState().ToDomain())
case *user.SearchQuery_TypeQuery:
return query.NewUserTypeSearchQuery(q.TypeQuery.GetType().ToDomain())
case *user.SearchQuery_LoginNameQuery:
return query.NewUserLoginNameExistsQuery(q.LoginNameQuery.GetLoginName(), object.TextMethodToQuery(q.LoginNameQuery.GetMethod()))
case *user.SearchQuery_OrganizationIdQuery:
return query.NewUserResourceOwnerSearchQuery(q.OrganizationIdQuery.GetOrganizationId(), query.TextEquals)
case *user.SearchQuery_InUserIdsQuery:
return query.NewUserInUserIdsSearchQuery(q.InUserIdsQuery.GetUserIds())
case *user.SearchQuery_OrQuery:
mappedQueries, err := userQueriesToQuery(q.OrQuery.GetQueries(), level+1)
if err != nil {
return nil, err
}
return query.NewUserOrSearchQuery(mappedQueries)
case *user.SearchQuery_AndQuery:
mappedQueries, err := userQueriesToQuery(q.AndQuery.GetQueries(), level+1)
if err != nil {
return nil, err
}
return query.NewUserAndSearchQuery(mappedQueries)
case *user.SearchQuery_NotQuery:
mappedQuery, err := userQueryToQuery(q.NotQuery.GetQuery(), level+1)
if err != nil {
return nil, err
}
return query.NewUserNotSearchQuery(mappedQuery)
case *user.SearchQuery_InUserEmailsQuery:
return query.NewUserInUserEmailsSearchQuery(q.InUserEmailsQuery.GetUserEmails())
case *user.SearchQuery_MetadataKeyFilter:
return query.NewUserMetadataKeySearchQuery(q.MetadataKeyFilter.GetKey(), query.TextComparison(q.MetadataKeyFilter.GetMethod()))
case *user.SearchQuery_MetadataValueFilter:
return query.NewUserMetadataValueSearchQuery(q.MetadataValueFilter.GetValue(), query.BytesComparison(q.MetadataValueFilter.GetMethod()))
default:
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -4,13 +4,10 @@ import (
"context"
"connectrpc.com/connect"
"github.com/muhlemmer/gu"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
"github.com/zitadel/zitadel/internal/api/grpc/user/v2/convert"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors"
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
)
@@ -26,12 +23,12 @@ func (s *Server) GetUserByID(ctx context.Context, req *connect.Request[user.GetU
EventDate: resp.ChangeDate,
ResourceOwner: resp.ResourceOwner,
}),
User: userToPb(resp, s.assetAPIPrefix(ctx)),
User: convert.UserToPb(resp, s.assetAPIPrefix(ctx)),
}), nil
}
func (s *Server) ListUsers(ctx context.Context, req *connect.Request[user.ListUsersRequest]) (*connect.Response[user.ListUsersResponse], error) {
queries, err := listUsersRequestToModel(req.Msg)
queries, err := convert.ListUsersRequestToModel(req.Msg)
if err != nil {
return nil, err
}
@@ -40,305 +37,7 @@ func (s *Server) ListUsers(ctx context.Context, req *connect.Request[user.ListUs
return nil, err
}
return connect.NewResponse(&user.ListUsersResponse{
Result: UsersToPb(res.Users, s.assetAPIPrefix(ctx)),
Result: convert.UsersToPb(res.Users, s.assetAPIPrefix(ctx)),
Details: object.ToListDetails(res.SearchResponse),
}), nil
}
func UsersToPb(users []*query.User, assetPrefix string) []*user.User {
u := make([]*user.User, len(users))
for i, user := range users {
u[i] = userToPb(user, assetPrefix)
}
return u
}
func userToPb(userQ *query.User, assetPrefix string) *user.User {
return &user.User{
UserId: userQ.ID,
Details: object.DomainToDetailsPb(&domain.ObjectDetails{
Sequence: userQ.Sequence,
EventDate: userQ.ChangeDate,
ResourceOwner: userQ.ResourceOwner,
CreationDate: userQ.CreationDate,
}),
State: userStateToPb(userQ.State),
Username: userQ.Username,
LoginNames: userQ.LoginNames,
PreferredLoginName: userQ.PreferredLoginName,
Type: userTypeToPb(userQ, assetPrefix),
}
}
func userTypeToPb(userQ *query.User, assetPrefix string) user.UserType {
if userQ.Human != nil {
return &user.User_Human{
Human: humanToPb(userQ.Human, assetPrefix, userQ.ResourceOwner),
}
}
if userQ.Machine != nil {
return &user.User_Machine{
Machine: machineToPb(userQ.Machine),
}
}
return nil
}
func humanToPb(userQ *query.Human, assetPrefix, owner string) *user.HumanUser {
var passwordChanged, mfaInitSkipped *timestamppb.Timestamp
if !userQ.PasswordChanged.IsZero() {
passwordChanged = timestamppb.New(userQ.PasswordChanged)
}
if !userQ.MFAInitSkipped.IsZero() {
mfaInitSkipped = timestamppb.New(userQ.MFAInitSkipped)
}
return &user.HumanUser{
Profile: &user.HumanProfile{
GivenName: userQ.FirstName,
FamilyName: userQ.LastName,
NickName: gu.Ptr(userQ.NickName),
DisplayName: gu.Ptr(userQ.DisplayName),
PreferredLanguage: gu.Ptr(userQ.PreferredLanguage.String()),
Gender: gu.Ptr(genderToPb(userQ.Gender)),
AvatarUrl: domain.AvatarURL(assetPrefix, owner, userQ.AvatarKey),
},
Email: &user.HumanEmail{
Email: string(userQ.Email),
IsVerified: userQ.IsEmailVerified,
},
Phone: &user.HumanPhone{
Phone: string(userQ.Phone),
IsVerified: userQ.IsPhoneVerified,
},
PasswordChangeRequired: userQ.PasswordChangeRequired,
PasswordChanged: passwordChanged,
MfaInitSkipped: mfaInitSkipped,
}
}
func machineToPb(userQ *query.Machine) *user.MachineUser {
return &user.MachineUser{
Name: userQ.Name,
Description: userQ.Description,
HasSecret: userQ.EncodedSecret != "",
AccessTokenType: accessTokenTypeToPb(userQ.AccessTokenType),
}
}
func userStateToPb(state domain.UserState) user.UserState {
switch state {
case domain.UserStateActive:
return user.UserState_USER_STATE_ACTIVE
case domain.UserStateInactive:
return user.UserState_USER_STATE_INACTIVE
case domain.UserStateDeleted:
return user.UserState_USER_STATE_DELETED
case domain.UserStateInitial:
return user.UserState_USER_STATE_INITIAL
case domain.UserStateLocked:
return user.UserState_USER_STATE_LOCKED
case domain.UserStateUnspecified:
return user.UserState_USER_STATE_UNSPECIFIED
case domain.UserStateSuspend:
return user.UserState_USER_STATE_UNSPECIFIED
default:
return user.UserState_USER_STATE_UNSPECIFIED
}
}
func genderToPb(gender domain.Gender) user.Gender {
switch gender {
case domain.GenderDiverse:
return user.Gender_GENDER_DIVERSE
case domain.GenderFemale:
return user.Gender_GENDER_FEMALE
case domain.GenderMale:
return user.Gender_GENDER_MALE
case domain.GenderUnspecified:
return user.Gender_GENDER_UNSPECIFIED
default:
return user.Gender_GENDER_UNSPECIFIED
}
}
func accessTokenTypeToPb(accessTokenType domain.OIDCTokenType) user.AccessTokenType {
switch accessTokenType {
case domain.OIDCTokenTypeBearer:
return user.AccessTokenType_ACCESS_TOKEN_TYPE_BEARER
case domain.OIDCTokenTypeJWT:
return user.AccessTokenType_ACCESS_TOKEN_TYPE_JWT
default:
return user.AccessTokenType_ACCESS_TOKEN_TYPE_BEARER
}
}
func listUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueries, error) {
offset, limit, asc := object.ListQueryToQuery(req.Query)
queries, err := userQueriesToQuery(req.Queries, 0 /*start from level 0*/)
if err != nil {
return nil, err
}
return &query.UserSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: userFieldNameToSortingColumn(req.SortingColumn),
},
Queries: queries,
}, nil
}
func userFieldNameToSortingColumn(field user.UserFieldName) query.Column {
switch field {
case user.UserFieldName_USER_FIELD_NAME_EMAIL:
return query.HumanEmailCol
case user.UserFieldName_USER_FIELD_NAME_FIRST_NAME:
return query.HumanFirstNameCol
case user.UserFieldName_USER_FIELD_NAME_LAST_NAME:
return query.HumanLastNameCol
case user.UserFieldName_USER_FIELD_NAME_DISPLAY_NAME:
return query.HumanDisplayNameCol
case user.UserFieldName_USER_FIELD_NAME_USER_NAME:
return query.UserUsernameCol
case user.UserFieldName_USER_FIELD_NAME_STATE:
return query.UserStateCol
case user.UserFieldName_USER_FIELD_NAME_TYPE:
return query.UserTypeCol
case user.UserFieldName_USER_FIELD_NAME_NICK_NAME:
return query.HumanNickNameCol
case user.UserFieldName_USER_FIELD_NAME_CREATION_DATE:
return query.UserCreationDateCol
case user.UserFieldName_USER_FIELD_NAME_UNSPECIFIED:
return query.UserIDCol
default:
return query.UserIDCol
}
}
func userQueriesToQuery(queries []*user.SearchQuery, level uint8) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
q[i], err = userQueryToQuery(query, level)
if err != nil {
return nil, err
}
}
return q, nil
}
func userQueryToQuery(query *user.SearchQuery, level uint8) (query.SearchQuery, error) {
if level > 20 {
// can't go deeper than 20 levels of nesting.
return nil, zerrors.ThrowInvalidArgument(nil, "USER-zsQ97", "Errors.Query.TooManyNestingLevels")
}
switch q := query.Query.(type) {
case *user.SearchQuery_UserNameQuery:
return userNameQueryToQuery(q.UserNameQuery)
case *user.SearchQuery_FirstNameQuery:
return firstNameQueryToQuery(q.FirstNameQuery)
case *user.SearchQuery_LastNameQuery:
return lastNameQueryToQuery(q.LastNameQuery)
case *user.SearchQuery_NickNameQuery:
return nickNameQueryToQuery(q.NickNameQuery)
case *user.SearchQuery_DisplayNameQuery:
return displayNameQueryToQuery(q.DisplayNameQuery)
case *user.SearchQuery_EmailQuery:
return emailQueryToQuery(q.EmailQuery)
case *user.SearchQuery_PhoneQuery:
return phoneQueryToQuery(q.PhoneQuery)
case *user.SearchQuery_StateQuery:
return stateQueryToQuery(q.StateQuery)
case *user.SearchQuery_TypeQuery:
return typeQueryToQuery(q.TypeQuery)
case *user.SearchQuery_LoginNameQuery:
return loginNameQueryToQuery(q.LoginNameQuery)
case *user.SearchQuery_OrganizationIdQuery:
return resourceOwnerQueryToQuery(q.OrganizationIdQuery)
case *user.SearchQuery_InUserIdsQuery:
return inUserIdsQueryToQuery(q.InUserIdsQuery)
case *user.SearchQuery_OrQuery:
return orQueryToQuery(q.OrQuery, level)
case *user.SearchQuery_AndQuery:
return andQueryToQuery(q.AndQuery, level)
case *user.SearchQuery_NotQuery:
return notQueryToQuery(q.NotQuery, level)
case *user.SearchQuery_InUserEmailsQuery:
return inUserEmailsQueryToQuery(q.InUserEmailsQuery)
default:
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
}
}
func userNameQueryToQuery(q *user.UserNameQuery) (query.SearchQuery, error) {
return query.NewUserUsernameSearchQuery(q.UserName, object.TextMethodToQuery(q.Method))
}
func firstNameQueryToQuery(q *user.FirstNameQuery) (query.SearchQuery, error) {
return query.NewUserFirstNameSearchQuery(q.FirstName, object.TextMethodToQuery(q.Method))
}
func lastNameQueryToQuery(q *user.LastNameQuery) (query.SearchQuery, error) {
return query.NewUserLastNameSearchQuery(q.LastName, object.TextMethodToQuery(q.Method))
}
func nickNameQueryToQuery(q *user.NickNameQuery) (query.SearchQuery, error) {
return query.NewUserNickNameSearchQuery(q.NickName, object.TextMethodToQuery(q.Method))
}
func displayNameQueryToQuery(q *user.DisplayNameQuery) (query.SearchQuery, error) {
return query.NewUserDisplayNameSearchQuery(q.DisplayName, object.TextMethodToQuery(q.Method))
}
func emailQueryToQuery(q *user.EmailQuery) (query.SearchQuery, error) {
return query.NewUserEmailSearchQuery(q.EmailAddress, object.TextMethodToQuery(q.Method))
}
func phoneQueryToQuery(q *user.PhoneQuery) (query.SearchQuery, error) {
return query.NewUserPhoneSearchQuery(q.Number, object.TextMethodToQuery(q.Method))
}
func stateQueryToQuery(q *user.StateQuery) (query.SearchQuery, error) {
return query.NewUserStateSearchQuery(q.State.ToDomain())
}
func typeQueryToQuery(q *user.TypeQuery) (query.SearchQuery, error) {
return query.NewUserTypeSearchQuery(q.Type.ToDomain())
}
func loginNameQueryToQuery(q *user.LoginNameQuery) (query.SearchQuery, error) {
return query.NewUserLoginNameExistsQuery(q.LoginName, object.TextMethodToQuery(q.Method))
}
func resourceOwnerQueryToQuery(q *user.OrganizationIdQuery) (query.SearchQuery, error) {
return query.NewUserResourceOwnerSearchQuery(q.OrganizationId, query.TextEquals)
}
func inUserIdsQueryToQuery(q *user.InUserIDQuery) (query.SearchQuery, error) {
return query.NewUserInUserIdsSearchQuery(q.UserIds)
}
func orQueryToQuery(q *user.OrQuery, level uint8) (query.SearchQuery, error) {
mappedQueries, err := userQueriesToQuery(q.Queries, level+1)
if err != nil {
return nil, err
}
return query.NewUserOrSearchQuery(mappedQueries)
}
func andQueryToQuery(q *user.AndQuery, level uint8) (query.SearchQuery, error) {
mappedQueries, err := userQueriesToQuery(q.Queries, level+1)
if err != nil {
return nil, err
}
return query.NewUserAndSearchQuery(mappedQueries)
}
func notQueryToQuery(q *user.NotQuery, level uint8) (query.SearchQuery, error) {
mappedQuery, err := userQueryToQuery(q.Query, level+1)
if err != nil {
return nil, err
}
return query.NewUserNotSearchQuery(mappedQuery)
}
func inUserEmailsQueryToQuery(q *user.InUserEmailsQuery) (query.SearchQuery, error) {
return query.NewUserInUserEmailsSearchQuery(q.UserEmails)
}

View File

@@ -233,6 +233,10 @@ func NewUserMetadataKeySearchQuery(value string, comparison TextComparison) (Sea
return NewTextQuery(UserMetadataKeyCol, value, comparison)
}
func NewUserMetadataValueSearchQuery(value []byte, comparison BytesComparison) (SearchQuery, error) {
return NewBytesQuery(UserMetadataValueCol, value, comparison)
}
func NewUserMetadataExistsQuery(key string, value []byte, keyComparison TextComparison, valueComparison BytesComparison) (SearchQuery, error) {
// linking queries for the subselect
instanceQuery, err := NewColumnComparisonQuery(UserMetadataInstanceIDCol, UserInstanceIDCol, ColumnEquals)