mirror of
https://github.com/zitadel/zitadel.git
synced 2025-11-02 08:09:44 +00:00
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-pick648c234caf# Additional Context Parent of https://github.com/zitadel/zitadel/pull/10415 (cherry picked from commitb604615cab)
This commit is contained in:
@@ -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 {
|
||||
|
||||
61
internal/api/grpc/user/v2/convert/human.go
Normal file
61
internal/api/grpc/user/v2/convert/human.go
Normal 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
|
||||
}
|
||||
}
|
||||
179
internal/api/grpc/user/v2/convert/human_test.go
Normal file
179
internal/api/grpc/user/v2/convert/human_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
27
internal/api/grpc/user/v2/convert/machine.go
Normal file
27
internal/api/grpc/user/v2/convert/machine.go
Normal 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
|
||||
}
|
||||
}
|
||||
108
internal/api/grpc/user/v2/convert/machine_test.go
Normal file
108
internal/api/grpc/user/v2/convert/machine_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
191
internal/api/grpc/user/v2/convert/user.go
Normal file
191
internal/api/grpc/user/v2/convert/user.go
Normal 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")
|
||||
}
|
||||
}
|
||||
1025
internal/api/grpc/user/v2/convert/user_test.go
Normal file
1025
internal/api/grpc/user/v2/convert/user_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user