mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 03:57:32 +00:00
Merge branch 'main' into next-rc
# Conflicts: # cmd/defaults.yaml # cmd/setup/config.go # cmd/setup/setup.go # cmd/start/start.go # docs/yarn.lock # go.mod # go.sum # internal/api/grpc/action/v2beta/execution.go # internal/api/grpc/action/v2beta/query.go # internal/api/grpc/action/v2beta/server.go # internal/api/grpc/action/v2beta/target.go # internal/api/grpc/feature/v2/converter.go # internal/api/grpc/feature/v2/converter_test.go # internal/api/grpc/feature/v2/integration_test/feature_test.go # internal/api/grpc/feature/v2beta/converter.go # internal/api/grpc/feature/v2beta/converter_test.go # internal/api/grpc/feature/v2beta/integration_test/feature_test.go # internal/api/oidc/key.go # internal/api/oidc/op.go # internal/command/idp_intent_test.go # internal/command/instance_features.go # internal/command/instance_features_test.go # internal/command/system_features.go # internal/command/system_features_test.go # internal/feature/feature.go # internal/feature/key_enumer.go # internal/integration/client.go # internal/query/instance_features.go # internal/query/system_features.go # internal/repository/feature/feature_v2/feature.go # proto/zitadel/feature/v2/instance.proto # proto/zitadel/feature/v2/system.proto # proto/zitadel/feature/v2beta/instance.proto # proto/zitadel/feature/v2beta/system.proto
This commit is contained in:
@@ -3,6 +3,7 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -11,18 +12,18 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) SetEmail(ctx context.Context, req *user.SetEmailRequest) (resp *user.SetEmailResponse, err error) {
|
||||
func (s *Server) SetEmail(ctx context.Context, req *connect.Request[user.SetEmailRequest]) (resp *connect.Response[user.SetEmailResponse], err error) {
|
||||
var email *domain.Email
|
||||
|
||||
switch v := req.GetVerification().(type) {
|
||||
switch v := req.Msg.GetVerification().(type) {
|
||||
case *user.SetEmailRequest_SendCode:
|
||||
email, err = s.command.ChangeUserEmailURLTemplate(ctx, req.GetUserId(), req.GetEmail(), s.userCodeAlg, v.SendCode.GetUrlTemplate())
|
||||
email, err = s.command.ChangeUserEmailURLTemplate(ctx, req.Msg.GetUserId(), req.Msg.GetEmail(), s.userCodeAlg, v.SendCode.GetUrlTemplate())
|
||||
case *user.SetEmailRequest_ReturnCode:
|
||||
email, err = s.command.ChangeUserEmailReturnCode(ctx, req.GetUserId(), req.GetEmail(), s.userCodeAlg)
|
||||
email, err = s.command.ChangeUserEmailReturnCode(ctx, req.Msg.GetUserId(), req.Msg.GetEmail(), s.userCodeAlg)
|
||||
case *user.SetEmailRequest_IsVerified:
|
||||
email, err = s.command.ChangeUserEmailVerified(ctx, req.GetUserId(), req.GetEmail())
|
||||
email, err = s.command.ChangeUserEmailVerified(ctx, req.Msg.GetUserId(), req.Msg.GetEmail())
|
||||
case nil:
|
||||
email, err = s.command.ChangeUserEmail(ctx, req.GetUserId(), req.GetEmail(), s.userCodeAlg)
|
||||
email, err = s.command.ChangeUserEmail(ctx, req.Msg.GetUserId(), req.Msg.GetEmail(), s.userCodeAlg)
|
||||
default:
|
||||
err = zerrors.ThrowUnimplementedf(nil, "USERv2-Ahng0", "verification oneOf %T in method SetEmail not implemented", v)
|
||||
}
|
||||
@@ -30,26 +31,26 @@ func (s *Server) SetEmail(ctx context.Context, req *user.SetEmailRequest) (resp
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.SetEmailResponse{
|
||||
return connect.NewResponse(&user.SetEmailResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: email.Sequence,
|
||||
ChangeDate: timestamppb.New(email.ChangeDate),
|
||||
ResourceOwner: email.ResourceOwner,
|
||||
},
|
||||
VerificationCode: email.PlainCode,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ResendEmailCode(ctx context.Context, req *user.ResendEmailCodeRequest) (resp *user.ResendEmailCodeResponse, err error) {
|
||||
func (s *Server) ResendEmailCode(ctx context.Context, req *connect.Request[user.ResendEmailCodeRequest]) (resp *connect.Response[user.ResendEmailCodeResponse], err error) {
|
||||
var email *domain.Email
|
||||
|
||||
switch v := req.GetVerification().(type) {
|
||||
switch v := req.Msg.GetVerification().(type) {
|
||||
case *user.ResendEmailCodeRequest_SendCode:
|
||||
email, err = s.command.ResendUserEmailCodeURLTemplate(ctx, req.GetUserId(), s.userCodeAlg, v.SendCode.GetUrlTemplate())
|
||||
email, err = s.command.ResendUserEmailCodeURLTemplate(ctx, req.Msg.GetUserId(), s.userCodeAlg, v.SendCode.GetUrlTemplate())
|
||||
case *user.ResendEmailCodeRequest_ReturnCode:
|
||||
email, err = s.command.ResendUserEmailReturnCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||
email, err = s.command.ResendUserEmailReturnCode(ctx, req.Msg.GetUserId(), s.userCodeAlg)
|
||||
case nil:
|
||||
email, err = s.command.ResendUserEmailCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||
email, err = s.command.ResendUserEmailCode(ctx, req.Msg.GetUserId(), s.userCodeAlg)
|
||||
default:
|
||||
err = zerrors.ThrowUnimplementedf(nil, "USERv2-faj0l0nj5x", "verification oneOf %T in method ResendEmailCode not implemented", v)
|
||||
}
|
||||
@@ -57,26 +58,26 @@ func (s *Server) ResendEmailCode(ctx context.Context, req *user.ResendEmailCodeR
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.ResendEmailCodeResponse{
|
||||
return connect.NewResponse(&user.ResendEmailCodeResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: email.Sequence,
|
||||
ChangeDate: timestamppb.New(email.ChangeDate),
|
||||
ResourceOwner: email.ResourceOwner,
|
||||
},
|
||||
VerificationCode: email.PlainCode,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) SendEmailCode(ctx context.Context, req *user.SendEmailCodeRequest) (resp *user.SendEmailCodeResponse, err error) {
|
||||
func (s *Server) SendEmailCode(ctx context.Context, req *connect.Request[user.SendEmailCodeRequest]) (resp *connect.Response[user.SendEmailCodeResponse], err error) {
|
||||
var email *domain.Email
|
||||
|
||||
switch v := req.GetVerification().(type) {
|
||||
switch v := req.Msg.GetVerification().(type) {
|
||||
case *user.SendEmailCodeRequest_SendCode:
|
||||
email, err = s.command.SendUserEmailCodeURLTemplate(ctx, req.GetUserId(), s.userCodeAlg, v.SendCode.GetUrlTemplate())
|
||||
email, err = s.command.SendUserEmailCodeURLTemplate(ctx, req.Msg.GetUserId(), s.userCodeAlg, v.SendCode.GetUrlTemplate())
|
||||
case *user.SendEmailCodeRequest_ReturnCode:
|
||||
email, err = s.command.SendUserEmailReturnCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||
email, err = s.command.SendUserEmailReturnCode(ctx, req.Msg.GetUserId(), s.userCodeAlg)
|
||||
case nil:
|
||||
email, err = s.command.SendUserEmailCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||
email, err = s.command.SendUserEmailCode(ctx, req.Msg.GetUserId(), s.userCodeAlg)
|
||||
default:
|
||||
err = zerrors.ThrowUnimplementedf(nil, "USERv2-faj0l0nj5x", "verification oneOf %T in method SendEmailCode not implemented", v)
|
||||
}
|
||||
@@ -84,30 +85,30 @@ func (s *Server) SendEmailCode(ctx context.Context, req *user.SendEmailCodeReque
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.SendEmailCodeResponse{
|
||||
return connect.NewResponse(&user.SendEmailCodeResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: email.Sequence,
|
||||
ChangeDate: timestamppb.New(email.ChangeDate),
|
||||
ResourceOwner: email.ResourceOwner,
|
||||
},
|
||||
VerificationCode: email.PlainCode,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) VerifyEmail(ctx context.Context, req *user.VerifyEmailRequest) (*user.VerifyEmailResponse, error) {
|
||||
func (s *Server) VerifyEmail(ctx context.Context, req *connect.Request[user.VerifyEmailRequest]) (*connect.Response[user.VerifyEmailResponse], error) {
|
||||
details, err := s.command.VerifyUserEmail(ctx,
|
||||
req.GetUserId(),
|
||||
req.GetVerificationCode(),
|
||||
req.Msg.GetUserId(),
|
||||
req.Msg.GetVerificationCode(),
|
||||
s.userCodeAlg,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyEmailResponse{
|
||||
return connect.NewResponse(&user.VerifyEmailResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: details.Sequence,
|
||||
ChangeDate: timestamppb.New(details.EventDate),
|
||||
ResourceOwner: details.ResourceOwner,
|
||||
},
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
188
internal/api/grpc/user/v2/human.go
Normal file
188
internal/api/grpc/user/v2/human.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
legacyobject "github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) createUserTypeHuman(ctx context.Context, humanPb *user.CreateUserRequest_Human, orgId string, userName, userId *string) (*connect.Response[user.CreateUserResponse], error) {
|
||||
addHumanPb := &user.AddHumanUserRequest{
|
||||
Username: userName,
|
||||
UserId: userId,
|
||||
Organization: &legacyobject.Organization{
|
||||
Org: &legacyobject.Organization_OrgId{OrgId: orgId},
|
||||
},
|
||||
Profile: humanPb.Profile,
|
||||
Email: humanPb.Email,
|
||||
Phone: humanPb.Phone,
|
||||
IdpLinks: humanPb.IdpLinks,
|
||||
TotpSecret: humanPb.TotpSecret,
|
||||
}
|
||||
switch pwType := humanPb.GetPasswordType().(type) {
|
||||
case *user.CreateUserRequest_Human_HashedPassword:
|
||||
addHumanPb.PasswordType = &user.AddHumanUserRequest_HashedPassword{
|
||||
HashedPassword: pwType.HashedPassword,
|
||||
}
|
||||
case *user.CreateUserRequest_Human_Password:
|
||||
addHumanPb.PasswordType = &user.AddHumanUserRequest_Password{
|
||||
Password: pwType.Password,
|
||||
}
|
||||
default:
|
||||
// optional password is not set
|
||||
}
|
||||
newHuman, err := AddUserRequestToAddHuman(addHumanPb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = s.command.AddUserHuman(
|
||||
ctx,
|
||||
orgId,
|
||||
newHuman,
|
||||
false,
|
||||
s.userCodeAlg,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&user.CreateUserResponse{
|
||||
Id: newHuman.ID,
|
||||
CreationDate: timestamppb.New(newHuman.Details.EventDate),
|
||||
EmailCode: newHuman.EmailCode,
|
||||
PhoneCode: newHuman.PhoneCode,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) updateUserTypeHuman(ctx context.Context, humanPb *user.UpdateUserRequest_Human, userId string, userName *string) (*connect.Response[user.UpdateUserResponse], error) {
|
||||
cmd, err := updateHumanUserToCommand(userId, userName, humanPb)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = s.command.ChangeUserHuman(ctx, cmd, s.userCodeAlg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&user.UpdateUserResponse{
|
||||
ChangeDate: timestamppb.New(cmd.Details.EventDate),
|
||||
EmailCode: cmd.EmailCode,
|
||||
PhoneCode: cmd.PhoneCode,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func updateHumanUserToCommand(userId string, userName *string, human *user.UpdateUserRequest_Human) (*command.ChangeHuman, error) {
|
||||
phone := human.GetPhone()
|
||||
if phone != nil && phone.Phone == "" && phone.GetVerification() != nil {
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "USERv2-4f3d6", "Errors.User.Phone.VerifyingRemovalIsNotSupported")
|
||||
}
|
||||
email, err := setHumanEmailToEmail(human.Email, userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &command.ChangeHuman{
|
||||
ID: userId,
|
||||
Username: userName,
|
||||
Profile: SetHumanProfileToProfile(human.Profile),
|
||||
Email: email,
|
||||
Phone: setHumanPhoneToPhone(human.Phone, true),
|
||||
Password: setHumanPasswordToPassword(human.Password),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func updateHumanUserRequestToChangeHuman(req *user.UpdateHumanUserRequest) (*command.ChangeHuman, error) {
|
||||
email, err := setHumanEmailToEmail(req.Email, req.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changeHuman := &command.ChangeHuman{
|
||||
ID: req.GetUserId(),
|
||||
Username: req.Username,
|
||||
Email: email,
|
||||
Phone: setHumanPhoneToPhone(req.Phone, false),
|
||||
Password: setHumanPasswordToPassword(req.Password),
|
||||
}
|
||||
if profile := req.GetProfile(); profile != nil {
|
||||
var firstName *string
|
||||
if profile.GivenName != "" {
|
||||
firstName = &profile.GivenName
|
||||
}
|
||||
var lastName *string
|
||||
if profile.FamilyName != "" {
|
||||
lastName = &profile.FamilyName
|
||||
}
|
||||
changeHuman.Profile = SetHumanProfileToProfile(&user.UpdateUserRequest_Human_Profile{
|
||||
GivenName: firstName,
|
||||
FamilyName: lastName,
|
||||
NickName: profile.NickName,
|
||||
DisplayName: profile.DisplayName,
|
||||
PreferredLanguage: profile.PreferredLanguage,
|
||||
Gender: profile.Gender,
|
||||
})
|
||||
}
|
||||
return changeHuman, nil
|
||||
}
|
||||
|
||||
func SetHumanProfileToProfile(profile *user.UpdateUserRequest_Human_Profile) *command.Profile {
|
||||
if profile == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.Profile{
|
||||
FirstName: profile.GivenName,
|
||||
LastName: profile.FamilyName,
|
||||
NickName: profile.NickName,
|
||||
DisplayName: profile.DisplayName,
|
||||
PreferredLanguage: ifNotNilPtr(profile.PreferredLanguage, language.Make),
|
||||
Gender: ifNotNilPtr(profile.Gender, genderToDomain),
|
||||
}
|
||||
}
|
||||
|
||||
func setHumanEmailToEmail(email *user.SetHumanEmail, userID string) (*command.Email, error) {
|
||||
if email == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var urlTemplate string
|
||||
if email.GetSendCode() != nil && email.GetSendCode().UrlTemplate != nil {
|
||||
urlTemplate = *email.GetSendCode().UrlTemplate
|
||||
if err := domain.RenderConfirmURLTemplate(io.Discard, urlTemplate, userID, "code", "orgID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &command.Email{
|
||||
Address: domain.EmailAddress(email.Email),
|
||||
Verified: email.GetIsVerified(),
|
||||
ReturnCode: email.GetReturnCode() != nil,
|
||||
URLTemplate: urlTemplate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setHumanPhoneToPhone(phone *user.SetHumanPhone, withRemove bool) *command.Phone {
|
||||
if phone == nil {
|
||||
return nil
|
||||
}
|
||||
number := phone.GetPhone()
|
||||
return &command.Phone{
|
||||
Number: domain.PhoneNumber(number),
|
||||
Verified: phone.GetIsVerified(),
|
||||
ReturnCode: phone.GetReturnCode() != nil,
|
||||
Remove: withRemove && number == "",
|
||||
}
|
||||
}
|
||||
|
||||
func setHumanPasswordToPassword(password *user.SetPassword) *command.Password {
|
||||
if password == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.Password{
|
||||
PasswordCode: password.GetVerificationCode(),
|
||||
OldPassword: password.GetCurrentPassword(),
|
||||
Password: password.GetPassword().GetPassword(),
|
||||
EncodedPasswordHash: password.GetHashedPassword().GetHash(),
|
||||
ChangeRequired: password.GetPassword().GetChangeRequired() || password.GetHashedPassword().GetChangeRequired(),
|
||||
}
|
||||
}
|
254
internal/api/grpc/user/v2/human_test.go
Normal file
254
internal/api/grpc/user/v2/human_test.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func Test_patchHumanUserToCommand(t *testing.T) {
|
||||
type args struct {
|
||||
userId string
|
||||
userName *string
|
||||
human *user.UpdateUserRequest_Human
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.ChangeHuman
|
||||
wantErr assert.ErrorAssertionFunc
|
||||
}{{
|
||||
name: "single property",
|
||||
args: args{
|
||||
userId: "userId",
|
||||
human: &user.UpdateUserRequest_Human{
|
||||
Profile: &user.UpdateUserRequest_Human_Profile{
|
||||
GivenName: gu.Ptr("givenName"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &command.ChangeHuman{
|
||||
ID: "userId",
|
||||
Profile: &command.Profile{
|
||||
FirstName: gu.Ptr("givenName"),
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
}, {
|
||||
name: "all properties",
|
||||
args: args{
|
||||
userId: "userId",
|
||||
userName: gu.Ptr("userName"),
|
||||
human: &user.UpdateUserRequest_Human{
|
||||
Profile: &user.UpdateUserRequest_Human_Profile{
|
||||
GivenName: gu.Ptr("givenName"),
|
||||
FamilyName: gu.Ptr("familyName"),
|
||||
NickName: gu.Ptr("nickName"),
|
||||
DisplayName: gu.Ptr("displayName"),
|
||||
PreferredLanguage: gu.Ptr("en-US"),
|
||||
Gender: gu.Ptr(user.Gender_GENDER_FEMALE),
|
||||
},
|
||||
Email: &user.SetHumanEmail{
|
||||
Email: "email@example.com",
|
||||
Verification: &user.SetHumanEmail_IsVerified{
|
||||
IsVerified: true,
|
||||
},
|
||||
},
|
||||
Phone: &user.SetHumanPhone{
|
||||
Phone: "+123456789",
|
||||
Verification: &user.SetHumanPhone_IsVerified{
|
||||
IsVerified: true,
|
||||
},
|
||||
},
|
||||
Password: &user.SetPassword{
|
||||
Verification: &user.SetPassword_CurrentPassword{
|
||||
CurrentPassword: "currentPassword",
|
||||
},
|
||||
PasswordType: &user.SetPassword_Password{
|
||||
Password: &user.Password{
|
||||
Password: "newPassword",
|
||||
ChangeRequired: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &command.ChangeHuman{
|
||||
ID: "userId",
|
||||
Username: gu.Ptr("userName"),
|
||||
Profile: &command.Profile{
|
||||
FirstName: gu.Ptr("givenName"),
|
||||
LastName: gu.Ptr("familyName"),
|
||||
NickName: gu.Ptr("nickName"),
|
||||
DisplayName: gu.Ptr("displayName"),
|
||||
PreferredLanguage: &language.AmericanEnglish,
|
||||
Gender: gu.Ptr(domain.GenderFemale),
|
||||
},
|
||||
Email: &command.Email{
|
||||
Address: "email@example.com",
|
||||
Verified: true,
|
||||
},
|
||||
Phone: &command.Phone{
|
||||
Number: "+123456789",
|
||||
Verified: true,
|
||||
},
|
||||
Password: &command.Password{
|
||||
OldPassword: "currentPassword",
|
||||
Password: "newPassword",
|
||||
ChangeRequired: true,
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
}, {
|
||||
name: "set email and request code",
|
||||
args: args{
|
||||
userId: "userId",
|
||||
human: &user.UpdateUserRequest_Human{
|
||||
Email: &user.SetHumanEmail{
|
||||
Email: "email@example.com",
|
||||
Verification: &user.SetHumanEmail_ReturnCode{
|
||||
ReturnCode: &user.ReturnEmailVerificationCode{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &command.ChangeHuman{
|
||||
ID: "userId",
|
||||
Email: &command.Email{
|
||||
Address: "email@example.com",
|
||||
ReturnCode: true,
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
}, {
|
||||
name: "set email and send code",
|
||||
args: args{
|
||||
userId: "userId",
|
||||
human: &user.UpdateUserRequest_Human{
|
||||
Email: &user.SetHumanEmail{
|
||||
Email: "email@example.com",
|
||||
Verification: &user.SetHumanEmail_SendCode{
|
||||
SendCode: &user.SendEmailVerificationCode{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &command.ChangeHuman{
|
||||
ID: "userId",
|
||||
Email: &command.Email{
|
||||
Address: "email@example.com",
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
}, {
|
||||
name: "set email and send code with template",
|
||||
args: args{
|
||||
userId: "userId",
|
||||
human: &user.UpdateUserRequest_Human{
|
||||
Email: &user.SetHumanEmail{
|
||||
Email: "email@example.com",
|
||||
Verification: &user.SetHumanEmail_SendCode{
|
||||
SendCode: &user.SendEmailVerificationCode{
|
||||
UrlTemplate: gu.Ptr("Code: {{.Code}}"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &command.ChangeHuman{
|
||||
ID: "userId",
|
||||
Email: &command.Email{
|
||||
Address: "email@example.com",
|
||||
URLTemplate: "Code: {{.Code}}",
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
}, {
|
||||
name: "set phone and request code",
|
||||
args: args{
|
||||
userId: "userId",
|
||||
human: &user.UpdateUserRequest_Human{
|
||||
Phone: &user.SetHumanPhone{
|
||||
Phone: "+123456789",
|
||||
Verification: &user.SetHumanPhone_ReturnCode{
|
||||
ReturnCode: &user.ReturnPhoneVerificationCode{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &command.ChangeHuman{
|
||||
ID: "userId",
|
||||
Phone: &command.Phone{
|
||||
Number: "+123456789",
|
||||
ReturnCode: true,
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
}, {
|
||||
name: "set phone and send code",
|
||||
args: args{
|
||||
userId: "userId",
|
||||
human: &user.UpdateUserRequest_Human{
|
||||
Phone: &user.SetHumanPhone{
|
||||
Phone: "+123456789",
|
||||
Verification: &user.SetHumanPhone_SendCode{
|
||||
SendCode: &user.SendPhoneVerificationCode{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &command.ChangeHuman{
|
||||
ID: "userId",
|
||||
Phone: &command.Phone{
|
||||
Number: "+123456789",
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
}, {
|
||||
name: "remove phone, ok",
|
||||
args: args{
|
||||
userId: "userId",
|
||||
human: &user.UpdateUserRequest_Human{
|
||||
Phone: &user.SetHumanPhone{},
|
||||
},
|
||||
},
|
||||
want: &command.ChangeHuman{
|
||||
ID: "userId",
|
||||
Phone: &command.Phone{
|
||||
Remove: true,
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
}, {
|
||||
name: "remove phone with verification, error",
|
||||
args: args{
|
||||
userId: "userId",
|
||||
human: &user.UpdateUserRequest_Human{
|
||||
Phone: &user.SetHumanPhone{
|
||||
Verification: &user.SetHumanPhone_ReturnCode{},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: assert.Error,
|
||||
}}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := updateHumanUserToCommand(tt.args.userId, tt.args.userName, tt.args.human)
|
||||
if !tt.wantErr(t, err, fmt.Sprintf("patchHumanUserToCommand(%v, %v, %v)", tt.args.userId, tt.args.userName, tt.args.human)) {
|
||||
return
|
||||
}
|
||||
if diff := cmp.Diff(tt.want, got, cmpopts.EquateComparable(language.Tag{})); diff != "" {
|
||||
t.Errorf("patchHumanUserToCommand() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -3,6 +3,8 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -11,22 +13,22 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) AddIDPLink(ctx context.Context, req *user.AddIDPLinkRequest) (_ *user.AddIDPLinkResponse, err error) {
|
||||
details, err := s.command.AddUserIDPLink(ctx, req.UserId, "", &command.AddLink{
|
||||
IDPID: req.GetIdpLink().GetIdpId(),
|
||||
DisplayName: req.GetIdpLink().GetUserName(),
|
||||
IDPExternalID: req.GetIdpLink().GetUserId(),
|
||||
func (s *Server) AddIDPLink(ctx context.Context, req *connect.Request[user.AddIDPLinkRequest]) (_ *connect.Response[user.AddIDPLinkResponse], err error) {
|
||||
details, err := s.command.AddUserIDPLink(ctx, req.Msg.GetUserId(), "", &command.AddLink{
|
||||
IDPID: req.Msg.GetIdpLink().GetIdpId(),
|
||||
DisplayName: req.Msg.GetIdpLink().GetUserName(),
|
||||
IDPExternalID: req.Msg.GetIdpLink().GetUserId(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.AddIDPLinkResponse{
|
||||
return connect.NewResponse(&user.AddIDPLinkResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ListIDPLinks(ctx context.Context, req *user.ListIDPLinksRequest) (_ *user.ListIDPLinksResponse, err error) {
|
||||
queries, err := ListLinkedIDPsRequestToQuery(req)
|
||||
func (s *Server) ListIDPLinks(ctx context.Context, req *connect.Request[user.ListIDPLinksRequest]) (_ *connect.Response[user.ListIDPLinksResponse], err error) {
|
||||
queries, err := ListLinkedIDPsRequestToQuery(req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -34,10 +36,10 @@ func (s *Server) ListIDPLinks(ctx context.Context, req *user.ListIDPLinksRequest
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ListIDPLinksResponse{
|
||||
return connect.NewResponse(&user.ListIDPLinksResponse{
|
||||
Result: IDPLinksToPb(res.Links),
|
||||
Details: object.ToListDetails(res.SearchResponse),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func ListLinkedIDPsRequestToQuery(req *user.ListIDPLinksRequest) (*query.IDPUserLinksSearchQuery, error) {
|
||||
@@ -72,14 +74,14 @@ func IDPLinkToPb(link *query.IDPUserLink) *user.IDPLink {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) RemoveIDPLink(ctx context.Context, req *user.RemoveIDPLinkRequest) (*user.RemoveIDPLinkResponse, error) {
|
||||
objectDetails, err := s.command.RemoveUserIDPLink(ctx, RemoveIDPLinkRequestToDomain(ctx, req))
|
||||
func (s *Server) RemoveIDPLink(ctx context.Context, req *connect.Request[user.RemoveIDPLinkRequest]) (*connect.Response[user.RemoveIDPLinkResponse], error) {
|
||||
objectDetails, err := s.command.RemoveUserIDPLink(ctx, RemoveIDPLinkRequestToDomain(ctx, req.Msg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RemoveIDPLinkResponse{
|
||||
return connect.NewResponse(&user.RemoveIDPLinkResponse{
|
||||
Details: object.DomainToDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func RemoveIDPLinkRequestToDomain(ctx context.Context, req *user.RemoveIDPLinkRequest) *domain.UserIDPLink {
|
||||
|
@@ -10,13 +10,13 @@ import (
|
||||
"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/object/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func TestServer_SetEmail(t *testing.T) {
|
||||
func TestServer_Deprecated_SetEmail(t *testing.T) {
|
||||
userID := Instance.CreateHumanUser(CTX).GetUserId()
|
||||
|
||||
tests := []struct {
|
||||
|
659
internal/api/grpc/user/v2/integration_test/key_test.go
Normal file
659
internal/api/grpc/user/v2/integration_test/key_test.go
Normal file
@@ -0,0 +1,659 @@
|
||||
//go:build integration
|
||||
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func TestServer_AddKey(t *testing.T) {
|
||||
resp := Instance.CreateUserTypeMachine(IamCTX)
|
||||
userId := resp.GetId()
|
||||
expirationDate := timestamppb.New(time.Now().Add(time.Hour * 24))
|
||||
type args struct {
|
||||
req *user.AddKeyRequest
|
||||
prepare func(request *user.AddKeyRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
wantEmtpyKey bool
|
||||
}{
|
||||
{
|
||||
name: "add key, user not existing",
|
||||
args: args{
|
||||
&user.AddKeyRequest{
|
||||
UserId: "notexisting",
|
||||
ExpirationDate: expirationDate,
|
||||
},
|
||||
func(request *user.AddKeyRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "generate key pair, ok",
|
||||
args: args{
|
||||
&user.AddKeyRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
},
|
||||
func(request *user.AddKeyRequest) error {
|
||||
request.UserId = userId
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add valid public key, ok",
|
||||
args: args{
|
||||
&user.AddKeyRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
// This is the public key of the tester system user. This must be valid.
|
||||
PublicKey: []byte(`
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzi+FFSJL7f5yw4KTwzgM
|
||||
P34ePGycm/M+kT0M7V4Cgx5V3EaDIvTQKTLfBaEB45zb9LtjIXzDw0rXRoS2hO6t
|
||||
h+CYQCz3KCvh09C0IzxZiB2IS3H/aT+5Bx9EFY+vnAkZjccbyG5YNRvmtOlnvIeI
|
||||
H7qZ0tEwkPfF5GEZNPJPtmy3UGV7iofdVQS1xRj73+aMw5rvH4D8IdyiAC3VekIb
|
||||
pt0Vj0SUX3DwKtog337BzTiPk3aXRF0sbFhQoqdJRI8NqgZjCwjq9yfI5tyxYswn
|
||||
+JGzHGdHvW3idODlmwEt5K2pasiRIWK2OGfq+w0EcltQHabuqEPgZlmhCkRdNfix
|
||||
BwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
`),
|
||||
},
|
||||
func(request *user.AddKeyRequest) error {
|
||||
request.UserId = userId
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantEmtpyKey: true,
|
||||
},
|
||||
{
|
||||
name: "add invalid public key, error",
|
||||
args: args{
|
||||
&user.AddKeyRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
PublicKey: []byte(`
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
|
||||
-----END PUBLIC KEY-----
|
||||
`),
|
||||
},
|
||||
func(request *user.AddKeyRequest) error {
|
||||
request.UserId = userId
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "add key human, error",
|
||||
args: args{
|
||||
&user.AddKeyRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
},
|
||||
func(request *user.AddKeyRequest) error {
|
||||
resp := Instance.CreateUserTypeHuman(IamCTX)
|
||||
request.UserId = resp.Id
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "add another key, ok",
|
||||
args: args{
|
||||
&user.AddKeyRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
},
|
||||
func(request *user.AddKeyRequest) error {
|
||||
request.UserId = userId
|
||||
_, err := Client.AddKey(IamCTX, &user.AddKeyRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
UserId: userId,
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
got, err := Client.AddKey(CTX, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, got.KeyId, "key id is empty")
|
||||
if tt.wantEmtpyKey {
|
||||
assert.Empty(t, got.KeyContent, "key content is not empty")
|
||||
} else {
|
||||
assert.NotEmpty(t, got.KeyContent, "key content is empty")
|
||||
}
|
||||
creationDate := got.CreationDate.AsTime()
|
||||
assert.Greater(t, creationDate, now, "creation date is before the test started")
|
||||
assert.Less(t, creationDate, time.Now(), "creation date is in the future")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_AddKey_Permission(t *testing.T) {
|
||||
OrgCTX := CTX
|
||||
otherOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("AddKey-%s", gofakeit.AppName()), gofakeit.Email())
|
||||
otherOrgUser, err := Client.CreateUser(IamCTX, &user.CreateUserRequest{
|
||||
OrganizationId: otherOrg.OrganizationId,
|
||||
UserType: &user.CreateUserRequest_Machine_{
|
||||
Machine: &user.CreateUserRequest_Machine{
|
||||
Name: gofakeit.Name(),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
request := &user.AddKeyRequest{
|
||||
ExpirationDate: timestamppb.New(time.Now().Add(time.Hour * 24)),
|
||||
UserId: otherOrgUser.GetId(),
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.AddKeyRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "system, ok",
|
||||
args: args{SystemCTX, request},
|
||||
},
|
||||
{
|
||||
name: "instance, ok",
|
||||
args: args{IamCTX, request},
|
||||
},
|
||||
{
|
||||
name: "org, error",
|
||||
args: args{OrgCTX, request},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user, error",
|
||||
args: args{UserCTX, request},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
require.NoError(t, err)
|
||||
got, err := Client.AddKey(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, got.KeyId, "key id is empty")
|
||||
assert.NotEmpty(t, got.KeyContent, "key content is empty")
|
||||
creationDate := got.CreationDate.AsTime()
|
||||
assert.Greater(t, creationDate, now, "creation date is before the test started")
|
||||
assert.Less(t, creationDate, time.Now(), "creation date is in the future")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_RemoveKey(t *testing.T) {
|
||||
resp := Instance.CreateUserTypeMachine(IamCTX)
|
||||
userId := resp.GetId()
|
||||
expirationDate := timestamppb.New(time.Now().Add(time.Hour * 24))
|
||||
type args struct {
|
||||
req *user.RemoveKeyRequest
|
||||
prepare func(request *user.RemoveKeyRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "remove key, user not existing",
|
||||
args: args{
|
||||
&user.RemoveKeyRequest{
|
||||
UserId: "notexisting",
|
||||
},
|
||||
func(request *user.RemoveKeyRequest) error {
|
||||
key, err := Client.AddKey(IamCTX, &user.AddKeyRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
UserId: userId,
|
||||
})
|
||||
request.KeyId = key.GetKeyId()
|
||||
return err
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "remove key, not existing",
|
||||
args: args{
|
||||
&user.RemoveKeyRequest{
|
||||
KeyId: "notexisting",
|
||||
},
|
||||
func(request *user.RemoveKeyRequest) error {
|
||||
request.UserId = userId
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "remove key, ok",
|
||||
args: args{
|
||||
&user.RemoveKeyRequest{},
|
||||
func(request *user.RemoveKeyRequest) error {
|
||||
key, err := Client.AddKey(IamCTX, &user.AddKeyRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
UserId: userId,
|
||||
})
|
||||
request.KeyId = key.GetKeyId()
|
||||
request.UserId = userId
|
||||
return err
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
got, err := Client.RemoveKey(CTX, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
deletionDate := got.DeletionDate.AsTime()
|
||||
assert.Greater(t, deletionDate, now, "creation date is before the test started")
|
||||
assert.Less(t, deletionDate, time.Now(), "creation date is in the future")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_RemoveKey_Permission(t *testing.T) {
|
||||
OrgCTX := CTX
|
||||
otherOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("RemoveKey-%s", gofakeit.AppName()), gofakeit.Email())
|
||||
otherOrgUser, err := Client.CreateUser(IamCTX, &user.CreateUserRequest{
|
||||
OrganizationId: otherOrg.OrganizationId,
|
||||
UserType: &user.CreateUserRequest_Machine_{
|
||||
Machine: &user.CreateUserRequest_Machine{
|
||||
Name: gofakeit.Name(),
|
||||
},
|
||||
},
|
||||
})
|
||||
request := &user.RemoveKeyRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
}
|
||||
prepare := func(request *user.RemoveKeyRequest) error {
|
||||
key, err := Client.AddKey(IamCTX, &user.AddKeyRequest{
|
||||
ExpirationDate: timestamppb.New(time.Now().Add(time.Hour * 24)),
|
||||
UserId: otherOrgUser.GetId(),
|
||||
})
|
||||
request.KeyId = key.GetKeyId()
|
||||
return err
|
||||
}
|
||||
require.NoError(t, err)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.RemoveKeyRequest
|
||||
prepare func(request *user.RemoveKeyRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "system, ok",
|
||||
args: args{SystemCTX, request, prepare},
|
||||
},
|
||||
{
|
||||
name: "instance, ok",
|
||||
args: args{IamCTX, request, prepare},
|
||||
},
|
||||
{
|
||||
name: "org, error",
|
||||
args: args{OrgCTX, request, prepare},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user, error",
|
||||
args: args{UserCTX, request, prepare},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
require.NoError(t, tt.args.prepare(tt.args.req))
|
||||
got, err := Client.RemoveKey(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, got.DeletionDate, "client key is empty")
|
||||
creationDate := got.DeletionDate.AsTime()
|
||||
assert.Greater(t, creationDate, now, "creation date is before the test started")
|
||||
assert.Less(t, creationDate, time.Now(), "creation date is in the future")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ListKeys(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.ListKeysRequest
|
||||
}
|
||||
type testCase struct {
|
||||
name string
|
||||
args args
|
||||
want *user.ListKeysResponse
|
||||
}
|
||||
OrgCTX := CTX
|
||||
otherOrg := Instance.CreateOrganization(SystemCTX, fmt.Sprintf("ListKeys-%s", gofakeit.AppName()), gofakeit.Email())
|
||||
otherOrgUser, err := Client.CreateUser(SystemCTX, &user.CreateUserRequest{
|
||||
OrganizationId: otherOrg.OrganizationId,
|
||||
UserType: &user.CreateUserRequest_Machine_{
|
||||
Machine: &user.CreateUserRequest_Machine{
|
||||
Name: gofakeit.Name(),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
otherOrgUserId := otherOrgUser.GetId()
|
||||
otherUserId := Instance.CreateUserTypeMachine(SystemCTX).GetId()
|
||||
onlySinceTestStartFilter := &user.KeysSearchFilter{Filter: &user.KeysSearchFilter_CreatedDateFilter{CreatedDateFilter: &filter.TimestampFilter{
|
||||
Timestamp: timestamppb.Now(),
|
||||
Method: filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_AFTER_OR_EQUALS,
|
||||
}}}
|
||||
myOrgId := Instance.DefaultOrg.GetId()
|
||||
myUserId := Instance.Users.Get(integration.UserTypeNoPermission).ID
|
||||
expiresInADay := time.Now().Truncate(time.Hour).Add(time.Hour * 24)
|
||||
myDataPoint := setupKeyDataPoint(t, myUserId, myOrgId, expiresInADay)
|
||||
otherUserDataPoint := setupKeyDataPoint(t, otherUserId, myOrgId, expiresInADay)
|
||||
otherOrgDataPointExpiringSoon := setupKeyDataPoint(t, otherOrgUserId, otherOrg.OrganizationId, time.Now().Truncate(time.Hour).Add(time.Hour))
|
||||
otherOrgDataPointExpiringLate := setupKeyDataPoint(t, otherOrgUserId, otherOrg.OrganizationId, expiresInADay.Add(time.Hour*24*30))
|
||||
sortingColumnExpirationDate := user.KeyFieldName_KEY_FIELD_NAME_KEY_EXPIRATION_DATE
|
||||
awaitKeys(t, onlySinceTestStartFilter,
|
||||
otherOrgDataPointExpiringSoon.GetId(),
|
||||
otherOrgDataPointExpiringLate.GetId(),
|
||||
otherUserDataPoint.GetId(),
|
||||
myDataPoint.GetId(),
|
||||
)
|
||||
tests := []testCase{
|
||||
{
|
||||
name: "list all, instance",
|
||||
args: args{
|
||||
IamCTX,
|
||||
&user.ListKeysRequest{Filters: []*user.KeysSearchFilter{onlySinceTestStartFilter}},
|
||||
},
|
||||
want: &user.ListKeysResponse{
|
||||
Result: []*user.Key{
|
||||
otherOrgDataPointExpiringLate,
|
||||
otherOrgDataPointExpiringSoon,
|
||||
otherUserDataPoint,
|
||||
myDataPoint,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 4,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list all, org",
|
||||
args: args{
|
||||
OrgCTX,
|
||||
&user.ListKeysRequest{Filters: []*user.KeysSearchFilter{onlySinceTestStartFilter}},
|
||||
},
|
||||
want: &user.ListKeysResponse{
|
||||
Result: []*user.Key{
|
||||
otherUserDataPoint,
|
||||
myDataPoint,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 2,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list all, user",
|
||||
args: args{
|
||||
UserCTX,
|
||||
&user.ListKeysRequest{Filters: []*user.KeysSearchFilter{onlySinceTestStartFilter}},
|
||||
},
|
||||
want: &user.ListKeysResponse{
|
||||
Result: []*user.Key{
|
||||
myDataPoint,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list by id",
|
||||
args: args{
|
||||
IamCTX,
|
||||
&user.ListKeysRequest{
|
||||
Filters: []*user.KeysSearchFilter{
|
||||
onlySinceTestStartFilter,
|
||||
{
|
||||
Filter: &user.KeysSearchFilter_KeyIdFilter{
|
||||
KeyIdFilter: &filter.IDFilter{Id: otherOrgDataPointExpiringSoon.Id},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.ListKeysResponse{
|
||||
Result: []*user.Key{
|
||||
otherOrgDataPointExpiringSoon,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list all from other org",
|
||||
args: args{
|
||||
IamCTX,
|
||||
&user.ListKeysRequest{
|
||||
Filters: []*user.KeysSearchFilter{
|
||||
onlySinceTestStartFilter,
|
||||
{
|
||||
Filter: &user.KeysSearchFilter_OrganizationIdFilter{
|
||||
OrganizationIdFilter: &filter.IDFilter{Id: otherOrg.OrganizationId},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.ListKeysResponse{
|
||||
Result: []*user.Key{
|
||||
otherOrgDataPointExpiringLate,
|
||||
otherOrgDataPointExpiringSoon,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 2,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by next expiration dates",
|
||||
args: args{
|
||||
IamCTX,
|
||||
&user.ListKeysRequest{
|
||||
Pagination: &filter.PaginationRequest{
|
||||
Asc: true,
|
||||
},
|
||||
SortingColumn: &sortingColumnExpirationDate,
|
||||
Filters: []*user.KeysSearchFilter{
|
||||
onlySinceTestStartFilter,
|
||||
{Filter: &user.KeysSearchFilter_OrganizationIdFilter{OrganizationIdFilter: &filter.IDFilter{Id: otherOrg.OrganizationId}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.ListKeysResponse{
|
||||
Result: []*user.Key{
|
||||
otherOrgDataPointExpiringSoon,
|
||||
otherOrgDataPointExpiringLate,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 2,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get page",
|
||||
args: args{
|
||||
IamCTX,
|
||||
&user.ListKeysRequest{
|
||||
Pagination: &filter.PaginationRequest{
|
||||
Offset: 2,
|
||||
Limit: 2,
|
||||
Asc: true,
|
||||
},
|
||||
Filters: []*user.KeysSearchFilter{
|
||||
onlySinceTestStartFilter,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.ListKeysResponse{
|
||||
Result: []*user.Key{
|
||||
otherOrgDataPointExpiringSoon,
|
||||
otherOrgDataPointExpiringLate,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 4,
|
||||
AppliedLimit: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty list",
|
||||
args: args{
|
||||
UserCTX,
|
||||
&user.ListKeysRequest{
|
||||
Filters: []*user.KeysSearchFilter{
|
||||
{
|
||||
Filter: &user.KeysSearchFilter_KeyIdFilter{
|
||||
KeyIdFilter: &filter.IDFilter{Id: otherUserDataPoint.Id},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.ListKeysResponse{
|
||||
Result: []*user.Key{},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 0,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
t.Run("with permission flag v2", func(t *testing.T) {
|
||||
setPermissionCheckV2Flag(t, true)
|
||||
defer setPermissionCheckV2Flag(t, false)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Client.ListKeys(tt.args.ctx, tt.args.req)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, got.Result, len(tt.want.Result))
|
||||
if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("ListKeys() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("without permission flag v2", func(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Client.ListKeys(tt.args.ctx, tt.args.req)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, got.Result, len(tt.want.Result))
|
||||
// ignore the total result, as this is a known bug with the in-memory permission checks.
|
||||
// The command can't know how many keys exist in the system if the SQL statement has a limit.
|
||||
// This is fixed, once the in-memory permission checks are removed with https://github.com/zitadel/zitadel/issues/9188
|
||||
tt.want.Pagination.TotalResult = got.Pagination.TotalResult
|
||||
if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("ListKeys() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func setupKeyDataPoint(t *testing.T, userId, orgId string, expirationDate time.Time) *user.Key {
|
||||
expirationDatePb := timestamppb.New(expirationDate)
|
||||
newKey, err := Client.AddKey(SystemCTX, &user.AddKeyRequest{
|
||||
UserId: userId,
|
||||
ExpirationDate: expirationDatePb,
|
||||
PublicKey: nil,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return &user.Key{
|
||||
CreationDate: newKey.CreationDate,
|
||||
ChangeDate: newKey.CreationDate,
|
||||
Id: newKey.GetKeyId(),
|
||||
UserId: userId,
|
||||
OrganizationId: orgId,
|
||||
ExpirationDate: expirationDatePb,
|
||||
}
|
||||
}
|
||||
|
||||
func awaitKeys(t *testing.T, sinceTestStartFilter *user.KeysSearchFilter, keyIds ...string) {
|
||||
sortingColumn := user.KeyFieldName_KEY_FIELD_NAME_ID
|
||||
slices.Sort(keyIds)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
result, err := Client.ListKeys(SystemCTX, &user.ListKeysRequest{
|
||||
Filters: []*user.KeysSearchFilter{sinceTestStartFilter},
|
||||
SortingColumn: &sortingColumn,
|
||||
Pagination: &filter.PaginationRequest{
|
||||
Asc: true,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if !assert.Len(collect, result.Result, len(keyIds)) {
|
||||
return
|
||||
}
|
||||
for i := range keyIds {
|
||||
keyId := keyIds[i]
|
||||
require.Equal(collect, keyId, result.Result[i].GetId())
|
||||
}
|
||||
}, 5*time.Second, time.Second, "key not created in time")
|
||||
}
|
@@ -104,7 +104,7 @@ func TestServer_RequestPasswordReset(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_SetPassword(t *testing.T) {
|
||||
func TestServer_Deprecated_SetPassword(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.SetPasswordRequest
|
||||
|
615
internal/api/grpc/user/v2/integration_test/pat_test.go
Normal file
615
internal/api/grpc/user/v2/integration_test/pat_test.go
Normal file
@@ -0,0 +1,615 @@
|
||||
//go:build integration
|
||||
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func TestServer_AddPersonalAccessToken(t *testing.T) {
|
||||
resp := Instance.CreateUserTypeMachine(IamCTX)
|
||||
userId := resp.GetId()
|
||||
expirationDate := timestamppb.New(time.Now().Add(time.Hour * 24))
|
||||
type args struct {
|
||||
req *user.AddPersonalAccessTokenRequest
|
||||
prepare func(request *user.AddPersonalAccessTokenRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "add pat, user not existing",
|
||||
args: args{
|
||||
&user.AddPersonalAccessTokenRequest{
|
||||
UserId: "notexisting",
|
||||
ExpirationDate: expirationDate,
|
||||
},
|
||||
func(request *user.AddPersonalAccessTokenRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "add pat, ok",
|
||||
args: args{
|
||||
&user.AddPersonalAccessTokenRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
},
|
||||
func(request *user.AddPersonalAccessTokenRequest) error {
|
||||
request.UserId = userId
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add pat human, not ok",
|
||||
args: args{
|
||||
&user.AddPersonalAccessTokenRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
},
|
||||
func(request *user.AddPersonalAccessTokenRequest) error {
|
||||
resp := Instance.CreateUserTypeHuman(IamCTX)
|
||||
request.UserId = resp.Id
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "add another pat, ok",
|
||||
args: args{
|
||||
&user.AddPersonalAccessTokenRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
},
|
||||
func(request *user.AddPersonalAccessTokenRequest) error {
|
||||
request.UserId = userId
|
||||
_, err := Client.AddPersonalAccessToken(IamCTX, &user.AddPersonalAccessTokenRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
UserId: userId,
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
got, err := Client.AddPersonalAccessToken(CTX, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, got.TokenId, "id is empty")
|
||||
assert.NotEmpty(t, got.Token, "token is empty")
|
||||
creationDate := got.CreationDate.AsTime()
|
||||
assert.Greater(t, creationDate, now, "creation date is before the test started")
|
||||
assert.Less(t, creationDate, time.Now(), "creation date is in the future")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_AddPersonalAccessToken_Permission(t *testing.T) {
|
||||
OrgCTX := CTX
|
||||
otherOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("AddPersonalAccessToken-%s", gofakeit.AppName()), gofakeit.Email())
|
||||
otherOrgUser, err := Client.CreateUser(IamCTX, &user.CreateUserRequest{
|
||||
OrganizationId: otherOrg.OrganizationId,
|
||||
UserType: &user.CreateUserRequest_Machine_{
|
||||
Machine: &user.CreateUserRequest_Machine{
|
||||
Name: gofakeit.Name(),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
request := &user.AddPersonalAccessTokenRequest{
|
||||
ExpirationDate: timestamppb.New(time.Now().Add(time.Hour * 24)),
|
||||
UserId: otherOrgUser.GetId(),
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.AddPersonalAccessTokenRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "system, ok",
|
||||
args: args{SystemCTX, request},
|
||||
},
|
||||
{
|
||||
name: "instance, ok",
|
||||
args: args{IamCTX, request},
|
||||
},
|
||||
{
|
||||
name: "org, error",
|
||||
args: args{OrgCTX, request},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user, error",
|
||||
args: args{UserCTX, request},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
require.NoError(t, err)
|
||||
got, err := Client.AddPersonalAccessToken(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, got.TokenId, "id is empty")
|
||||
assert.NotEmpty(t, got.Token, "token is empty")
|
||||
creationDate := got.CreationDate.AsTime()
|
||||
assert.Greater(t, creationDate, now, "creation date is before the test started")
|
||||
assert.Less(t, creationDate, time.Now(), "creation date is in the future")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_RemovePersonalAccessToken(t *testing.T) {
|
||||
resp := Instance.CreateUserTypeMachine(IamCTX)
|
||||
userId := resp.GetId()
|
||||
expirationDate := timestamppb.New(time.Now().Add(time.Hour * 24))
|
||||
type args struct {
|
||||
req *user.RemovePersonalAccessTokenRequest
|
||||
prepare func(request *user.RemovePersonalAccessTokenRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "remove pat, user not existing",
|
||||
args: args{
|
||||
&user.RemovePersonalAccessTokenRequest{
|
||||
UserId: "notexisting",
|
||||
},
|
||||
func(request *user.RemovePersonalAccessTokenRequest) error {
|
||||
pat, err := Client.AddPersonalAccessToken(CTX, &user.AddPersonalAccessTokenRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
UserId: userId,
|
||||
})
|
||||
request.TokenId = pat.GetTokenId()
|
||||
return err
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "remove pat, not existing",
|
||||
args: args{
|
||||
&user.RemovePersonalAccessTokenRequest{
|
||||
TokenId: "notexisting",
|
||||
},
|
||||
func(request *user.RemovePersonalAccessTokenRequest) error {
|
||||
request.UserId = userId
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "remove pat, ok",
|
||||
args: args{
|
||||
&user.RemovePersonalAccessTokenRequest{},
|
||||
func(request *user.RemovePersonalAccessTokenRequest) error {
|
||||
pat, err := Client.AddPersonalAccessToken(CTX, &user.AddPersonalAccessTokenRequest{
|
||||
ExpirationDate: expirationDate,
|
||||
UserId: userId,
|
||||
})
|
||||
request.TokenId = pat.GetTokenId()
|
||||
request.UserId = userId
|
||||
return err
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
got, err := Client.RemovePersonalAccessToken(CTX, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
deletionDate := got.DeletionDate.AsTime()
|
||||
assert.Greater(t, deletionDate, now, "creation date is before the test started")
|
||||
assert.Less(t, deletionDate, time.Now(), "creation date is in the future")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_RemovePersonalAccessToken_Permission(t *testing.T) {
|
||||
otherOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("RemovePersonalAccessToken-%s", gofakeit.AppName()), gofakeit.Email())
|
||||
otherOrgUser, err := Client.CreateUser(IamCTX, &user.CreateUserRequest{
|
||||
OrganizationId: otherOrg.OrganizationId,
|
||||
UserType: &user.CreateUserRequest_Machine_{
|
||||
Machine: &user.CreateUserRequest_Machine{
|
||||
Name: gofakeit.Name(),
|
||||
},
|
||||
},
|
||||
})
|
||||
request := &user.RemovePersonalAccessTokenRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
}
|
||||
prepare := func(request *user.RemovePersonalAccessTokenRequest) error {
|
||||
pat, err := Client.AddPersonalAccessToken(IamCTX, &user.AddPersonalAccessTokenRequest{
|
||||
ExpirationDate: timestamppb.New(time.Now().Add(time.Hour * 24)),
|
||||
UserId: otherOrgUser.GetId(),
|
||||
})
|
||||
request.TokenId = pat.GetTokenId()
|
||||
return err
|
||||
}
|
||||
require.NoError(t, err)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.RemovePersonalAccessTokenRequest
|
||||
prepare func(request *user.RemovePersonalAccessTokenRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "system, ok",
|
||||
args: args{SystemCTX, request, prepare},
|
||||
},
|
||||
{
|
||||
name: "instance, ok",
|
||||
args: args{IamCTX, request, prepare},
|
||||
},
|
||||
{
|
||||
name: "org, error",
|
||||
args: args{CTX, request, prepare},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user, error",
|
||||
args: args{UserCTX, request, prepare},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
require.NoError(t, tt.args.prepare(tt.args.req))
|
||||
got, err := Client.RemovePersonalAccessToken(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, got.DeletionDate, "client pat is empty")
|
||||
creationDate := got.DeletionDate.AsTime()
|
||||
assert.Greater(t, creationDate, now, "creation date is before the test started")
|
||||
assert.Less(t, creationDate, time.Now(), "creation date is in the future")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ListPersonalAccessTokens(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.ListPersonalAccessTokensRequest
|
||||
}
|
||||
type testCase struct {
|
||||
name string
|
||||
args args
|
||||
want *user.ListPersonalAccessTokensResponse
|
||||
}
|
||||
OrgCTX := CTX
|
||||
otherOrg := Instance.CreateOrganization(SystemCTX, fmt.Sprintf("ListPersonalAccessTokens-%s", gofakeit.AppName()), gofakeit.Email())
|
||||
otherOrgUser, err := Client.CreateUser(SystemCTX, &user.CreateUserRequest{
|
||||
OrganizationId: otherOrg.OrganizationId,
|
||||
UserType: &user.CreateUserRequest_Machine_{
|
||||
Machine: &user.CreateUserRequest_Machine{
|
||||
Name: gofakeit.Name(),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
otherOrgUserId := otherOrgUser.GetId()
|
||||
otherUserId := Instance.CreateUserTypeMachine(SystemCTX).GetId()
|
||||
onlySinceTestStartFilter := &user.PersonalAccessTokensSearchFilter{Filter: &user.PersonalAccessTokensSearchFilter_CreatedDateFilter{CreatedDateFilter: &filter.TimestampFilter{
|
||||
Timestamp: timestamppb.Now(),
|
||||
Method: filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_AFTER_OR_EQUALS,
|
||||
}}}
|
||||
myOrgId := Instance.DefaultOrg.GetId()
|
||||
myUserId := Instance.Users.Get(integration.UserTypeNoPermission).ID
|
||||
expiresInADay := time.Now().Truncate(time.Hour).Add(time.Hour * 24)
|
||||
myDataPoint := setupPATDataPoint(t, myUserId, myOrgId, expiresInADay)
|
||||
otherUserDataPoint := setupPATDataPoint(t, otherUserId, myOrgId, expiresInADay)
|
||||
otherOrgDataPointExpiringSoon := setupPATDataPoint(t, otherOrgUserId, otherOrg.OrganizationId, time.Now().Truncate(time.Hour).Add(time.Hour))
|
||||
otherOrgDataPointExpiringLate := setupPATDataPoint(t, otherOrgUserId, otherOrg.OrganizationId, expiresInADay.Add(time.Hour*24*30))
|
||||
sortingColumnExpirationDate := user.PersonalAccessTokenFieldName_PERSONAL_ACCESS_TOKEN_FIELD_NAME_EXPIRATION_DATE
|
||||
awaitPersonalAccessTokens(t,
|
||||
onlySinceTestStartFilter,
|
||||
otherOrgDataPointExpiringSoon.GetId(),
|
||||
otherOrgDataPointExpiringLate.GetId(),
|
||||
otherUserDataPoint.GetId(),
|
||||
myDataPoint.GetId(),
|
||||
)
|
||||
tests := []testCase{
|
||||
{
|
||||
name: "list all, instance",
|
||||
args: args{
|
||||
IamCTX,
|
||||
&user.ListPersonalAccessTokensRequest{
|
||||
Filters: []*user.PersonalAccessTokensSearchFilter{onlySinceTestStartFilter},
|
||||
},
|
||||
},
|
||||
want: &user.ListPersonalAccessTokensResponse{
|
||||
Result: []*user.PersonalAccessToken{
|
||||
otherOrgDataPointExpiringLate,
|
||||
otherOrgDataPointExpiringSoon,
|
||||
otherUserDataPoint,
|
||||
myDataPoint,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 4,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list all, org",
|
||||
args: args{
|
||||
OrgCTX,
|
||||
&user.ListPersonalAccessTokensRequest{
|
||||
Filters: []*user.PersonalAccessTokensSearchFilter{onlySinceTestStartFilter},
|
||||
},
|
||||
},
|
||||
want: &user.ListPersonalAccessTokensResponse{
|
||||
Result: []*user.PersonalAccessToken{
|
||||
otherUserDataPoint,
|
||||
myDataPoint,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 2,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list all, user",
|
||||
args: args{
|
||||
UserCTX,
|
||||
&user.ListPersonalAccessTokensRequest{
|
||||
Filters: []*user.PersonalAccessTokensSearchFilter{onlySinceTestStartFilter},
|
||||
},
|
||||
},
|
||||
want: &user.ListPersonalAccessTokensResponse{
|
||||
Result: []*user.PersonalAccessToken{
|
||||
myDataPoint,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list by id",
|
||||
args: args{
|
||||
IamCTX,
|
||||
&user.ListPersonalAccessTokensRequest{
|
||||
Filters: []*user.PersonalAccessTokensSearchFilter{
|
||||
onlySinceTestStartFilter,
|
||||
{
|
||||
Filter: &user.PersonalAccessTokensSearchFilter_TokenIdFilter{
|
||||
TokenIdFilter: &filter.IDFilter{Id: otherOrgDataPointExpiringSoon.Id},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.ListPersonalAccessTokensResponse{
|
||||
Result: []*user.PersonalAccessToken{
|
||||
otherOrgDataPointExpiringSoon,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 1,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list all from other org",
|
||||
args: args{
|
||||
IamCTX,
|
||||
&user.ListPersonalAccessTokensRequest{
|
||||
Filters: []*user.PersonalAccessTokensSearchFilter{
|
||||
onlySinceTestStartFilter,
|
||||
{
|
||||
Filter: &user.PersonalAccessTokensSearchFilter_OrganizationIdFilter{
|
||||
OrganizationIdFilter: &filter.IDFilter{Id: otherOrg.OrganizationId},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
want: &user.ListPersonalAccessTokensResponse{
|
||||
Result: []*user.PersonalAccessToken{
|
||||
otherOrgDataPointExpiringLate,
|
||||
otherOrgDataPointExpiringSoon,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 2,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by next expiration dates",
|
||||
args: args{
|
||||
IamCTX,
|
||||
&user.ListPersonalAccessTokensRequest{
|
||||
Pagination: &filter.PaginationRequest{
|
||||
Asc: true,
|
||||
},
|
||||
SortingColumn: &sortingColumnExpirationDate,
|
||||
Filters: []*user.PersonalAccessTokensSearchFilter{
|
||||
onlySinceTestStartFilter,
|
||||
{Filter: &user.PersonalAccessTokensSearchFilter_OrganizationIdFilter{OrganizationIdFilter: &filter.IDFilter{Id: otherOrg.OrganizationId}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.ListPersonalAccessTokensResponse{
|
||||
Result: []*user.PersonalAccessToken{
|
||||
otherOrgDataPointExpiringSoon,
|
||||
otherOrgDataPointExpiringLate,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 2,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "get page",
|
||||
args: args{
|
||||
IamCTX,
|
||||
&user.ListPersonalAccessTokensRequest{
|
||||
Pagination: &filter.PaginationRequest{
|
||||
Offset: 2,
|
||||
Limit: 2,
|
||||
Asc: true,
|
||||
},
|
||||
Filters: []*user.PersonalAccessTokensSearchFilter{
|
||||
onlySinceTestStartFilter,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.ListPersonalAccessTokensResponse{
|
||||
Result: []*user.PersonalAccessToken{
|
||||
otherOrgDataPointExpiringSoon,
|
||||
otherOrgDataPointExpiringLate,
|
||||
},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 4,
|
||||
AppliedLimit: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty list",
|
||||
args: args{
|
||||
UserCTX,
|
||||
&user.ListPersonalAccessTokensRequest{
|
||||
Filters: []*user.PersonalAccessTokensSearchFilter{
|
||||
{
|
||||
Filter: &user.PersonalAccessTokensSearchFilter_TokenIdFilter{
|
||||
TokenIdFilter: &filter.IDFilter{Id: otherUserDataPoint.Id},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: &user.ListPersonalAccessTokensResponse{
|
||||
Result: []*user.PersonalAccessToken{},
|
||||
Pagination: &filter.PaginationResponse{
|
||||
TotalResult: 0,
|
||||
AppliedLimit: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
t.Run("with permission flag v2", func(t *testing.T) {
|
||||
setPermissionCheckV2Flag(t, true)
|
||||
defer setPermissionCheckV2Flag(t, false)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Client.ListPersonalAccessTokens(tt.args.ctx, tt.args.req)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, got.Result, len(tt.want.Result))
|
||||
if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("ListPersonalAccessTokens() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("without permission flag v2", func(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Client.ListPersonalAccessTokens(tt.args.ctx, tt.args.req)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, got.Result, len(tt.want.Result))
|
||||
// ignore the total result, as this is a known bug with the in-memory permission checks.
|
||||
// The command can't know how many keys exist in the system if the SQL statement has a limit.
|
||||
// This is fixed, once the in-memory permission checks are removed with https://github.com/zitadel/zitadel/issues/9188
|
||||
tt.want.Pagination.TotalResult = got.Pagination.TotalResult
|
||||
if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" {
|
||||
t.Errorf("ListPersonalAccessTokens() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func setupPATDataPoint(t *testing.T, userId, orgId string, expirationDate time.Time) *user.PersonalAccessToken {
|
||||
expirationDatePb := timestamppb.New(expirationDate)
|
||||
newPersonalAccessToken, err := Client.AddPersonalAccessToken(SystemCTX, &user.AddPersonalAccessTokenRequest{
|
||||
UserId: userId,
|
||||
ExpirationDate: expirationDatePb,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return &user.PersonalAccessToken{
|
||||
CreationDate: newPersonalAccessToken.CreationDate,
|
||||
ChangeDate: newPersonalAccessToken.CreationDate,
|
||||
Id: newPersonalAccessToken.GetTokenId(),
|
||||
UserId: userId,
|
||||
OrganizationId: orgId,
|
||||
ExpirationDate: expirationDatePb,
|
||||
}
|
||||
}
|
||||
|
||||
func awaitPersonalAccessTokens(t *testing.T, sinceTestStartFilter *user.PersonalAccessTokensSearchFilter, patIds ...string) {
|
||||
sortingColumn := user.PersonalAccessTokenFieldName_PERSONAL_ACCESS_TOKEN_FIELD_NAME_ID
|
||||
slices.Sort(patIds)
|
||||
require.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
result, err := Client.ListPersonalAccessTokens(SystemCTX, &user.ListPersonalAccessTokensRequest{
|
||||
Filters: []*user.PersonalAccessTokensSearchFilter{sinceTestStartFilter},
|
||||
SortingColumn: &sortingColumn,
|
||||
Pagination: &filter.PaginationRequest{
|
||||
Asc: true,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
if !assert.Len(collect, result.Result, len(patIds)) {
|
||||
return
|
||||
}
|
||||
for i := range patIds {
|
||||
patId := patIds[i]
|
||||
require.Equal(collect, patId, result.Result[i].GetId())
|
||||
}
|
||||
}, 5*time.Second, time.Second, "pat not created in time")
|
||||
}
|
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func TestServer_SetPhone(t *testing.T) {
|
||||
func TestServer_Deprecated_SetPhone(t *testing.T) {
|
||||
userID := Instance.CreateHumanUser(CTX).GetUserId()
|
||||
|
||||
tests := []struct {
|
||||
@@ -249,7 +249,7 @@ func TestServer_VerifyPhone(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_RemovePhone(t *testing.T) {
|
||||
func TestServer_Deprecated_RemovePhone(t *testing.T) {
|
||||
userResp := Instance.CreateHumanUser(CTX)
|
||||
failResp := Instance.CreateHumanUserNoPhone(CTX)
|
||||
otherUser := Instance.CreateHumanUser(CTX).GetUserId()
|
||||
|
347
internal/api/grpc/user/v2/integration_test/secret_test.go
Normal file
347
internal/api/grpc/user/v2/integration_test/secret_test.go
Normal file
@@ -0,0 +1,347 @@
|
||||
//go:build integration
|
||||
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func TestServer_AddSecret(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.AddSecretRequest
|
||||
prepare func(request *user.AddSecretRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "add secret, user not existing",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.AddSecretRequest{
|
||||
UserId: "notexisting",
|
||||
},
|
||||
func(request *user.AddSecretRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "add secret, ok",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.AddSecretRequest{},
|
||||
func(request *user.AddSecretRequest) error {
|
||||
resp := Instance.CreateUserTypeMachine(CTX)
|
||||
request.UserId = resp.GetId()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add secret human, not ok",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.AddSecretRequest{},
|
||||
func(request *user.AddSecretRequest) error {
|
||||
resp := Instance.CreateUserTypeMachine(CTX)
|
||||
request.UserId = resp.GetId()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "overwrite secret, ok",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.AddSecretRequest{},
|
||||
func(request *user.AddSecretRequest) error {
|
||||
resp := Instance.CreateUserTypeMachine(CTX)
|
||||
request.UserId = resp.GetId()
|
||||
_, err := Client.AddSecret(CTX, &user.AddSecretRequest{
|
||||
UserId: resp.GetId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
got, err := Client.AddSecret(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, got.ClientSecret, "client secret is empty")
|
||||
creationDate := got.CreationDate.AsTime()
|
||||
assert.Greater(t, creationDate, now, "creation date is before the test started")
|
||||
assert.Less(t, creationDate, time.Now(), "creation date is in the future")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_AddSecret_Permission(t *testing.T) {
|
||||
otherOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("AddSecret-%s", gofakeit.AppName()), gofakeit.Email())
|
||||
otherOrgUser, err := Instance.Client.UserV2.CreateUser(IamCTX, &user.CreateUserRequest{
|
||||
OrganizationId: otherOrg.OrganizationId,
|
||||
UserType: &user.CreateUserRequest_Machine_{
|
||||
Machine: &user.CreateUserRequest_Machine{
|
||||
Name: gofakeit.Name(),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.AddSecretRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "system, ok",
|
||||
args: args{
|
||||
SystemCTX,
|
||||
&user.AddSecretRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "instance, ok",
|
||||
args: args{
|
||||
IamCTX,
|
||||
&user.AddSecretRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org, error",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.AddSecretRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user, error",
|
||||
args: args{
|
||||
UserCTX,
|
||||
&user.AddSecretRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
require.NoError(t, err)
|
||||
got, err := Client.AddSecret(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, got.ClientSecret, "client secret is empty")
|
||||
creationDate := got.CreationDate.AsTime()
|
||||
assert.Greater(t, creationDate, now, "creation date is before the test started")
|
||||
assert.Less(t, creationDate, time.Now(), "creation date is in the future")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_RemoveSecret(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.RemoveSecretRequest
|
||||
prepare func(request *user.RemoveSecretRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "remove secret, user not existing",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RemoveSecretRequest{
|
||||
UserId: "notexisting",
|
||||
},
|
||||
func(request *user.RemoveSecretRequest) error { return nil },
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "remove secret, not existing",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RemoveSecretRequest{},
|
||||
func(request *user.RemoveSecretRequest) error {
|
||||
resp := Instance.CreateUserTypeMachine(CTX)
|
||||
request.UserId = resp.GetId()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "remove secret, ok",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RemoveSecretRequest{},
|
||||
func(request *user.RemoveSecretRequest) error {
|
||||
resp := Instance.CreateUserTypeMachine(CTX)
|
||||
request.UserId = resp.GetId()
|
||||
_, err := Instance.Client.UserV2.AddSecret(CTX, &user.AddSecretRequest{
|
||||
UserId: resp.GetId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
got, err := Client.RemoveSecret(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
deletionDate := got.DeletionDate.AsTime()
|
||||
assert.Greater(t, deletionDate, now, "creation date is before the test started")
|
||||
assert.Less(t, deletionDate, time.Now(), "creation date is in the future")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_RemoveSecret_Permission(t *testing.T) {
|
||||
otherOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("RemoveSecret-%s", gofakeit.AppName()), gofakeit.Email())
|
||||
otherOrgUser, err := Instance.Client.UserV2.CreateUser(IamCTX, &user.CreateUserRequest{
|
||||
OrganizationId: otherOrg.OrganizationId,
|
||||
UserType: &user.CreateUserRequest_Machine_{
|
||||
Machine: &user.CreateUserRequest_Machine{
|
||||
Name: gofakeit.Name(),
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.RemoveSecretRequest
|
||||
prepare func(request *user.RemoveSecretRequest) error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "system, ok",
|
||||
args: args{
|
||||
SystemCTX,
|
||||
&user.RemoveSecretRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
},
|
||||
func(request *user.RemoveSecretRequest) error {
|
||||
_, err := Instance.Client.UserV2.AddSecret(IamCTX, &user.AddSecretRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "instance, ok",
|
||||
args: args{
|
||||
IamCTX,
|
||||
&user.RemoveSecretRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
},
|
||||
func(request *user.RemoveSecretRequest) error {
|
||||
_, err := Instance.Client.UserV2.AddSecret(IamCTX, &user.AddSecretRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org, error",
|
||||
args: args{
|
||||
CTX,
|
||||
&user.RemoveSecretRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
},
|
||||
func(request *user.RemoveSecretRequest) error {
|
||||
_, err := Instance.Client.UserV2.AddSecret(IamCTX, &user.AddSecretRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "user, error",
|
||||
args: args{
|
||||
UserCTX,
|
||||
&user.RemoveSecretRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
},
|
||||
func(request *user.RemoveSecretRequest) error {
|
||||
_, err := Instance.Client.UserV2.AddSecret(IamCTX, &user.AddSecretRequest{
|
||||
UserId: otherOrgUser.GetId(),
|
||||
})
|
||||
return err
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
now := time.Now()
|
||||
require.NoError(t, tt.args.prepare(tt.args.req))
|
||||
got, err := Client.RemoveSecret(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, got.DeletionDate, "client secret is empty")
|
||||
creationDate := got.DeletionDate.AsTime()
|
||||
assert.Greater(t, creationDate, now, "creation date is before the test started")
|
||||
assert.Less(t, creationDate, time.Now(), "creation date is in the future")
|
||||
})
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
oidc_pkg "github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
@@ -32,18 +33,18 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) StartIdentityProviderIntent(ctx context.Context, req *user.StartIdentityProviderIntentRequest) (_ *user.StartIdentityProviderIntentResponse, err error) {
|
||||
switch t := req.GetContent().(type) {
|
||||
func (s *Server) StartIdentityProviderIntent(ctx context.Context, req *connect.Request[user.StartIdentityProviderIntentRequest]) (_ *connect.Response[user.StartIdentityProviderIntentResponse], err error) {
|
||||
switch t := req.Msg.GetContent().(type) {
|
||||
case *user.StartIdentityProviderIntentRequest_Urls:
|
||||
return s.startIDPIntent(ctx, req.GetIdpId(), t.Urls)
|
||||
return s.startIDPIntent(ctx, req.Msg.GetIdpId(), t.Urls)
|
||||
case *user.StartIdentityProviderIntentRequest_Ldap:
|
||||
return s.startLDAPIntent(ctx, req.GetIdpId(), t.Ldap)
|
||||
return s.startLDAPIntent(ctx, req.Msg.GetIdpId(), t.Ldap)
|
||||
default:
|
||||
return nil, zerrors.ThrowUnimplementedf(nil, "USERv2-S2g21", "type oneOf %T in method StartIdentityProviderIntent not implemented", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.RedirectURLs) (*user.StartIdentityProviderIntentResponse, error) {
|
||||
func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.RedirectURLs) (*connect.Response[user.StartIdentityProviderIntentResponse], error) {
|
||||
state, session, err := s.command.AuthFromProvider(ctx, idpID, s.idpCallback(ctx), s.samlRootURL(ctx, idpID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -52,22 +53,31 @@ func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.Re
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, redirect := session.GetAuth(ctx)
|
||||
if redirect {
|
||||
return &user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_AuthUrl{AuthUrl: content},
|
||||
}, nil
|
||||
auth, err := session.GetAuth(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_PostForm{
|
||||
PostForm: []byte(content),
|
||||
},
|
||||
}, nil
|
||||
switch a := auth.(type) {
|
||||
case *idp.RedirectAuth:
|
||||
return connect.NewResponse(&user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_AuthUrl{AuthUrl: a.RedirectURL},
|
||||
}), nil
|
||||
case *idp.FormAuth:
|
||||
return connect.NewResponse(&user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_FormData{
|
||||
FormData: &user.FormData{
|
||||
Url: a.URL,
|
||||
Fields: a.Fields,
|
||||
},
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "USERv2-3g2j3", "type oneOf %T in method StartIdentityProviderIntent not implemented", auth)
|
||||
}
|
||||
|
||||
func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*user.StartIdentityProviderIntentResponse, error) {
|
||||
func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*connect.Response[user.StartIdentityProviderIntentResponse], error) {
|
||||
intentWriteModel, details, err := s.command.CreateIntent(ctx, "", idpID, "", "", authz.GetInstance(ctx).InstanceID(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -83,7 +93,7 @@ func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredenti
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.StartIdentityProviderIntentResponse{
|
||||
return connect.NewResponse(&user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_IdpIntent{
|
||||
IdpIntent: &user.IDPIntent{
|
||||
@@ -92,7 +102,7 @@ func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredenti
|
||||
UserId: userID,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) checkLinkedExternalUser(ctx context.Context, idpID, externalUserID string) (string, error) {
|
||||
@@ -141,12 +151,12 @@ func (s *Server) ldapLogin(ctx context.Context, idpID, username, password string
|
||||
return externalUser, userID, session, nil
|
||||
}
|
||||
|
||||
func (s *Server) RetrieveIdentityProviderIntent(ctx context.Context, req *user.RetrieveIdentityProviderIntentRequest) (_ *user.RetrieveIdentityProviderIntentResponse, err error) {
|
||||
intent, err := s.command.GetIntentWriteModel(ctx, req.GetIdpIntentId(), "")
|
||||
func (s *Server) RetrieveIdentityProviderIntent(ctx context.Context, req *connect.Request[user.RetrieveIdentityProviderIntentRequest]) (_ *connect.Response[user.RetrieveIdentityProviderIntentResponse], err error) {
|
||||
intent, err := s.command.GetIntentWriteModel(ctx, req.Msg.GetIdpIntentId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.checkIntentToken(req.GetIdpIntentToken(), intent.AggregateID); err != nil {
|
||||
if err := s.checkIntentToken(req.Msg.GetIdpIntentToken(), intent.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if intent.State != domain.IDPIntentStateSucceeded {
|
||||
@@ -194,7 +204,7 @@ func (s *Server) RetrieveIdentityProviderIntent(ctx context.Context, req *user.R
|
||||
}
|
||||
idpIntent.AddHumanUser = idpUserToAddHumanUser(idpUser, idpIntent.IdpInformation.IdpId)
|
||||
}
|
||||
return idpIntent, nil
|
||||
return connect.NewResponse(idpIntent), nil
|
||||
}
|
||||
|
||||
type rawUserMapper struct {
|
||||
|
63
internal/api/grpc/user/v2/key.go
Normal file
63
internal/api/grpc/user/v2/key.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) AddKey(ctx context.Context, req *connect.Request[user.AddKeyRequest]) (*connect.Response[user.AddKeyResponse], error) {
|
||||
newMachineKey := &command.MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: req.Msg.GetUserId(),
|
||||
},
|
||||
ExpirationDate: req.Msg.GetExpirationDate().AsTime(),
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
PermissionCheck: s.command.NewPermissionCheckUserWrite(ctx),
|
||||
}
|
||||
newMachineKey.PublicKey = req.Msg.GetPublicKey()
|
||||
|
||||
pubkeySupplied := len(newMachineKey.PublicKey) > 0
|
||||
details, err := s.command.AddUserMachineKey(ctx, newMachineKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Return key details only if the pubkey wasn't supplied, otherwise the user already has
|
||||
// private key locally
|
||||
var keyDetails []byte
|
||||
if !pubkeySupplied {
|
||||
var err error
|
||||
keyDetails, err = newMachineKey.Detail()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return connect.NewResponse(&user.AddKeyResponse{
|
||||
KeyId: newMachineKey.KeyID,
|
||||
KeyContent: keyDetails,
|
||||
CreationDate: timestamppb.New(details.EventDate),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveKey(ctx context.Context, req *connect.Request[user.RemoveKeyRequest]) (*connect.Response[user.RemoveKeyResponse], error) {
|
||||
machineKey := &command.MachineKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: req.Msg.GetUserId(),
|
||||
},
|
||||
PermissionCheck: s.command.NewPermissionCheckUserWrite(ctx),
|
||||
KeyID: req.Msg.GetKeyId(),
|
||||
}
|
||||
objectDetails, err := s.command.RemoveUserMachineKey(ctx, machineKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&user.RemoveKeyResponse{
|
||||
DeletionDate: timestamppb.New(objectDetails.EventDate),
|
||||
}), nil
|
||||
}
|
125
internal/api/grpc/user/v2/key_query.go
Normal file
125
internal/api/grpc/user/v2/key_query.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
filter_pb "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) ListKeys(ctx context.Context, req *connect.Request[user.ListKeysRequest]) (*connect.Response[user.ListKeysResponse], error) {
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Msg.GetPagination())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filters, err := keyFiltersToQueries(req.Msg.GetFilters())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
search := &query.AuthNKeySearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: authnKeyFieldNameToSortingColumn(req.Msg.SortingColumn),
|
||||
},
|
||||
Queries: filters,
|
||||
}
|
||||
result, err := s.query.SearchAuthNKeys(ctx, search, query.JoinFilterUserMachine, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := &user.ListKeysResponse{
|
||||
Result: make([]*user.Key, len(result.AuthNKeys)),
|
||||
Pagination: filter.QueryToPaginationPb(search.SearchRequest, result.SearchResponse),
|
||||
}
|
||||
for i, key := range result.AuthNKeys {
|
||||
resp.Result[i] = &user.Key{
|
||||
CreationDate: timestamppb.New(key.CreationDate),
|
||||
ChangeDate: timestamppb.New(key.ChangeDate),
|
||||
Id: key.ID,
|
||||
UserId: key.AggregateID,
|
||||
OrganizationId: key.ResourceOwner,
|
||||
ExpirationDate: timestamppb.New(key.Expiration),
|
||||
}
|
||||
}
|
||||
return connect.NewResponse(resp), nil
|
||||
}
|
||||
|
||||
func keyFiltersToQueries(filters []*user.KeysSearchFilter) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(filters))
|
||||
for i, filter := range filters {
|
||||
q[i], err = keyFilterToQuery(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func keyFilterToQuery(filter *user.KeysSearchFilter) (query.SearchQuery, error) {
|
||||
switch q := filter.Filter.(type) {
|
||||
case *user.KeysSearchFilter_CreatedDateFilter:
|
||||
return authnKeyCreatedFilterToQuery(q.CreatedDateFilter)
|
||||
case *user.KeysSearchFilter_ExpirationDateFilter:
|
||||
return authnKeyExpirationFilterToQuery(q.ExpirationDateFilter)
|
||||
case *user.KeysSearchFilter_KeyIdFilter:
|
||||
return authnKeyIdFilterToQuery(q.KeyIdFilter)
|
||||
case *user.KeysSearchFilter_UserIdFilter:
|
||||
return authnKeyUserIdFilterToQuery(q.UserIdFilter)
|
||||
case *user.KeysSearchFilter_OrganizationIdFilter:
|
||||
return authnKeyOrgIdFilterToQuery(q.OrganizationIdFilter)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func authnKeyIdFilterToQuery(f *filter_pb.IDFilter) (query.SearchQuery, error) {
|
||||
return query.NewAuthNKeyIDQuery(f.Id)
|
||||
}
|
||||
|
||||
func authnKeyUserIdFilterToQuery(f *filter_pb.IDFilter) (query.SearchQuery, error) {
|
||||
return query.NewAuthNKeyIdentifyerQuery(f.Id)
|
||||
}
|
||||
|
||||
func authnKeyOrgIdFilterToQuery(f *filter_pb.IDFilter) (query.SearchQuery, error) {
|
||||
return query.NewAuthNKeyResourceOwnerQuery(f.Id)
|
||||
}
|
||||
|
||||
func authnKeyCreatedFilterToQuery(f *filter_pb.TimestampFilter) (query.SearchQuery, error) {
|
||||
return query.NewAuthNKeyCreationDateQuery(f.Timestamp.AsTime(), filter.TimestampMethodPbToQuery(f.Method))
|
||||
}
|
||||
|
||||
func authnKeyExpirationFilterToQuery(f *filter_pb.TimestampFilter) (query.SearchQuery, error) {
|
||||
return query.NewAuthNKeyExpirationDateDateQuery(f.Timestamp.AsTime(), filter.TimestampMethodPbToQuery(f.Method))
|
||||
}
|
||||
|
||||
// authnKeyFieldNameToSortingColumn defaults to the creation date because this ensures deterministic pagination
|
||||
func authnKeyFieldNameToSortingColumn(field *user.KeyFieldName) query.Column {
|
||||
if field == nil {
|
||||
return query.AuthNKeyColumnCreationDate
|
||||
}
|
||||
switch *field {
|
||||
case user.KeyFieldName_KEY_FIELD_NAME_UNSPECIFIED:
|
||||
return query.AuthNKeyColumnCreationDate
|
||||
case user.KeyFieldName_KEY_FIELD_NAME_ID:
|
||||
return query.AuthNKeyColumnID
|
||||
case user.KeyFieldName_KEY_FIELD_NAME_USER_ID:
|
||||
return query.AuthNKeyColumnIdentifier
|
||||
case user.KeyFieldName_KEY_FIELD_NAME_ORGANIZATION_ID:
|
||||
return query.AuthNKeyColumnResourceOwner
|
||||
case user.KeyFieldName_KEY_FIELD_NAME_CREATED_DATE:
|
||||
return query.AuthNKeyColumnCreationDate
|
||||
case user.KeyFieldName_KEY_FIELD_NAME_KEY_EXPIRATION_DATE:
|
||||
return query.AuthNKeyColumnExpiration
|
||||
default:
|
||||
return query.AuthNKeyColumnCreationDate
|
||||
}
|
||||
}
|
60
internal/api/grpc/user/v2/machine.go
Normal file
60
internal/api/grpc/user/v2/machine.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) createUserTypeMachine(ctx context.Context, machinePb *user.CreateUserRequest_Machine, orgId, userName, userId string) (*connect.Response[user.CreateUserResponse], error) {
|
||||
cmd := &command.Machine{
|
||||
Username: userName,
|
||||
Name: machinePb.Name,
|
||||
Description: machinePb.GetDescription(),
|
||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
ResourceOwner: orgId,
|
||||
AggregateID: userId,
|
||||
},
|
||||
}
|
||||
details, err := s.command.AddMachine(
|
||||
ctx,
|
||||
cmd,
|
||||
nil,
|
||||
s.command.NewPermissionCheckUserWrite(ctx),
|
||||
command.AddMachineWithUsernameToIDFallback(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&user.CreateUserResponse{
|
||||
Id: cmd.AggregateID,
|
||||
CreationDate: timestamppb.New(details.EventDate),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) updateUserTypeMachine(ctx context.Context, machinePb *user.UpdateUserRequest_Machine, userId string, userName *string) (*connect.Response[user.UpdateUserResponse], error) {
|
||||
cmd := updateMachineUserToCommand(userId, userName, machinePb)
|
||||
err := s.command.ChangeUserMachine(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&user.UpdateUserResponse{
|
||||
ChangeDate: timestamppb.New(cmd.Details.EventDate),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func updateMachineUserToCommand(userId string, userName *string, machine *user.UpdateUserRequest_Machine) *command.ChangeMachine {
|
||||
return &command.ChangeMachine{
|
||||
ID: userId,
|
||||
Username: userName,
|
||||
Name: machine.Name,
|
||||
Description: machine.Description,
|
||||
}
|
||||
}
|
62
internal/api/grpc/user/v2/machine_test.go
Normal file
62
internal/api/grpc/user/v2/machine_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/muhlemmer/gu"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func Test_patchMachineUserToCommand(t *testing.T) {
|
||||
type args struct {
|
||||
userId string
|
||||
userName *string
|
||||
machine *user.UpdateUserRequest_Machine
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.ChangeMachine
|
||||
}{{
|
||||
name: "single property",
|
||||
args: args{
|
||||
userId: "userId",
|
||||
machine: &user.UpdateUserRequest_Machine{
|
||||
Name: gu.Ptr("name"),
|
||||
},
|
||||
},
|
||||
want: &command.ChangeMachine{
|
||||
ID: "userId",
|
||||
Name: gu.Ptr("name"),
|
||||
},
|
||||
}, {
|
||||
name: "all properties",
|
||||
args: args{
|
||||
userId: "userId",
|
||||
userName: gu.Ptr("userName"),
|
||||
machine: &user.UpdateUserRequest_Machine{
|
||||
Name: gu.Ptr("name"),
|
||||
Description: gu.Ptr("description"),
|
||||
},
|
||||
},
|
||||
want: &command.ChangeMachine{
|
||||
ID: "userId",
|
||||
Username: gu.Ptr("userName"),
|
||||
Name: gu.Ptr("name"),
|
||||
Description: gu.Ptr("description"),
|
||||
},
|
||||
}}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := updateMachineUserToCommand(tt.args.userId, tt.args.userName, tt.args.machine)
|
||||
if diff := cmp.Diff(tt.want, got, cmpopts.EquateComparable(language.Tag{})); diff != "" {
|
||||
t.Errorf("patchMachineUserToCommand() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -3,39 +3,41 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) AddOTPSMS(ctx context.Context, req *user.AddOTPSMSRequest) (*user.AddOTPSMSResponse, error) {
|
||||
details, err := s.command.AddHumanOTPSMS(ctx, req.GetUserId(), "")
|
||||
func (s *Server) AddOTPSMS(ctx context.Context, req *connect.Request[user.AddOTPSMSRequest]) (*connect.Response[user.AddOTPSMSResponse], error) {
|
||||
details, err := s.command.AddHumanOTPSMS(ctx, req.Msg.GetUserId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.AddOTPSMSResponse{Details: object.DomainToDetailsPb(details)}, nil
|
||||
return connect.NewResponse(&user.AddOTPSMSResponse{Details: object.DomainToDetailsPb(details)}), nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveOTPSMS(ctx context.Context, req *user.RemoveOTPSMSRequest) (*user.RemoveOTPSMSResponse, error) {
|
||||
objectDetails, err := s.command.RemoveHumanOTPSMS(ctx, req.GetUserId(), "")
|
||||
func (s *Server) RemoveOTPSMS(ctx context.Context, req *connect.Request[user.RemoveOTPSMSRequest]) (*connect.Response[user.RemoveOTPSMSResponse], error) {
|
||||
objectDetails, err := s.command.RemoveHumanOTPSMS(ctx, req.Msg.GetUserId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RemoveOTPSMSResponse{Details: object.DomainToDetailsPb(objectDetails)}, nil
|
||||
return connect.NewResponse(&user.RemoveOTPSMSResponse{Details: object.DomainToDetailsPb(objectDetails)}), nil
|
||||
}
|
||||
|
||||
func (s *Server) AddOTPEmail(ctx context.Context, req *user.AddOTPEmailRequest) (*user.AddOTPEmailResponse, error) {
|
||||
details, err := s.command.AddHumanOTPEmail(ctx, req.GetUserId(), "")
|
||||
func (s *Server) AddOTPEmail(ctx context.Context, req *connect.Request[user.AddOTPEmailRequest]) (*connect.Response[user.AddOTPEmailResponse], error) {
|
||||
details, err := s.command.AddHumanOTPEmail(ctx, req.Msg.GetUserId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.AddOTPEmailResponse{Details: object.DomainToDetailsPb(details)}, nil
|
||||
return connect.NewResponse(&user.AddOTPEmailResponse{Details: object.DomainToDetailsPb(details)}), nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Server) RemoveOTPEmail(ctx context.Context, req *user.RemoveOTPEmailRequest) (*user.RemoveOTPEmailResponse, error) {
|
||||
objectDetails, err := s.command.RemoveHumanOTPEmail(ctx, req.GetUserId(), "")
|
||||
func (s *Server) RemoveOTPEmail(ctx context.Context, req *connect.Request[user.RemoveOTPEmailRequest]) (*connect.Response[user.RemoveOTPEmailResponse], error) {
|
||||
objectDetails, err := s.command.RemoveHumanOTPEmail(ctx, req.Msg.GetUserId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RemoveOTPEmailResponse{Details: object.DomainToDetailsPb(objectDetails)}, nil
|
||||
return connect.NewResponse(&user.RemoveOTPEmailResponse{Details: object.DomainToDetailsPb(objectDetails)}), nil
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
@@ -13,17 +14,17 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) RegisterPasskey(ctx context.Context, req *user.RegisterPasskeyRequest) (resp *user.RegisterPasskeyResponse, err error) {
|
||||
func (s *Server) RegisterPasskey(ctx context.Context, req *connect.Request[user.RegisterPasskeyRequest]) (resp *connect.Response[user.RegisterPasskeyResponse], err error) {
|
||||
var (
|
||||
authenticator = passkeyAuthenticatorToDomain(req.GetAuthenticator())
|
||||
authenticator = passkeyAuthenticatorToDomain(req.Msg.GetAuthenticator())
|
||||
)
|
||||
if code := req.GetCode(); code != nil {
|
||||
if code := req.Msg.GetCode(); code != nil {
|
||||
return passkeyRegistrationDetailsToPb(
|
||||
s.command.RegisterUserPasskeyWithCode(ctx, req.GetUserId(), "", authenticator, code.Id, code.Code, req.GetDomain(), s.userCodeAlg),
|
||||
s.command.RegisterUserPasskeyWithCode(ctx, req.Msg.GetUserId(), "", authenticator, code.Id, code.Code, req.Msg.GetDomain(), s.userCodeAlg),
|
||||
)
|
||||
}
|
||||
return passkeyRegistrationDetailsToPb(
|
||||
s.command.RegisterUserPasskey(ctx, req.GetUserId(), "", req.GetDomain(), authenticator),
|
||||
s.command.RegisterUserPasskey(ctx, req.Msg.GetUserId(), "", req.Msg.GetDomain(), authenticator),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -51,86 +52,86 @@ func webAuthNRegistrationDetailsToPb(details *domain.WebAuthNRegistrationDetails
|
||||
return object.DomainToDetailsPb(details.ObjectDetails), options, nil
|
||||
}
|
||||
|
||||
func passkeyRegistrationDetailsToPb(details *domain.WebAuthNRegistrationDetails, err error) (*user.RegisterPasskeyResponse, error) {
|
||||
func passkeyRegistrationDetailsToPb(details *domain.WebAuthNRegistrationDetails, err error) (*connect.Response[user.RegisterPasskeyResponse], error) {
|
||||
objectDetails, options, err := webAuthNRegistrationDetailsToPb(details, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RegisterPasskeyResponse{
|
||||
return connect.NewResponse(&user.RegisterPasskeyResponse{
|
||||
Details: objectDetails,
|
||||
PasskeyId: details.ID,
|
||||
PublicKeyCredentialCreationOptions: options,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) VerifyPasskeyRegistration(ctx context.Context, req *user.VerifyPasskeyRegistrationRequest) (*user.VerifyPasskeyRegistrationResponse, error) {
|
||||
pkc, err := req.GetPublicKeyCredential().MarshalJSON()
|
||||
func (s *Server) VerifyPasskeyRegistration(ctx context.Context, req *connect.Request[user.VerifyPasskeyRegistrationRequest]) (*connect.Response[user.VerifyPasskeyRegistrationResponse], error) {
|
||||
pkc, err := req.Msg.GetPublicKeyCredential().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "USERv2-Pha2o", "Errors.Internal")
|
||||
}
|
||||
objectDetails, err := s.command.HumanHumanPasswordlessSetup(ctx, req.GetUserId(), "", req.GetPasskeyName(), "", pkc)
|
||||
objectDetails, err := s.command.HumanHumanPasswordlessSetup(ctx, req.Msg.GetUserId(), "", req.Msg.GetPasskeyName(), "", pkc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyPasskeyRegistrationResponse{
|
||||
return connect.NewResponse(&user.VerifyPasskeyRegistrationResponse{
|
||||
Details: object.DomainToDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) CreatePasskeyRegistrationLink(ctx context.Context, req *user.CreatePasskeyRegistrationLinkRequest) (resp *user.CreatePasskeyRegistrationLinkResponse, err error) {
|
||||
switch medium := req.Medium.(type) {
|
||||
func (s *Server) CreatePasskeyRegistrationLink(ctx context.Context, req *connect.Request[user.CreatePasskeyRegistrationLinkRequest]) (resp *connect.Response[user.CreatePasskeyRegistrationLinkResponse], err error) {
|
||||
switch medium := req.Msg.Medium.(type) {
|
||||
case nil:
|
||||
return passkeyDetailsToPb(
|
||||
s.command.AddUserPasskeyCode(ctx, req.GetUserId(), "", s.userCodeAlg),
|
||||
s.command.AddUserPasskeyCode(ctx, req.Msg.GetUserId(), "", s.userCodeAlg),
|
||||
)
|
||||
case *user.CreatePasskeyRegistrationLinkRequest_SendLink:
|
||||
return passkeyDetailsToPb(
|
||||
s.command.AddUserPasskeyCodeURLTemplate(ctx, req.GetUserId(), "", s.userCodeAlg, medium.SendLink.GetUrlTemplate()),
|
||||
s.command.AddUserPasskeyCodeURLTemplate(ctx, req.Msg.GetUserId(), "", s.userCodeAlg, medium.SendLink.GetUrlTemplate()),
|
||||
)
|
||||
case *user.CreatePasskeyRegistrationLinkRequest_ReturnCode:
|
||||
return passkeyCodeDetailsToPb(
|
||||
s.command.AddUserPasskeyCodeReturn(ctx, req.GetUserId(), "", s.userCodeAlg),
|
||||
s.command.AddUserPasskeyCodeReturn(ctx, req.Msg.GetUserId(), "", s.userCodeAlg),
|
||||
)
|
||||
default:
|
||||
return nil, zerrors.ThrowUnimplementedf(nil, "USERv2-gaD8y", "verification oneOf %T in method CreatePasskeyRegistrationLink not implemented", medium)
|
||||
}
|
||||
}
|
||||
|
||||
func passkeyDetailsToPb(details *domain.ObjectDetails, err error) (*user.CreatePasskeyRegistrationLinkResponse, error) {
|
||||
func passkeyDetailsToPb(details *domain.ObjectDetails, err error) (*connect.Response[user.CreatePasskeyRegistrationLinkResponse], error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.CreatePasskeyRegistrationLinkResponse{
|
||||
return connect.NewResponse(&user.CreatePasskeyRegistrationLinkResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func passkeyCodeDetailsToPb(details *domain.PasskeyCodeDetails, err error) (*user.CreatePasskeyRegistrationLinkResponse, error) {
|
||||
func passkeyCodeDetailsToPb(details *domain.PasskeyCodeDetails, err error) (*connect.Response[user.CreatePasskeyRegistrationLinkResponse], error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.CreatePasskeyRegistrationLinkResponse{
|
||||
return connect.NewResponse(&user.CreatePasskeyRegistrationLinkResponse{
|
||||
Details: object.DomainToDetailsPb(details.ObjectDetails),
|
||||
Code: &user.PasskeyRegistrationCode{
|
||||
Id: details.CodeID,
|
||||
Code: details.Code,
|
||||
},
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) RemovePasskey(ctx context.Context, req *user.RemovePasskeyRequest) (*user.RemovePasskeyResponse, error) {
|
||||
objectDetails, err := s.command.HumanRemovePasswordless(ctx, req.GetUserId(), req.GetPasskeyId(), "")
|
||||
func (s *Server) RemovePasskey(ctx context.Context, req *connect.Request[user.RemovePasskeyRequest]) (*connect.Response[user.RemovePasskeyResponse], error) {
|
||||
objectDetails, err := s.command.HumanRemovePasswordless(ctx, req.Msg.GetUserId(), req.Msg.GetPasskeyId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RemovePasskeyResponse{
|
||||
return connect.NewResponse(&user.RemovePasskeyResponse{
|
||||
Details: object.DomainToDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ListPasskeys(ctx context.Context, req *user.ListPasskeysRequest) (*user.ListPasskeysResponse, error) {
|
||||
func (s *Server) ListPasskeys(ctx context.Context, req *connect.Request[user.ListPasskeysRequest]) (*connect.Response[user.ListPasskeysResponse], error) {
|
||||
query := new(query.UserAuthMethodSearchQueries)
|
||||
err := query.AppendUserIDQuery(req.UserId)
|
||||
err := query.AppendUserIDQuery(req.Msg.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -146,10 +147,10 @@ func (s *Server) ListPasskeys(ctx context.Context, req *user.ListPasskeysRequest
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ListPasskeysResponse{
|
||||
return connect.NewResponse(&user.ListPasskeysResponse{
|
||||
Details: object.ToListDetails(authMethods.SearchResponse),
|
||||
Result: authMethodsToPasskeyPb(authMethods),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func authMethodsToPasskeyPb(methods *query.AuthMethods) []*user.Passkey {
|
||||
|
@@ -123,11 +123,11 @@ func Test_passkeyRegistrationDetailsToPb(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := passkeyRegistrationDetailsToPb(tt.args.details, tt.args.err)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
if !proto.Equal(tt.want, got) {
|
||||
if tt.want != nil && !proto.Equal(tt.want, got.Msg) {
|
||||
t.Errorf("Not equal:\nExpected\n%s\nActual:%s", tt.want, got)
|
||||
}
|
||||
if tt.want != nil {
|
||||
grpc.AllFieldsSet(t, got.ProtoReflect())
|
||||
grpc.AllFieldsSet(t, got.Msg.ProtoReflect())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -181,7 +181,9 @@ func Test_passkeyDetailsToPb(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := passkeyDetailsToPb(tt.args.details, tt.args.err)
|
||||
require.ErrorIs(t, err, tt.args.err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
if tt.want != nil {
|
||||
assert.Equal(t, tt.want, got.Msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -242,9 +244,9 @@ func Test_passkeyCodeDetailsToPb(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := passkeyCodeDetailsToPb(tt.args.details, tt.args.err)
|
||||
require.ErrorIs(t, err, tt.args.err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
if tt.want != nil {
|
||||
grpc.AllFieldsSet(t, got.ProtoReflect())
|
||||
assert.Equal(t, tt.want, got.Msg)
|
||||
grpc.AllFieldsSet(t, got.Msg.ProtoReflect())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -3,23 +3,25 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) PasswordReset(ctx context.Context, req *user.PasswordResetRequest) (_ *user.PasswordResetResponse, err error) {
|
||||
func (s *Server) PasswordReset(ctx context.Context, req *connect.Request[user.PasswordResetRequest]) (_ *connect.Response[user.PasswordResetResponse], err error) {
|
||||
var details *domain.ObjectDetails
|
||||
var code *string
|
||||
|
||||
switch m := req.GetMedium().(type) {
|
||||
switch m := req.Msg.GetMedium().(type) {
|
||||
case *user.PasswordResetRequest_SendLink:
|
||||
details, code, err = s.command.RequestPasswordResetURLTemplate(ctx, req.GetUserId(), m.SendLink.GetUrlTemplate(), notificationTypeToDomain(m.SendLink.GetNotificationType()))
|
||||
details, code, err = s.command.RequestPasswordResetURLTemplate(ctx, req.Msg.GetUserId(), m.SendLink.GetUrlTemplate(), notificationTypeToDomain(m.SendLink.GetNotificationType()))
|
||||
case *user.PasswordResetRequest_ReturnCode:
|
||||
details, code, err = s.command.RequestPasswordResetReturnCode(ctx, req.GetUserId())
|
||||
details, code, err = s.command.RequestPasswordResetReturnCode(ctx, req.Msg.GetUserId())
|
||||
case nil:
|
||||
details, code, err = s.command.RequestPasswordReset(ctx, req.GetUserId())
|
||||
details, code, err = s.command.RequestPasswordReset(ctx, req.Msg.GetUserId())
|
||||
default:
|
||||
err = zerrors.ThrowUnimplementedf(nil, "USERv2-SDeeg", "verification oneOf %T in method RequestPasswordReset not implemented", m)
|
||||
}
|
||||
@@ -27,10 +29,10 @@ func (s *Server) PasswordReset(ctx context.Context, req *user.PasswordResetReque
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.PasswordResetResponse{
|
||||
return connect.NewResponse(&user.PasswordResetResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
VerificationCode: code,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func notificationTypeToDomain(notificationType user.NotificationType) domain.NotificationType {
|
||||
@@ -46,16 +48,16 @@ func notificationTypeToDomain(notificationType user.NotificationType) domain.Not
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) SetPassword(ctx context.Context, req *user.SetPasswordRequest) (_ *user.SetPasswordResponse, err error) {
|
||||
func (s *Server) SetPassword(ctx context.Context, req *connect.Request[user.SetPasswordRequest]) (_ *connect.Response[user.SetPasswordResponse], err error) {
|
||||
var details *domain.ObjectDetails
|
||||
|
||||
switch v := req.GetVerification().(type) {
|
||||
switch v := req.Msg.GetVerification().(type) {
|
||||
case *user.SetPasswordRequest_CurrentPassword:
|
||||
details, err = s.command.ChangePassword(ctx, "", req.GetUserId(), v.CurrentPassword, req.GetNewPassword().GetPassword(), "", req.GetNewPassword().GetChangeRequired())
|
||||
details, err = s.command.ChangePassword(ctx, "", req.Msg.GetUserId(), v.CurrentPassword, req.Msg.GetNewPassword().GetPassword(), "", req.Msg.GetNewPassword().GetChangeRequired())
|
||||
case *user.SetPasswordRequest_VerificationCode:
|
||||
details, err = s.command.SetPasswordWithVerifyCode(ctx, "", req.GetUserId(), v.VerificationCode, req.GetNewPassword().GetPassword(), "", req.GetNewPassword().GetChangeRequired())
|
||||
details, err = s.command.SetPasswordWithVerifyCode(ctx, "", req.Msg.GetUserId(), v.VerificationCode, req.Msg.GetNewPassword().GetPassword(), "", req.Msg.GetNewPassword().GetChangeRequired())
|
||||
case nil:
|
||||
details, err = s.command.SetPassword(ctx, "", req.GetUserId(), req.GetNewPassword().GetPassword(), req.GetNewPassword().GetChangeRequired())
|
||||
details, err = s.command.SetPassword(ctx, "", req.Msg.GetUserId(), req.Msg.GetNewPassword().GetPassword(), req.Msg.GetNewPassword().GetChangeRequired())
|
||||
default:
|
||||
err = zerrors.ThrowUnimplementedf(nil, "USERv2-SFdf2", "verification oneOf %T in method SetPasswordRequest not implemented", v)
|
||||
}
|
||||
@@ -63,7 +65,7 @@ func (s *Server) SetPassword(ctx context.Context, req *user.SetPasswordRequest)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.SetPasswordResponse{
|
||||
return connect.NewResponse(&user.SetPasswordResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
57
internal/api/grpc/user/v2/pat.go
Normal file
57
internal/api/grpc/user/v2/pat.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
z_oidc "github.com/zitadel/zitadel/internal/api/oidc"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) AddPersonalAccessToken(ctx context.Context, req *connect.Request[user.AddPersonalAccessTokenRequest]) (*connect.Response[user.AddPersonalAccessTokenResponse], error) {
|
||||
newPat := &command.PersonalAccessToken{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: req.Msg.GetUserId(),
|
||||
},
|
||||
PermissionCheck: s.command.NewPermissionCheckUserWrite(ctx),
|
||||
ExpirationDate: req.Msg.GetExpirationDate().AsTime(),
|
||||
Scopes: []string{
|
||||
oidc.ScopeOpenID,
|
||||
oidc.ScopeProfile,
|
||||
z_oidc.ScopeUserMetaData,
|
||||
z_oidc.ScopeResourceOwner,
|
||||
},
|
||||
AllowedUserType: domain.UserTypeMachine,
|
||||
}
|
||||
details, err := s.command.AddPersonalAccessToken(ctx, newPat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&user.AddPersonalAccessTokenResponse{
|
||||
CreationDate: timestamppb.New(details.EventDate),
|
||||
TokenId: newPat.TokenID,
|
||||
Token: newPat.Token,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) RemovePersonalAccessToken(ctx context.Context, req *connect.Request[user.RemovePersonalAccessTokenRequest]) (*connect.Response[user.RemovePersonalAccessTokenResponse], error) {
|
||||
objectDetails, err := s.command.RemovePersonalAccessToken(ctx, &command.PersonalAccessToken{
|
||||
TokenID: req.Msg.GetTokenId(),
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: req.Msg.GetUserId(),
|
||||
},
|
||||
PermissionCheck: s.command.NewPermissionCheckUserWrite(ctx),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&user.RemovePersonalAccessTokenResponse{
|
||||
DeletionDate: timestamppb.New(objectDetails.EventDate),
|
||||
}), nil
|
||||
}
|
124
internal/api/grpc/user/v2/pat_query.go
Normal file
124
internal/api/grpc/user/v2/pat_query.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
filter_pb "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) ListPersonalAccessTokens(ctx context.Context, req *connect.Request[user.ListPersonalAccessTokensRequest]) (*connect.Response[user.ListPersonalAccessTokensResponse], error) {
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Msg.GetPagination())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filters, err := patFiltersToQueries(req.Msg.GetFilters())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
search := &query.PersonalAccessTokenSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: authnPersonalAccessTokenFieldNameToSortingColumn(req.Msg.SortingColumn),
|
||||
},
|
||||
Queries: filters,
|
||||
}
|
||||
result, err := s.query.SearchPersonalAccessTokens(ctx, search, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := &user.ListPersonalAccessTokensResponse{
|
||||
Result: make([]*user.PersonalAccessToken, len(result.PersonalAccessTokens)),
|
||||
Pagination: filter.QueryToPaginationPb(search.SearchRequest, result.SearchResponse),
|
||||
}
|
||||
for i, pat := range result.PersonalAccessTokens {
|
||||
resp.Result[i] = &user.PersonalAccessToken{
|
||||
CreationDate: timestamppb.New(pat.CreationDate),
|
||||
ChangeDate: timestamppb.New(pat.ChangeDate),
|
||||
Id: pat.ID,
|
||||
UserId: pat.UserID,
|
||||
OrganizationId: pat.ResourceOwner,
|
||||
ExpirationDate: timestamppb.New(pat.Expiration),
|
||||
}
|
||||
}
|
||||
return connect.NewResponse(resp), nil
|
||||
}
|
||||
|
||||
func patFiltersToQueries(filters []*user.PersonalAccessTokensSearchFilter) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(filters))
|
||||
for i, filter := range filters {
|
||||
q[i], err = patFilterToQuery(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func patFilterToQuery(filter *user.PersonalAccessTokensSearchFilter) (query.SearchQuery, error) {
|
||||
switch q := filter.Filter.(type) {
|
||||
case *user.PersonalAccessTokensSearchFilter_CreatedDateFilter:
|
||||
return authnPersonalAccessTokenCreatedFilterToQuery(q.CreatedDateFilter)
|
||||
case *user.PersonalAccessTokensSearchFilter_ExpirationDateFilter:
|
||||
return authnPersonalAccessTokenExpirationFilterToQuery(q.ExpirationDateFilter)
|
||||
case *user.PersonalAccessTokensSearchFilter_TokenIdFilter:
|
||||
return authnPersonalAccessTokenIdFilterToQuery(q.TokenIdFilter)
|
||||
case *user.PersonalAccessTokensSearchFilter_UserIdFilter:
|
||||
return authnPersonalAccessTokenUserIdFilterToQuery(q.UserIdFilter)
|
||||
case *user.PersonalAccessTokensSearchFilter_OrganizationIdFilter:
|
||||
return authnPersonalAccessTokenOrgIdFilterToQuery(q.OrganizationIdFilter)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "GRPC-vR9nC", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func authnPersonalAccessTokenIdFilterToQuery(f *filter_pb.IDFilter) (query.SearchQuery, error) {
|
||||
return query.NewPersonalAccessTokenIDQuery(f.Id)
|
||||
}
|
||||
|
||||
func authnPersonalAccessTokenUserIdFilterToQuery(f *filter_pb.IDFilter) (query.SearchQuery, error) {
|
||||
return query.NewPersonalAccessTokenUserIDSearchQuery(f.Id)
|
||||
}
|
||||
|
||||
func authnPersonalAccessTokenOrgIdFilterToQuery(f *filter_pb.IDFilter) (query.SearchQuery, error) {
|
||||
return query.NewPersonalAccessTokenResourceOwnerSearchQuery(f.Id)
|
||||
}
|
||||
|
||||
func authnPersonalAccessTokenCreatedFilterToQuery(f *filter_pb.TimestampFilter) (query.SearchQuery, error) {
|
||||
return query.NewPersonalAccessTokenCreationDateQuery(f.Timestamp.AsTime(), filter.TimestampMethodPbToQuery(f.Method))
|
||||
}
|
||||
|
||||
func authnPersonalAccessTokenExpirationFilterToQuery(f *filter_pb.TimestampFilter) (query.SearchQuery, error) {
|
||||
return query.NewPersonalAccessTokenExpirationDateDateQuery(f.Timestamp.AsTime(), filter.TimestampMethodPbToQuery(f.Method))
|
||||
}
|
||||
|
||||
// authnPersonalAccessTokenFieldNameToSortingColumn defaults to the creation date because this ensures deterministic pagination
|
||||
func authnPersonalAccessTokenFieldNameToSortingColumn(field *user.PersonalAccessTokenFieldName) query.Column {
|
||||
if field == nil {
|
||||
return query.PersonalAccessTokenColumnCreationDate
|
||||
}
|
||||
switch *field {
|
||||
case user.PersonalAccessTokenFieldName_PERSONAL_ACCESS_TOKEN_FIELD_NAME_UNSPECIFIED:
|
||||
return query.PersonalAccessTokenColumnCreationDate
|
||||
case user.PersonalAccessTokenFieldName_PERSONAL_ACCESS_TOKEN_FIELD_NAME_ID:
|
||||
return query.PersonalAccessTokenColumnID
|
||||
case user.PersonalAccessTokenFieldName_PERSONAL_ACCESS_TOKEN_FIELD_NAME_USER_ID:
|
||||
return query.PersonalAccessTokenColumnUserID
|
||||
case user.PersonalAccessTokenFieldName_PERSONAL_ACCESS_TOKEN_FIELD_NAME_ORGANIZATION_ID:
|
||||
return query.PersonalAccessTokenColumnResourceOwner
|
||||
case user.PersonalAccessTokenFieldName_PERSONAL_ACCESS_TOKEN_FIELD_NAME_CREATED_DATE:
|
||||
return query.PersonalAccessTokenColumnCreationDate
|
||||
case user.PersonalAccessTokenFieldName_PERSONAL_ACCESS_TOKEN_FIELD_NAME_EXPIRATION_DATE:
|
||||
return query.PersonalAccessTokenColumnExpiration
|
||||
default:
|
||||
return query.PersonalAccessTokenColumnCreationDate
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -11,18 +12,18 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) SetPhone(ctx context.Context, req *user.SetPhoneRequest) (resp *user.SetPhoneResponse, err error) {
|
||||
func (s *Server) SetPhone(ctx context.Context, req *connect.Request[user.SetPhoneRequest]) (resp *connect.Response[user.SetPhoneResponse], err error) {
|
||||
var phone *domain.Phone
|
||||
|
||||
switch v := req.GetVerification().(type) {
|
||||
switch v := req.Msg.GetVerification().(type) {
|
||||
case *user.SetPhoneRequest_SendCode:
|
||||
phone, err = s.command.ChangeUserPhone(ctx, req.GetUserId(), req.GetPhone(), s.userCodeAlg)
|
||||
phone, err = s.command.ChangeUserPhone(ctx, req.Msg.GetUserId(), req.Msg.GetPhone(), s.userCodeAlg)
|
||||
case *user.SetPhoneRequest_ReturnCode:
|
||||
phone, err = s.command.ChangeUserPhoneReturnCode(ctx, req.GetUserId(), req.GetPhone(), s.userCodeAlg)
|
||||
phone, err = s.command.ChangeUserPhoneReturnCode(ctx, req.Msg.GetUserId(), req.Msg.GetPhone(), s.userCodeAlg)
|
||||
case *user.SetPhoneRequest_IsVerified:
|
||||
phone, err = s.command.ChangeUserPhoneVerified(ctx, req.GetUserId(), req.GetPhone())
|
||||
phone, err = s.command.ChangeUserPhoneVerified(ctx, req.Msg.GetUserId(), req.Msg.GetPhone())
|
||||
case nil:
|
||||
phone, err = s.command.ChangeUserPhone(ctx, req.GetUserId(), req.GetPhone(), s.userCodeAlg)
|
||||
phone, err = s.command.ChangeUserPhone(ctx, req.Msg.GetUserId(), req.Msg.GetPhone(), s.userCodeAlg)
|
||||
default:
|
||||
err = zerrors.ThrowUnimplementedf(nil, "USERv2-Ahng0", "verification oneOf %T in method SetPhone not implemented", v)
|
||||
}
|
||||
@@ -30,42 +31,42 @@ func (s *Server) SetPhone(ctx context.Context, req *user.SetPhoneRequest) (resp
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.SetPhoneResponse{
|
||||
return connect.NewResponse(&user.SetPhoneResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: phone.Sequence,
|
||||
ChangeDate: timestamppb.New(phone.ChangeDate),
|
||||
ResourceOwner: phone.ResourceOwner,
|
||||
},
|
||||
VerificationCode: phone.PlainCode,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) RemovePhone(ctx context.Context, req *user.RemovePhoneRequest) (resp *user.RemovePhoneResponse, err error) {
|
||||
func (s *Server) RemovePhone(ctx context.Context, req *connect.Request[user.RemovePhoneRequest]) (resp *connect.Response[user.RemovePhoneResponse], err error) {
|
||||
details, err := s.command.RemoveUserPhone(ctx,
|
||||
req.GetUserId(),
|
||||
req.Msg.GetUserId(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.RemovePhoneResponse{
|
||||
return connect.NewResponse(&user.RemovePhoneResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: details.Sequence,
|
||||
ChangeDate: timestamppb.New(details.EventDate),
|
||||
ResourceOwner: details.ResourceOwner,
|
||||
},
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ResendPhoneCode(ctx context.Context, req *user.ResendPhoneCodeRequest) (resp *user.ResendPhoneCodeResponse, err error) {
|
||||
func (s *Server) ResendPhoneCode(ctx context.Context, req *connect.Request[user.ResendPhoneCodeRequest]) (resp *connect.Response[user.ResendPhoneCodeResponse], err error) {
|
||||
var phone *domain.Phone
|
||||
switch v := req.GetVerification().(type) {
|
||||
switch v := req.Msg.GetVerification().(type) {
|
||||
case *user.ResendPhoneCodeRequest_SendCode:
|
||||
phone, err = s.command.ResendUserPhoneCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||
phone, err = s.command.ResendUserPhoneCode(ctx, req.Msg.GetUserId(), s.userCodeAlg)
|
||||
case *user.ResendPhoneCodeRequest_ReturnCode:
|
||||
phone, err = s.command.ResendUserPhoneCodeReturnCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||
phone, err = s.command.ResendUserPhoneCodeReturnCode(ctx, req.Msg.GetUserId(), s.userCodeAlg)
|
||||
case nil:
|
||||
phone, err = s.command.ResendUserPhoneCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||
phone, err = s.command.ResendUserPhoneCode(ctx, req.Msg.GetUserId(), s.userCodeAlg)
|
||||
default:
|
||||
err = zerrors.ThrowUnimplementedf(nil, "USERv2-ResendUserPhoneCode", "verification oneOf %T in method SetPhone not implemented", v)
|
||||
}
|
||||
@@ -73,30 +74,30 @@ func (s *Server) ResendPhoneCode(ctx context.Context, req *user.ResendPhoneCodeR
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.ResendPhoneCodeResponse{
|
||||
return connect.NewResponse(&user.ResendPhoneCodeResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: phone.Sequence,
|
||||
ChangeDate: timestamppb.New(phone.ChangeDate),
|
||||
ResourceOwner: phone.ResourceOwner,
|
||||
},
|
||||
VerificationCode: phone.PlainCode,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) VerifyPhone(ctx context.Context, req *user.VerifyPhoneRequest) (*user.VerifyPhoneResponse, error) {
|
||||
func (s *Server) VerifyPhone(ctx context.Context, req *connect.Request[user.VerifyPhoneRequest]) (*connect.Response[user.VerifyPhoneResponse], error) {
|
||||
details, err := s.command.VerifyUserPhone(ctx,
|
||||
req.GetUserId(),
|
||||
req.GetVerificationCode(),
|
||||
req.Msg.GetUserId(),
|
||||
req.Msg.GetVerificationCode(),
|
||||
s.userCodeAlg,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyPhoneResponse{
|
||||
return connect.NewResponse(&user.VerifyPhoneResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: details.Sequence,
|
||||
ChangeDate: timestamppb.New(details.EventDate),
|
||||
ResourceOwner: details.ResourceOwner,
|
||||
},
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
40
internal/api/grpc/user/v2/secret.go
Normal file
40
internal/api/grpc/user/v2/secret.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) AddSecret(ctx context.Context, req *connect.Request[user.AddSecretRequest]) (*connect.Response[user.AddSecretResponse], error) {
|
||||
newSecret := &command.GenerateMachineSecret{
|
||||
PermissionCheck: s.command.NewPermissionCheckUserWrite(ctx),
|
||||
}
|
||||
details, err := s.command.GenerateMachineSecret(ctx, req.Msg.GetUserId(), "", newSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&user.AddSecretResponse{
|
||||
CreationDate: timestamppb.New(details.EventDate),
|
||||
ClientSecret: newSecret.ClientSecret,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveSecret(ctx context.Context, req *connect.Request[user.RemoveSecretRequest]) (*connect.Response[user.RemoveSecretResponse], error) {
|
||||
details, err := s.command.RemoveMachineSecret(
|
||||
ctx,
|
||||
req.Msg.GetUserId(),
|
||||
"",
|
||||
s.command.NewPermissionCheckUserWrite(ctx),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&user.RemoveSecretResponse{
|
||||
DeletionDate: timestamppb.New(details.EventDate),
|
||||
}), nil
|
||||
}
|
@@ -2,28 +2,32 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2/userconnect"
|
||||
)
|
||||
|
||||
var _ user.UserServiceServer = (*Server)(nil)
|
||||
var _ userconnect.UserServiceHandler = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
user.UnimplementedUserServiceServer
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
userCodeAlg crypto.EncryptionAlgorithm
|
||||
idpAlg crypto.EncryptionAlgorithm
|
||||
idpCallback func(ctx context.Context) string
|
||||
samlRootURL func(ctx context.Context, idpID string) string
|
||||
systemDefaults systemdefaults.SystemDefaults
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
userCodeAlg crypto.EncryptionAlgorithm
|
||||
idpAlg crypto.EncryptionAlgorithm
|
||||
idpCallback func(ctx context.Context) string
|
||||
samlRootURL func(ctx context.Context, idpID string) string
|
||||
|
||||
assetAPIPrefix func(context.Context) string
|
||||
|
||||
@@ -33,6 +37,7 @@ type Server struct {
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
systemDefaults systemdefaults.SystemDefaults,
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
userCodeAlg crypto.EncryptionAlgorithm,
|
||||
@@ -43,6 +48,7 @@ func CreateServer(
|
||||
checkPermission domain.PermissionCheck,
|
||||
) *Server {
|
||||
return &Server{
|
||||
systemDefaults: systemDefaults,
|
||||
command: command,
|
||||
query: query,
|
||||
userCodeAlg: userCodeAlg,
|
||||
@@ -54,8 +60,12 @@ func CreateServer(
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||
user.RegisterUserServiceServer(grpcServer, s)
|
||||
func (s *Server) RegisterConnectServer(interceptors ...connect.Interceptor) (string, http.Handler) {
|
||||
return userconnect.NewUserServiceHandler(s, connect.WithInterceptors(interceptors...))
|
||||
}
|
||||
|
||||
func (s *Server) FileDescriptor() protoreflect.FileDescriptor {
|
||||
return user.File_zitadel_user_v2_user_service_proto
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
|
@@ -3,42 +3,44 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) RegisterTOTP(ctx context.Context, req *user.RegisterTOTPRequest) (*user.RegisterTOTPResponse, error) {
|
||||
func (s *Server) RegisterTOTP(ctx context.Context, req *connect.Request[user.RegisterTOTPRequest]) (*connect.Response[user.RegisterTOTPResponse], error) {
|
||||
return totpDetailsToPb(
|
||||
s.command.AddUserTOTP(ctx, req.GetUserId(), ""),
|
||||
s.command.AddUserTOTP(ctx, req.Msg.GetUserId(), ""),
|
||||
)
|
||||
}
|
||||
|
||||
func totpDetailsToPb(totp *domain.TOTP, err error) (*user.RegisterTOTPResponse, error) {
|
||||
func totpDetailsToPb(totp *domain.TOTP, err error) (*connect.Response[user.RegisterTOTPResponse], error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RegisterTOTPResponse{
|
||||
return connect.NewResponse(&user.RegisterTOTPResponse{
|
||||
Details: object.DomainToDetailsPb(totp.ObjectDetails),
|
||||
Uri: totp.URI,
|
||||
Secret: totp.Secret,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) VerifyTOTPRegistration(ctx context.Context, req *user.VerifyTOTPRegistrationRequest) (*user.VerifyTOTPRegistrationResponse, error) {
|
||||
objectDetails, err := s.command.CheckUserTOTP(ctx, req.GetUserId(), req.GetCode(), "")
|
||||
func (s *Server) VerifyTOTPRegistration(ctx context.Context, req *connect.Request[user.VerifyTOTPRegistrationRequest]) (*connect.Response[user.VerifyTOTPRegistrationResponse], error) {
|
||||
objectDetails, err := s.command.CheckUserTOTP(ctx, req.Msg.GetUserId(), req.Msg.GetCode(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyTOTPRegistrationResponse{
|
||||
return connect.NewResponse(&user.VerifyTOTPRegistrationResponse{
|
||||
Details: object.DomainToDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveTOTP(ctx context.Context, req *user.RemoveTOTPRequest) (*user.RemoveTOTPResponse, error) {
|
||||
objectDetails, err := s.command.HumanRemoveTOTP(ctx, req.GetUserId(), "")
|
||||
func (s *Server) RemoveTOTP(ctx context.Context, req *connect.Request[user.RemoveTOTPRequest]) (*connect.Response[user.RemoveTOTPResponse], error) {
|
||||
objectDetails, err := s.command.HumanRemoveTOTP(ctx, req.Msg.GetUserId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RemoveTOTPResponse{Details: object.DomainToDetailsPb(objectDetails)}, nil
|
||||
return connect.NewResponse(&user.RemoveTOTPResponse{Details: object.DomainToDetailsPb(objectDetails)}), nil
|
||||
}
|
||||
|
@@ -63,7 +63,7 @@ func Test_totpDetailsToPb(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := totpDetailsToPb(tt.args.otp, tt.args.err)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
if !proto.Equal(tt.want, got) {
|
||||
if tt.want != nil && !proto.Equal(tt.want, got.Msg) {
|
||||
t.Errorf("RegisterTOTPResponse =\n%v\nwant\n%v", got, tt.want)
|
||||
}
|
||||
})
|
||||
|
@@ -3,50 +3,52 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) RegisterU2F(ctx context.Context, req *user.RegisterU2FRequest) (*user.RegisterU2FResponse, error) {
|
||||
func (s *Server) RegisterU2F(ctx context.Context, req *connect.Request[user.RegisterU2FRequest]) (*connect.Response[user.RegisterU2FResponse], error) {
|
||||
return u2fRegistrationDetailsToPb(
|
||||
s.command.RegisterUserU2F(ctx, req.GetUserId(), "", req.GetDomain()),
|
||||
s.command.RegisterUserU2F(ctx, req.Msg.GetUserId(), "", req.Msg.GetDomain()),
|
||||
)
|
||||
}
|
||||
|
||||
func u2fRegistrationDetailsToPb(details *domain.WebAuthNRegistrationDetails, err error) (*user.RegisterU2FResponse, error) {
|
||||
func u2fRegistrationDetailsToPb(details *domain.WebAuthNRegistrationDetails, err error) (*connect.Response[user.RegisterU2FResponse], error) {
|
||||
objectDetails, options, err := webAuthNRegistrationDetailsToPb(details, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RegisterU2FResponse{
|
||||
return connect.NewResponse(&user.RegisterU2FResponse{
|
||||
Details: objectDetails,
|
||||
U2FId: details.ID,
|
||||
PublicKeyCredentialCreationOptions: options,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) VerifyU2FRegistration(ctx context.Context, req *user.VerifyU2FRegistrationRequest) (*user.VerifyU2FRegistrationResponse, error) {
|
||||
pkc, err := req.GetPublicKeyCredential().MarshalJSON()
|
||||
func (s *Server) VerifyU2FRegistration(ctx context.Context, req *connect.Request[user.VerifyU2FRegistrationRequest]) (*connect.Response[user.VerifyU2FRegistrationResponse], error) {
|
||||
pkc, err := req.Msg.GetPublicKeyCredential().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "USERv2-IeTh4", "Errors.Internal")
|
||||
}
|
||||
objectDetails, err := s.command.HumanVerifyU2FSetup(ctx, req.GetUserId(), "", req.GetTokenName(), "", pkc)
|
||||
objectDetails, err := s.command.HumanVerifyU2FSetup(ctx, req.Msg.GetUserId(), "", req.Msg.GetTokenName(), "", pkc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyU2FRegistrationResponse{
|
||||
return connect.NewResponse(&user.VerifyU2FRegistrationResponse{
|
||||
Details: object.DomainToDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveU2F(ctx context.Context, req *user.RemoveU2FRequest) (*user.RemoveU2FResponse, error) {
|
||||
objectDetails, err := s.command.HumanRemoveU2F(ctx, req.GetUserId(), req.GetU2FId(), "")
|
||||
func (s *Server) RemoveU2F(ctx context.Context, req *connect.Request[user.RemoveU2FRequest]) (*connect.Response[user.RemoveU2FResponse], error) {
|
||||
objectDetails, err := s.command.HumanRemoveU2F(ctx, req.Msg.GetUserId(), req.Msg.GetU2FId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RemoveU2FResponse{
|
||||
return connect.NewResponse(&user.RemoveU2FResponse{
|
||||
Details: object.DomainToDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
@@ -92,11 +92,11 @@ func Test_u2fRegistrationDetailsToPb(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := u2fRegistrationDetailsToPb(tt.args.details, tt.args.err)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
if !proto.Equal(tt.want, got) {
|
||||
if tt.want != nil && !proto.Equal(tt.want, got.Msg) {
|
||||
t.Errorf("Not equal:\nExpected\n%s\nActual:%s", tt.want, got)
|
||||
}
|
||||
if tt.want != nil {
|
||||
grpc.AllFieldsSet(t, got.ProtoReflect())
|
||||
grpc.AllFieldsSet(t, got.Msg.ProtoReflect())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
@@ -11,11 +12,12 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"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 (s *Server) AddHumanUser(ctx context.Context, req *user.AddHumanUserRequest) (_ *user.AddHumanUserResponse, err error) {
|
||||
human, err := AddUserRequestToAddHuman(req)
|
||||
func (s *Server) AddHumanUser(ctx context.Context, req *connect.Request[user.AddHumanUserRequest]) (_ *connect.Response[user.AddHumanUserResponse], err error) {
|
||||
human, err := AddUserRequestToAddHuman(req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -23,12 +25,12 @@ func (s *Server) AddHumanUser(ctx context.Context, req *user.AddHumanUserRequest
|
||||
if err = s.command.AddUserHuman(ctx, orgID, human, false, s.userCodeAlg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.AddHumanUserResponse{
|
||||
return connect.NewResponse(&user.AddHumanUserResponse{
|
||||
UserId: human.ID,
|
||||
Details: object.DomainToDetailsPb(human.Details),
|
||||
EmailCode: human.EmailCode,
|
||||
PhoneCode: human.PhoneCode,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func AddUserRequestToAddHuman(req *user.AddHumanUserRequest) (*command.AddHuman, error) {
|
||||
@@ -116,8 +118,8 @@ func genderToDomain(gender user.Gender) domain.Gender {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) UpdateHumanUser(ctx context.Context, req *user.UpdateHumanUserRequest) (_ *user.UpdateHumanUserResponse, err error) {
|
||||
human, err := UpdateUserRequestToChangeHuman(req)
|
||||
func (s *Server) UpdateHumanUser(ctx context.Context, req *connect.Request[user.UpdateHumanUserRequest]) (_ *connect.Response[user.UpdateHumanUserResponse], err error) {
|
||||
human, err := updateHumanUserRequestToChangeHuman(req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -125,51 +127,51 @@ func (s *Server) UpdateHumanUser(ctx context.Context, req *user.UpdateHumanUserR
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.UpdateHumanUserResponse{
|
||||
return connect.NewResponse(&user.UpdateHumanUserResponse{
|
||||
Details: object.DomainToDetailsPb(human.Details),
|
||||
EmailCode: human.EmailCode,
|
||||
PhoneCode: human.PhoneCode,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) LockUser(ctx context.Context, req *user.LockUserRequest) (_ *user.LockUserResponse, err error) {
|
||||
details, err := s.command.LockUserV2(ctx, req.UserId)
|
||||
func (s *Server) LockUser(ctx context.Context, req *connect.Request[user.LockUserRequest]) (_ *connect.Response[user.LockUserResponse], err error) {
|
||||
details, err := s.command.LockUserV2(ctx, req.Msg.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.LockUserResponse{
|
||||
return connect.NewResponse(&user.LockUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) UnlockUser(ctx context.Context, req *user.UnlockUserRequest) (_ *user.UnlockUserResponse, err error) {
|
||||
details, err := s.command.UnlockUserV2(ctx, req.UserId)
|
||||
func (s *Server) UnlockUser(ctx context.Context, req *connect.Request[user.UnlockUserRequest]) (_ *connect.Response[user.UnlockUserResponse], err error) {
|
||||
details, err := s.command.UnlockUserV2(ctx, req.Msg.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.UnlockUserResponse{
|
||||
return connect.NewResponse(&user.UnlockUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateUser(ctx context.Context, req *user.DeactivateUserRequest) (_ *user.DeactivateUserResponse, err error) {
|
||||
details, err := s.command.DeactivateUserV2(ctx, req.UserId)
|
||||
func (s *Server) DeactivateUser(ctx context.Context, req *connect.Request[user.DeactivateUserRequest]) (_ *connect.Response[user.DeactivateUserResponse], err error) {
|
||||
details, err := s.command.DeactivateUserV2(ctx, req.Msg.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.DeactivateUserResponse{
|
||||
return connect.NewResponse(&user.DeactivateUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ReactivateUser(ctx context.Context, req *user.ReactivateUserRequest) (_ *user.ReactivateUserResponse, err error) {
|
||||
details, err := s.command.ReactivateUserV2(ctx, req.UserId)
|
||||
func (s *Server) ReactivateUser(ctx context.Context, req *connect.Request[user.ReactivateUserRequest]) (_ *connect.Response[user.ReactivateUserResponse], err error) {
|
||||
details, err := s.command.ReactivateUserV2(ctx, req.Msg.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ReactivateUserResponse{
|
||||
return connect.NewResponse(&user.ReactivateUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func ifNotNilPtr[v, p any](value *v, conv func(v) p) *p {
|
||||
@@ -181,98 +183,18 @@ func ifNotNilPtr[v, p any](value *v, conv func(v) p) *p {
|
||||
return &pVal
|
||||
}
|
||||
|
||||
func UpdateUserRequestToChangeHuman(req *user.UpdateHumanUserRequest) (*command.ChangeHuman, error) {
|
||||
email, err := SetHumanEmailToEmail(req.Email, req.GetUserId())
|
||||
func (s *Server) DeleteUser(ctx context.Context, req *connect.Request[user.DeleteUserRequest]) (_ *connect.Response[user.DeleteUserResponse], err error) {
|
||||
memberships, grants, err := s.removeUserDependencies(ctx, req.Msg.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &command.ChangeHuman{
|
||||
ID: req.GetUserId(),
|
||||
Username: req.Username,
|
||||
Profile: SetHumanProfileToProfile(req.Profile),
|
||||
Email: email,
|
||||
Phone: SetHumanPhoneToPhone(req.Phone),
|
||||
Password: SetHumanPasswordToPassword(req.Password),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SetHumanProfileToProfile(profile *user.SetHumanProfile) *command.Profile {
|
||||
if profile == nil {
|
||||
return nil
|
||||
}
|
||||
var firstName *string
|
||||
if profile.GivenName != "" {
|
||||
firstName = &profile.GivenName
|
||||
}
|
||||
var lastName *string
|
||||
if profile.FamilyName != "" {
|
||||
lastName = &profile.FamilyName
|
||||
}
|
||||
return &command.Profile{
|
||||
FirstName: firstName,
|
||||
LastName: lastName,
|
||||
NickName: profile.NickName,
|
||||
DisplayName: profile.DisplayName,
|
||||
PreferredLanguage: ifNotNilPtr(profile.PreferredLanguage, language.Make),
|
||||
Gender: ifNotNilPtr(profile.Gender, genderToDomain),
|
||||
}
|
||||
}
|
||||
|
||||
func SetHumanEmailToEmail(email *user.SetHumanEmail, userID string) (*command.Email, error) {
|
||||
if email == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var urlTemplate string
|
||||
if email.GetSendCode() != nil && email.GetSendCode().UrlTemplate != nil {
|
||||
urlTemplate = *email.GetSendCode().UrlTemplate
|
||||
if err := domain.RenderConfirmURLTemplate(io.Discard, urlTemplate, userID, "code", "orgID"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &command.Email{
|
||||
Address: domain.EmailAddress(email.Email),
|
||||
Verified: email.GetIsVerified(),
|
||||
ReturnCode: email.GetReturnCode() != nil,
|
||||
URLTemplate: urlTemplate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SetHumanPhoneToPhone(phone *user.SetHumanPhone) *command.Phone {
|
||||
if phone == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.Phone{
|
||||
Number: domain.PhoneNumber(phone.GetPhone()),
|
||||
Verified: phone.GetIsVerified(),
|
||||
ReturnCode: phone.GetReturnCode() != nil,
|
||||
}
|
||||
}
|
||||
|
||||
func SetHumanPasswordToPassword(password *user.SetPassword) *command.Password {
|
||||
if password == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.Password{
|
||||
PasswordCode: password.GetVerificationCode(),
|
||||
OldPassword: password.GetCurrentPassword(),
|
||||
Password: password.GetPassword().GetPassword(),
|
||||
EncodedPasswordHash: password.GetHashedPassword().GetHash(),
|
||||
ChangeRequired: password.GetPassword().GetChangeRequired() || password.GetHashedPassword().GetChangeRequired(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) DeleteUser(ctx context.Context, req *user.DeleteUserRequest) (_ *user.DeleteUserResponse, err error) {
|
||||
memberships, grants, err := s.removeUserDependencies(ctx, req.GetUserId())
|
||||
details, err := s.command.RemoveUserV2(ctx, req.Msg.GetUserId(), "", memberships, grants...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.RemoveUserV2(ctx, req.UserId, "", memberships, grants...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.DeleteUserResponse{
|
||||
return connect.NewResponse(&user.DeleteUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) removeUserDependencies(ctx context.Context, userID string) ([]*command.CascadingMembership, []string, error) {
|
||||
@@ -347,35 +269,35 @@ func userGrantsToIDs(userGrants []*query.UserGrant) []string {
|
||||
return converted
|
||||
}
|
||||
|
||||
func (s *Server) ListAuthenticationMethodTypes(ctx context.Context, req *user.ListAuthenticationMethodTypesRequest) (*user.ListAuthenticationMethodTypesResponse, error) {
|
||||
authMethods, err := s.query.ListUserAuthMethodTypes(ctx, req.GetUserId(), true, req.GetDomainQuery().GetIncludeWithoutDomain(), req.GetDomainQuery().GetDomain())
|
||||
func (s *Server) ListAuthenticationMethodTypes(ctx context.Context, req *connect.Request[user.ListAuthenticationMethodTypesRequest]) (*connect.Response[user.ListAuthenticationMethodTypesResponse], error) {
|
||||
authMethods, err := s.query.ListUserAuthMethodTypes(ctx, req.Msg.GetUserId(), true, req.Msg.GetDomainQuery().GetIncludeWithoutDomain(), req.Msg.GetDomainQuery().GetDomain())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ListAuthenticationMethodTypesResponse{
|
||||
return connect.NewResponse(&user.ListAuthenticationMethodTypesResponse{
|
||||
Details: object.ToListDetails(authMethods.SearchResponse),
|
||||
AuthMethodTypes: authMethodTypesToPb(authMethods.AuthMethodTypes),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ListAuthenticationFactors(ctx context.Context, req *user.ListAuthenticationFactorsRequest) (*user.ListAuthenticationFactorsResponse, error) {
|
||||
func (s *Server) ListAuthenticationFactors(ctx context.Context, req *connect.Request[user.ListAuthenticationFactorsRequest]) (*connect.Response[user.ListAuthenticationFactorsResponse], error) {
|
||||
query := new(query.UserAuthMethodSearchQueries)
|
||||
|
||||
if err := query.AppendUserIDQuery(req.UserId); err != nil {
|
||||
if err := query.AppendUserIDQuery(req.Msg.GetUserId()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authMethodsType := []domain.UserAuthMethodType{domain.UserAuthMethodTypeU2F, domain.UserAuthMethodTypeTOTP, domain.UserAuthMethodTypeOTPSMS, domain.UserAuthMethodTypeOTPEmail}
|
||||
if len(req.GetAuthFactors()) > 0 {
|
||||
authMethodsType = object.AuthFactorsToPb(req.GetAuthFactors())
|
||||
if len(req.Msg.GetAuthFactors()) > 0 {
|
||||
authMethodsType = object.AuthFactorsToPb(req.Msg.GetAuthFactors())
|
||||
}
|
||||
if err := query.AppendAuthMethodsQuery(authMethodsType...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
states := []domain.MFAState{domain.MFAStateReady}
|
||||
if len(req.GetStates()) > 0 {
|
||||
states = object.AuthFactorStatesToPb(req.GetStates())
|
||||
if len(req.Msg.GetStates()) > 0 {
|
||||
states = object.AuthFactorStatesToPb(req.Msg.GetStates())
|
||||
}
|
||||
if err := query.AppendStatesQuery(states...); err != nil {
|
||||
return nil, err
|
||||
@@ -386,9 +308,9 @@ func (s *Server) ListAuthenticationFactors(ctx context.Context, req *user.ListAu
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.ListAuthenticationFactorsResponse{
|
||||
return connect.NewResponse(&user.ListAuthenticationFactorsResponse{
|
||||
Result: object.AuthMethodsToPb(authMethods),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func authMethodTypesToPb(methodTypes []domain.UserAuthMethodType) []user.AuthenticationMethodType {
|
||||
@@ -422,8 +344,8 @@ func authMethodTypeToPb(methodType domain.UserAuthMethodType) user.Authenticatio
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) CreateInviteCode(ctx context.Context, req *user.CreateInviteCodeRequest) (*user.CreateInviteCodeResponse, error) {
|
||||
invite, err := createInviteCodeRequestToCommand(req)
|
||||
func (s *Server) CreateInviteCode(ctx context.Context, req *connect.Request[user.CreateInviteCodeRequest]) (*connect.Response[user.CreateInviteCodeResponse], error) {
|
||||
invite, err := createInviteCodeRequestToCommand(req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -431,30 +353,30 @@ func (s *Server) CreateInviteCode(ctx context.Context, req *user.CreateInviteCod
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.CreateInviteCodeResponse{
|
||||
return connect.NewResponse(&user.CreateInviteCodeResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
InviteCode: code,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ResendInviteCode(ctx context.Context, req *user.ResendInviteCodeRequest) (*user.ResendInviteCodeResponse, error) {
|
||||
details, err := s.command.ResendInviteCode(ctx, req.GetUserId(), "", "")
|
||||
func (s *Server) ResendInviteCode(ctx context.Context, req *connect.Request[user.ResendInviteCodeRequest]) (*connect.Response[user.ResendInviteCodeResponse], error) {
|
||||
details, err := s.command.ResendInviteCode(ctx, req.Msg.GetUserId(), "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ResendInviteCodeResponse{
|
||||
return connect.NewResponse(&user.ResendInviteCodeResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) VerifyInviteCode(ctx context.Context, req *user.VerifyInviteCodeRequest) (*user.VerifyInviteCodeResponse, error) {
|
||||
details, err := s.command.VerifyInviteCode(ctx, req.GetUserId(), req.GetVerificationCode())
|
||||
func (s *Server) VerifyInviteCode(ctx context.Context, req *connect.Request[user.VerifyInviteCodeRequest]) (*connect.Response[user.VerifyInviteCodeResponse], error) {
|
||||
details, err := s.command.VerifyInviteCode(ctx, req.Msg.GetUserId(), req.Msg.GetVerificationCode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyInviteCodeResponse{
|
||||
return connect.NewResponse(&user.VerifyInviteCodeResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func createInviteCodeRequestToCommand(req *user.CreateInviteCodeRequest) (*command.CreateUserInvite, error) {
|
||||
@@ -473,12 +395,34 @@ func createInviteCodeRequestToCommand(req *user.CreateInviteCodeRequest) (*comma
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) HumanMFAInitSkipped(ctx context.Context, req *user.HumanMFAInitSkippedRequest) (_ *user.HumanMFAInitSkippedResponse, err error) {
|
||||
details, err := s.command.HumanMFAInitSkippedV2(ctx, req.UserId)
|
||||
func (s *Server) HumanMFAInitSkipped(ctx context.Context, req *connect.Request[user.HumanMFAInitSkippedRequest]) (_ *connect.Response[user.HumanMFAInitSkippedResponse], err error) {
|
||||
details, err := s.command.HumanMFAInitSkippedV2(ctx, req.Msg.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.HumanMFAInitSkippedResponse{
|
||||
return connect.NewResponse(&user.HumanMFAInitSkippedResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) CreateUser(ctx context.Context, req *connect.Request[user.CreateUserRequest]) (*connect.Response[user.CreateUserResponse], error) {
|
||||
switch userType := req.Msg.GetUserType().(type) {
|
||||
case *user.CreateUserRequest_Human_:
|
||||
return s.createUserTypeHuman(ctx, userType.Human, req.Msg.GetOrganizationId(), req.Msg.Username, req.Msg.UserId)
|
||||
case *user.CreateUserRequest_Machine_:
|
||||
return s.createUserTypeMachine(ctx, userType.Machine, req.Msg.GetOrganizationId(), req.Msg.GetUsername(), req.Msg.GetUserId())
|
||||
default:
|
||||
return nil, zerrors.ThrowInternal(nil, "", "user type is not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) UpdateUser(ctx context.Context, req *connect.Request[user.UpdateUserRequest]) (*connect.Response[user.UpdateUserResponse], error) {
|
||||
switch userType := req.Msg.GetUserType().(type) {
|
||||
case *user.UpdateUserRequest_Human_:
|
||||
return s.updateUserTypeHuman(ctx, userType.Human, req.Msg.GetUserId(), req.Msg.Username)
|
||||
case *user.UpdateUserRequest_Machine_:
|
||||
return s.updateUserTypeMachine(ctx, userType.Machine, req.Msg.GetUserId(), req.Msg.Username)
|
||||
default:
|
||||
return nil, zerrors.ThrowUnimplemented(nil, "", "user type is not implemented")
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/muhlemmer/gu"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
@@ -13,12 +14,12 @@ import (
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
func (s *Server) GetUserByID(ctx context.Context, req *user.GetUserByIDRequest) (_ *user.GetUserByIDResponse, err error) {
|
||||
resp, err := s.query.GetUserByIDWithPermission(ctx, true, req.GetUserId(), s.checkPermission)
|
||||
func (s *Server) GetUserByID(ctx context.Context, req *connect.Request[user.GetUserByIDRequest]) (_ *connect.Response[user.GetUserByIDResponse], err error) {
|
||||
resp, err := s.query.GetUserByIDWithPermission(ctx, true, req.Msg.GetUserId(), s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.GetUserByIDResponse{
|
||||
return connect.NewResponse(&user.GetUserByIDResponse{
|
||||
Details: object.DomainToDetailsPb(&domain.ObjectDetails{
|
||||
Sequence: resp.Sequence,
|
||||
CreationDate: resp.CreationDate,
|
||||
@@ -26,22 +27,22 @@ func (s *Server) GetUserByID(ctx context.Context, req *user.GetUserByIDRequest)
|
||||
ResourceOwner: resp.ResourceOwner,
|
||||
}),
|
||||
User: userToPb(resp, s.assetAPIPrefix(ctx)),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ListUsers(ctx context.Context, req *user.ListUsersRequest) (*user.ListUsersResponse, error) {
|
||||
queries, filterOrgId, err := listUsersRequestToModel(req)
|
||||
func (s *Server) ListUsers(ctx context.Context, req *connect.Request[user.ListUsersRequest]) (*connect.Response[user.ListUsersResponse], error) {
|
||||
queries, err := listUsersRequestToModel(req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := s.query.SearchUsers(ctx, queries, filterOrgId, s.checkPermission)
|
||||
res, err := s.query.SearchUsers(ctx, queries, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ListUsersResponse{
|
||||
return connect.NewResponse(&user.ListUsersResponse{
|
||||
Result: UsersToPb(res.Users, s.assetAPIPrefix(ctx)),
|
||||
Details: object.ToListDetails(res.SearchResponse),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func UsersToPb(users []*query.User, assetPrefix string) []*user.User {
|
||||
@@ -171,11 +172,11 @@ func accessTokenTypeToPb(accessTokenType domain.OIDCTokenType) user.AccessTokenT
|
||||
}
|
||||
}
|
||||
|
||||
func listUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueries, string, error) {
|
||||
func listUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueries, error) {
|
||||
offset, limit, asc := object.ListQueryToQuery(req.Query)
|
||||
queries, filterOrgId, err := userQueriesToQuery(req.Queries, 0 /*start from level 0*/)
|
||||
queries, err := userQueriesToQuery(req.Queries, 0 /*start from level 0*/)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, err
|
||||
}
|
||||
return &query.UserSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
@@ -185,7 +186,7 @@ func listUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueri
|
||||
SortingColumn: userFieldNameToSortingColumn(req.SortingColumn),
|
||||
},
|
||||
Queries: queries,
|
||||
}, filterOrgId, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func userFieldNameToSortingColumn(field user.UserFieldName) query.Column {
|
||||
@@ -215,18 +216,15 @@ func userFieldNameToSortingColumn(field user.UserFieldName) query.Column {
|
||||
}
|
||||
}
|
||||
|
||||
func userQueriesToQuery(queries []*user.SearchQuery, level uint8) (_ []query.SearchQuery, filterOrgId string, err error) {
|
||||
func userQueriesToQuery(queries []*user.SearchQuery, level uint8) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
if orgFilter := query.GetOrganizationIdQuery(); orgFilter != nil {
|
||||
filterOrgId = orgFilter.OrganizationId
|
||||
}
|
||||
q[i], err = userQueryToQuery(query, level)
|
||||
if err != nil {
|
||||
return nil, filterOrgId, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, filterOrgId, nil
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func userQueryToQuery(query *user.SearchQuery, level uint8) (query.SearchQuery, error) {
|
||||
@@ -320,14 +318,14 @@ 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)
|
||||
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)
|
||||
mappedQueries, err := userQueriesToQuery(q.Queries, level+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
@@ -3,6 +3,7 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -11,18 +12,18 @@ import (
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) SetEmail(ctx context.Context, req *user.SetEmailRequest) (resp *user.SetEmailResponse, err error) {
|
||||
func (s *Server) SetEmail(ctx context.Context, req *connect.Request[user.SetEmailRequest]) (resp *connect.Response[user.SetEmailResponse], err error) {
|
||||
var email *domain.Email
|
||||
|
||||
switch v := req.GetVerification().(type) {
|
||||
switch v := req.Msg.GetVerification().(type) {
|
||||
case *user.SetEmailRequest_SendCode:
|
||||
email, err = s.command.ChangeUserEmailURLTemplate(ctx, req.GetUserId(), req.GetEmail(), s.userCodeAlg, v.SendCode.GetUrlTemplate())
|
||||
email, err = s.command.ChangeUserEmailURLTemplate(ctx, req.Msg.GetUserId(), req.Msg.GetEmail(), s.userCodeAlg, v.SendCode.GetUrlTemplate())
|
||||
case *user.SetEmailRequest_ReturnCode:
|
||||
email, err = s.command.ChangeUserEmailReturnCode(ctx, req.GetUserId(), req.GetEmail(), s.userCodeAlg)
|
||||
email, err = s.command.ChangeUserEmailReturnCode(ctx, req.Msg.GetUserId(), req.Msg.GetEmail(), s.userCodeAlg)
|
||||
case *user.SetEmailRequest_IsVerified:
|
||||
email, err = s.command.ChangeUserEmailVerified(ctx, req.GetUserId(), req.GetEmail())
|
||||
email, err = s.command.ChangeUserEmailVerified(ctx, req.Msg.GetUserId(), req.Msg.GetEmail())
|
||||
case nil:
|
||||
email, err = s.command.ChangeUserEmail(ctx, req.GetUserId(), req.GetEmail(), s.userCodeAlg)
|
||||
email, err = s.command.ChangeUserEmail(ctx, req.Msg.GetUserId(), req.Msg.GetEmail(), s.userCodeAlg)
|
||||
default:
|
||||
err = zerrors.ThrowUnimplementedf(nil, "USERv2-Ahng0", "verification oneOf %T in method SetEmail not implemented", v)
|
||||
}
|
||||
@@ -30,26 +31,26 @@ func (s *Server) SetEmail(ctx context.Context, req *user.SetEmailRequest) (resp
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.SetEmailResponse{
|
||||
return connect.NewResponse(&user.SetEmailResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: email.Sequence,
|
||||
ChangeDate: timestamppb.New(email.ChangeDate),
|
||||
ResourceOwner: email.ResourceOwner,
|
||||
},
|
||||
VerificationCode: email.PlainCode,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ResendEmailCode(ctx context.Context, req *user.ResendEmailCodeRequest) (resp *user.ResendEmailCodeResponse, err error) {
|
||||
func (s *Server) ResendEmailCode(ctx context.Context, req *connect.Request[user.ResendEmailCodeRequest]) (resp *connect.Response[user.ResendEmailCodeResponse], err error) {
|
||||
var email *domain.Email
|
||||
|
||||
switch v := req.GetVerification().(type) {
|
||||
switch v := req.Msg.GetVerification().(type) {
|
||||
case *user.ResendEmailCodeRequest_SendCode:
|
||||
email, err = s.command.ResendUserEmailCodeURLTemplate(ctx, req.GetUserId(), s.userCodeAlg, v.SendCode.GetUrlTemplate())
|
||||
email, err = s.command.ResendUserEmailCodeURLTemplate(ctx, req.Msg.GetUserId(), s.userCodeAlg, v.SendCode.GetUrlTemplate())
|
||||
case *user.ResendEmailCodeRequest_ReturnCode:
|
||||
email, err = s.command.ResendUserEmailReturnCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||
email, err = s.command.ResendUserEmailReturnCode(ctx, req.Msg.GetUserId(), s.userCodeAlg)
|
||||
case nil:
|
||||
email, err = s.command.ResendUserEmailCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||
email, err = s.command.ResendUserEmailCode(ctx, req.Msg.GetUserId(), s.userCodeAlg)
|
||||
default:
|
||||
err = zerrors.ThrowUnimplementedf(nil, "USERv2-faj0l0nj5x", "verification oneOf %T in method ResendEmailCode not implemented", v)
|
||||
}
|
||||
@@ -57,30 +58,30 @@ func (s *Server) ResendEmailCode(ctx context.Context, req *user.ResendEmailCodeR
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.ResendEmailCodeResponse{
|
||||
return connect.NewResponse(&user.ResendEmailCodeResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: email.Sequence,
|
||||
ChangeDate: timestamppb.New(email.ChangeDate),
|
||||
ResourceOwner: email.ResourceOwner,
|
||||
},
|
||||
VerificationCode: email.PlainCode,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) VerifyEmail(ctx context.Context, req *user.VerifyEmailRequest) (*user.VerifyEmailResponse, error) {
|
||||
func (s *Server) VerifyEmail(ctx context.Context, req *connect.Request[user.VerifyEmailRequest]) (*connect.Response[user.VerifyEmailResponse], error) {
|
||||
details, err := s.command.VerifyUserEmail(ctx,
|
||||
req.GetUserId(),
|
||||
req.GetVerificationCode(),
|
||||
req.Msg.GetUserId(),
|
||||
req.Msg.GetVerificationCode(),
|
||||
s.userCodeAlg,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyEmailResponse{
|
||||
return connect.NewResponse(&user.VerifyEmailResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: details.Sequence,
|
||||
ChangeDate: timestamppb.New(details.EventDate),
|
||||
ResourceOwner: details.ResourceOwner,
|
||||
},
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
@@ -1706,12 +1706,11 @@ func TestServer_ReactivateUser(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_DeleteUser(t *testing.T) {
|
||||
projectResp, err := Instance.CreateProject(CTX)
|
||||
require.NoError(t, err)
|
||||
projectResp := Instance.CreateProject(CTX, t, "", gofakeit.AppName(), false, false)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *user.DeleteUserRequest
|
||||
prepare func(request *user.DeleteUserRequest) error
|
||||
prepare func(request *user.DeleteUserRequest)
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -1726,7 +1725,7 @@ func TestServer_DeleteUser(t *testing.T) {
|
||||
&user.DeleteUserRequest{
|
||||
UserId: "notexisting",
|
||||
},
|
||||
func(request *user.DeleteUserRequest) error { return nil },
|
||||
nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -1735,10 +1734,9 @@ func TestServer_DeleteUser(t *testing.T) {
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.DeleteUserRequest{},
|
||||
prepare: func(request *user.DeleteUserRequest) error {
|
||||
prepare: func(request *user.DeleteUserRequest) {
|
||||
resp := Instance.CreateHumanUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
return err
|
||||
},
|
||||
},
|
||||
want: &user.DeleteUserResponse{
|
||||
@@ -1753,10 +1751,9 @@ func TestServer_DeleteUser(t *testing.T) {
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.DeleteUserRequest{},
|
||||
prepare: func(request *user.DeleteUserRequest) error {
|
||||
prepare: func(request *user.DeleteUserRequest) {
|
||||
resp := Instance.CreateMachineUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
return err
|
||||
},
|
||||
},
|
||||
want: &user.DeleteUserResponse{
|
||||
@@ -1771,13 +1768,12 @@ func TestServer_DeleteUser(t *testing.T) {
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
req: &user.DeleteUserRequest{},
|
||||
prepare: func(request *user.DeleteUserRequest) error {
|
||||
prepare: func(request *user.DeleteUserRequest) {
|
||||
resp := Instance.CreateHumanUser(CTX)
|
||||
request.UserId = resp.GetUserId()
|
||||
Instance.CreateProjectUserGrant(t, CTX, projectResp.GetId(), request.UserId)
|
||||
Instance.CreateProjectMembership(t, CTX, projectResp.GetId(), request.UserId)
|
||||
Instance.CreateOrgMembership(t, CTX, request.UserId)
|
||||
return err
|
||||
},
|
||||
},
|
||||
want: &user.DeleteUserResponse{
|
||||
@@ -1790,8 +1786,9 @@ func TestServer_DeleteUser(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.args.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
if tt.args.prepare != nil {
|
||||
tt.args.prepare(tt.args.req)
|
||||
}
|
||||
|
||||
got, err := Client.DeleteUser(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
@@ -2061,7 +2058,7 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
url: "http://" + Instance.Domain + ":8000/sso",
|
||||
url: "http://localhost:8000/sso",
|
||||
parametersExisting: []string{"RelayState", "SAMLRequest"},
|
||||
},
|
||||
wantErr: false,
|
||||
@@ -2085,7 +2082,7 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
url: "http://" + Instance.Domain + ":8000/sso",
|
||||
url: "http://localhost:8000/sso",
|
||||
parametersExisting: []string{"RelayState", "SAMLRequest"},
|
||||
},
|
||||
wantErr: false,
|
||||
@@ -2109,7 +2106,9 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
||||
ChangeDate: timestamppb.Now(),
|
||||
ResourceOwner: Instance.ID(),
|
||||
},
|
||||
postForm: true,
|
||||
url: "http://localhost:8000/sso",
|
||||
parametersExisting: []string{"RelayState", "SAMLRequest"},
|
||||
postForm: true,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -2123,9 +2122,11 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
if tt.want.url != "" {
|
||||
if tt.want.url != "" && !tt.want.postForm {
|
||||
authUrl, err := url.Parse(got.GetAuthUrl())
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.want.url, authUrl.Scheme+"://"+authUrl.Host+authUrl.Path)
|
||||
require.Len(t, authUrl.Query(), len(tt.want.parametersEqual)+len(tt.want.parametersExisting))
|
||||
|
||||
for _, existing := range tt.want.parametersExisting {
|
||||
@@ -2136,7 +2137,15 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
||||
}
|
||||
}
|
||||
if tt.want.postForm {
|
||||
assert.NotEmpty(t, got.GetPostForm())
|
||||
assert.Equal(t, tt.want.url, got.GetFormData().GetUrl())
|
||||
|
||||
require.Len(t, got.GetFormData().GetFields(), len(tt.want.parametersEqual)+len(tt.want.parametersExisting))
|
||||
for _, existing := range tt.want.parametersExisting {
|
||||
assert.Contains(t, got.GetFormData().GetFields(), existing)
|
||||
}
|
||||
for key, equal := range tt.want.parametersEqual {
|
||||
assert.Equal(t, got.GetFormData().GetFields()[key], equal)
|
||||
}
|
||||
}
|
||||
integration.AssertDetails(t, &user.StartIdentityProviderIntentResponse{
|
||||
Details: tt.want.details,
|
||||
|
@@ -3,40 +3,42 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
|
||||
object "github.com/zitadel/zitadel/internal/api/grpc/object/v2beta"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) AddOTPSMS(ctx context.Context, req *user.AddOTPSMSRequest) (*user.AddOTPSMSResponse, error) {
|
||||
details, err := s.command.AddHumanOTPSMS(ctx, req.GetUserId(), "")
|
||||
func (s *Server) AddOTPSMS(ctx context.Context, req *connect.Request[user.AddOTPSMSRequest]) (*connect.Response[user.AddOTPSMSResponse], error) {
|
||||
details, err := s.command.AddHumanOTPSMS(ctx, req.Msg.GetUserId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.AddOTPSMSResponse{Details: object.DomainToDetailsPb(details)}, nil
|
||||
return connect.NewResponse(&user.AddOTPSMSResponse{Details: object.DomainToDetailsPb(details)}), nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Server) RemoveOTPSMS(ctx context.Context, req *user.RemoveOTPSMSRequest) (*user.RemoveOTPSMSResponse, error) {
|
||||
objectDetails, err := s.command.RemoveHumanOTPSMS(ctx, req.GetUserId(), "")
|
||||
func (s *Server) RemoveOTPSMS(ctx context.Context, req *connect.Request[user.RemoveOTPSMSRequest]) (*connect.Response[user.RemoveOTPSMSResponse], error) {
|
||||
objectDetails, err := s.command.RemoveHumanOTPSMS(ctx, req.Msg.GetUserId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RemoveOTPSMSResponse{Details: object.DomainToDetailsPb(objectDetails)}, nil
|
||||
return connect.NewResponse(&user.RemoveOTPSMSResponse{Details: object.DomainToDetailsPb(objectDetails)}), nil
|
||||
}
|
||||
|
||||
func (s *Server) AddOTPEmail(ctx context.Context, req *user.AddOTPEmailRequest) (*user.AddOTPEmailResponse, error) {
|
||||
details, err := s.command.AddHumanOTPEmail(ctx, req.GetUserId(), "")
|
||||
func (s *Server) AddOTPEmail(ctx context.Context, req *connect.Request[user.AddOTPEmailRequest]) (*connect.Response[user.AddOTPEmailResponse], error) {
|
||||
details, err := s.command.AddHumanOTPEmail(ctx, req.Msg.GetUserId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.AddOTPEmailResponse{Details: object.DomainToDetailsPb(details)}, nil
|
||||
return connect.NewResponse(&user.AddOTPEmailResponse{Details: object.DomainToDetailsPb(details)}), nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Server) RemoveOTPEmail(ctx context.Context, req *user.RemoveOTPEmailRequest) (*user.RemoveOTPEmailResponse, error) {
|
||||
objectDetails, err := s.command.RemoveHumanOTPEmail(ctx, req.GetUserId(), "")
|
||||
func (s *Server) RemoveOTPEmail(ctx context.Context, req *connect.Request[user.RemoveOTPEmailRequest]) (*connect.Response[user.RemoveOTPEmailResponse], error) {
|
||||
objectDetails, err := s.command.RemoveHumanOTPEmail(ctx, req.Msg.GetUserId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RemoveOTPEmailResponse{Details: object.DomainToDetailsPb(objectDetails)}, nil
|
||||
return connect.NewResponse(&user.RemoveOTPEmailResponse{Details: object.DomainToDetailsPb(objectDetails)}), nil
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
object "github.com/zitadel/zitadel/internal/api/grpc/object/v2beta"
|
||||
@@ -12,17 +13,17 @@ import (
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) RegisterPasskey(ctx context.Context, req *user.RegisterPasskeyRequest) (resp *user.RegisterPasskeyResponse, err error) {
|
||||
func (s *Server) RegisterPasskey(ctx context.Context, req *connect.Request[user.RegisterPasskeyRequest]) (resp *connect.Response[user.RegisterPasskeyResponse], err error) {
|
||||
var (
|
||||
authenticator = passkeyAuthenticatorToDomain(req.GetAuthenticator())
|
||||
authenticator = passkeyAuthenticatorToDomain(req.Msg.GetAuthenticator())
|
||||
)
|
||||
if code := req.GetCode(); code != nil {
|
||||
if code := req.Msg.GetCode(); code != nil {
|
||||
return passkeyRegistrationDetailsToPb(
|
||||
s.command.RegisterUserPasskeyWithCode(ctx, req.GetUserId(), "", authenticator, code.Id, code.Code, req.GetDomain(), s.userCodeAlg),
|
||||
s.command.RegisterUserPasskeyWithCode(ctx, req.Msg.GetUserId(), "", authenticator, code.Id, code.Code, req.Msg.GetDomain(), s.userCodeAlg),
|
||||
)
|
||||
}
|
||||
return passkeyRegistrationDetailsToPb(
|
||||
s.command.RegisterUserPasskey(ctx, req.GetUserId(), "", req.GetDomain(), authenticator),
|
||||
s.command.RegisterUserPasskey(ctx, req.Msg.GetUserId(), "", req.Msg.GetDomain(), authenticator),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -50,69 +51,69 @@ func webAuthNRegistrationDetailsToPb(details *domain.WebAuthNRegistrationDetails
|
||||
return object.DomainToDetailsPb(details.ObjectDetails), options, nil
|
||||
}
|
||||
|
||||
func passkeyRegistrationDetailsToPb(details *domain.WebAuthNRegistrationDetails, err error) (*user.RegisterPasskeyResponse, error) {
|
||||
func passkeyRegistrationDetailsToPb(details *domain.WebAuthNRegistrationDetails, err error) (*connect.Response[user.RegisterPasskeyResponse], error) {
|
||||
objectDetails, options, err := webAuthNRegistrationDetailsToPb(details, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RegisterPasskeyResponse{
|
||||
return connect.NewResponse(&user.RegisterPasskeyResponse{
|
||||
Details: objectDetails,
|
||||
PasskeyId: details.ID,
|
||||
PublicKeyCredentialCreationOptions: options,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) VerifyPasskeyRegistration(ctx context.Context, req *user.VerifyPasskeyRegistrationRequest) (*user.VerifyPasskeyRegistrationResponse, error) {
|
||||
pkc, err := req.GetPublicKeyCredential().MarshalJSON()
|
||||
func (s *Server) VerifyPasskeyRegistration(ctx context.Context, req *connect.Request[user.VerifyPasskeyRegistrationRequest]) (*connect.Response[user.VerifyPasskeyRegistrationResponse], error) {
|
||||
pkc, err := req.Msg.GetPublicKeyCredential().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "USERv2-Pha2o", "Errors.Internal")
|
||||
}
|
||||
objectDetails, err := s.command.HumanHumanPasswordlessSetup(ctx, req.GetUserId(), "", req.GetPasskeyName(), "", pkc)
|
||||
objectDetails, err := s.command.HumanHumanPasswordlessSetup(ctx, req.Msg.GetUserId(), "", req.Msg.GetPasskeyName(), "", pkc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyPasskeyRegistrationResponse{
|
||||
return connect.NewResponse(&user.VerifyPasskeyRegistrationResponse{
|
||||
Details: object.DomainToDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) CreatePasskeyRegistrationLink(ctx context.Context, req *user.CreatePasskeyRegistrationLinkRequest) (resp *user.CreatePasskeyRegistrationLinkResponse, err error) {
|
||||
switch medium := req.Medium.(type) {
|
||||
func (s *Server) CreatePasskeyRegistrationLink(ctx context.Context, req *connect.Request[user.CreatePasskeyRegistrationLinkRequest]) (resp *connect.Response[user.CreatePasskeyRegistrationLinkResponse], err error) {
|
||||
switch medium := req.Msg.Medium.(type) {
|
||||
case nil:
|
||||
return passkeyDetailsToPb(
|
||||
s.command.AddUserPasskeyCode(ctx, req.GetUserId(), "", s.userCodeAlg),
|
||||
s.command.AddUserPasskeyCode(ctx, req.Msg.GetUserId(), "", s.userCodeAlg),
|
||||
)
|
||||
case *user.CreatePasskeyRegistrationLinkRequest_SendLink:
|
||||
return passkeyDetailsToPb(
|
||||
s.command.AddUserPasskeyCodeURLTemplate(ctx, req.GetUserId(), "", s.userCodeAlg, medium.SendLink.GetUrlTemplate()),
|
||||
s.command.AddUserPasskeyCodeURLTemplate(ctx, req.Msg.GetUserId(), "", s.userCodeAlg, medium.SendLink.GetUrlTemplate()),
|
||||
)
|
||||
case *user.CreatePasskeyRegistrationLinkRequest_ReturnCode:
|
||||
return passkeyCodeDetailsToPb(
|
||||
s.command.AddUserPasskeyCodeReturn(ctx, req.GetUserId(), "", s.userCodeAlg),
|
||||
s.command.AddUserPasskeyCodeReturn(ctx, req.Msg.GetUserId(), "", s.userCodeAlg),
|
||||
)
|
||||
default:
|
||||
return nil, zerrors.ThrowUnimplementedf(nil, "USERv2-gaD8y", "verification oneOf %T in method CreatePasskeyRegistrationLink not implemented", medium)
|
||||
}
|
||||
}
|
||||
|
||||
func passkeyDetailsToPb(details *domain.ObjectDetails, err error) (*user.CreatePasskeyRegistrationLinkResponse, error) {
|
||||
func passkeyDetailsToPb(details *domain.ObjectDetails, err error) (*connect.Response[user.CreatePasskeyRegistrationLinkResponse], error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.CreatePasskeyRegistrationLinkResponse{
|
||||
return connect.NewResponse(&user.CreatePasskeyRegistrationLinkResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func passkeyCodeDetailsToPb(details *domain.PasskeyCodeDetails, err error) (*user.CreatePasskeyRegistrationLinkResponse, error) {
|
||||
func passkeyCodeDetailsToPb(details *domain.PasskeyCodeDetails, err error) (*connect.Response[user.CreatePasskeyRegistrationLinkResponse], error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.CreatePasskeyRegistrationLinkResponse{
|
||||
return connect.NewResponse(&user.CreatePasskeyRegistrationLinkResponse{
|
||||
Details: object.DomainToDetailsPb(details.ObjectDetails),
|
||||
Code: &user.PasskeyRegistrationCode{
|
||||
Id: details.CodeID,
|
||||
Code: details.Code,
|
||||
},
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
@@ -123,11 +123,11 @@ func Test_passkeyRegistrationDetailsToPb(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := passkeyRegistrationDetailsToPb(tt.args.details, tt.args.err)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
if !proto.Equal(tt.want, got) {
|
||||
if tt.want != nil && !proto.Equal(tt.want, got.Msg) {
|
||||
t.Errorf("Not equal:\nExpected\n%s\nActual:%s", tt.want, got)
|
||||
}
|
||||
if tt.want != nil {
|
||||
grpc.AllFieldsSet(t, got.ProtoReflect())
|
||||
grpc.AllFieldsSet(t, got.Msg.ProtoReflect())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -181,7 +181,9 @@ func Test_passkeyDetailsToPb(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := passkeyDetailsToPb(tt.args.details, tt.args.err)
|
||||
require.ErrorIs(t, err, tt.args.err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
if tt.want != nil {
|
||||
assert.Equal(t, tt.want, got.Msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -242,9 +244,9 @@ func Test_passkeyCodeDetailsToPb(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := passkeyCodeDetailsToPb(tt.args.details, tt.args.err)
|
||||
require.ErrorIs(t, err, tt.args.err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
if tt.want != nil {
|
||||
grpc.AllFieldsSet(t, got.ProtoReflect())
|
||||
assert.Equal(t, tt.want, got.Msg)
|
||||
grpc.AllFieldsSet(t, got.Msg.ProtoReflect())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -3,23 +3,25 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
|
||||
object "github.com/zitadel/zitadel/internal/api/grpc/object/v2beta"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) PasswordReset(ctx context.Context, req *user.PasswordResetRequest) (_ *user.PasswordResetResponse, err error) {
|
||||
func (s *Server) PasswordReset(ctx context.Context, req *connect.Request[user.PasswordResetRequest]) (_ *connect.Response[user.PasswordResetResponse], err error) {
|
||||
var details *domain.ObjectDetails
|
||||
var code *string
|
||||
|
||||
switch m := req.GetMedium().(type) {
|
||||
switch m := req.Msg.GetMedium().(type) {
|
||||
case *user.PasswordResetRequest_SendLink:
|
||||
details, code, err = s.command.RequestPasswordResetURLTemplate(ctx, req.GetUserId(), m.SendLink.GetUrlTemplate(), notificationTypeToDomain(m.SendLink.GetNotificationType()))
|
||||
details, code, err = s.command.RequestPasswordResetURLTemplate(ctx, req.Msg.GetUserId(), m.SendLink.GetUrlTemplate(), notificationTypeToDomain(m.SendLink.GetNotificationType()))
|
||||
case *user.PasswordResetRequest_ReturnCode:
|
||||
details, code, err = s.command.RequestPasswordResetReturnCode(ctx, req.GetUserId())
|
||||
details, code, err = s.command.RequestPasswordResetReturnCode(ctx, req.Msg.GetUserId())
|
||||
case nil:
|
||||
details, code, err = s.command.RequestPasswordReset(ctx, req.GetUserId())
|
||||
details, code, err = s.command.RequestPasswordReset(ctx, req.Msg.GetUserId())
|
||||
default:
|
||||
err = zerrors.ThrowUnimplementedf(nil, "USERv2-SDeeg", "verification oneOf %T in method RequestPasswordReset not implemented", m)
|
||||
}
|
||||
@@ -27,10 +29,10 @@ func (s *Server) PasswordReset(ctx context.Context, req *user.PasswordResetReque
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.PasswordResetResponse{
|
||||
return connect.NewResponse(&user.PasswordResetResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
VerificationCode: code,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func notificationTypeToDomain(notificationType user.NotificationType) domain.NotificationType {
|
||||
@@ -46,16 +48,16 @@ func notificationTypeToDomain(notificationType user.NotificationType) domain.Not
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) SetPassword(ctx context.Context, req *user.SetPasswordRequest) (_ *user.SetPasswordResponse, err error) {
|
||||
func (s *Server) SetPassword(ctx context.Context, req *connect.Request[user.SetPasswordRequest]) (_ *connect.Response[user.SetPasswordResponse], err error) {
|
||||
var details *domain.ObjectDetails
|
||||
|
||||
switch v := req.GetVerification().(type) {
|
||||
switch v := req.Msg.GetVerification().(type) {
|
||||
case *user.SetPasswordRequest_CurrentPassword:
|
||||
details, err = s.command.ChangePassword(ctx, "", req.GetUserId(), v.CurrentPassword, req.GetNewPassword().GetPassword(), "", req.GetNewPassword().GetChangeRequired())
|
||||
details, err = s.command.ChangePassword(ctx, "", req.Msg.GetUserId(), v.CurrentPassword, req.Msg.GetNewPassword().GetPassword(), "", req.Msg.GetNewPassword().GetChangeRequired())
|
||||
case *user.SetPasswordRequest_VerificationCode:
|
||||
details, err = s.command.SetPasswordWithVerifyCode(ctx, "", req.GetUserId(), v.VerificationCode, req.GetNewPassword().GetPassword(), "", req.GetNewPassword().GetChangeRequired())
|
||||
details, err = s.command.SetPasswordWithVerifyCode(ctx, "", req.Msg.GetUserId(), v.VerificationCode, req.Msg.GetNewPassword().GetPassword(), "", req.Msg.GetNewPassword().GetChangeRequired())
|
||||
case nil:
|
||||
details, err = s.command.SetPassword(ctx, "", req.GetUserId(), req.GetNewPassword().GetPassword(), req.GetNewPassword().GetChangeRequired())
|
||||
details, err = s.command.SetPassword(ctx, "", req.Msg.GetUserId(), req.Msg.GetNewPassword().GetPassword(), req.Msg.GetNewPassword().GetChangeRequired())
|
||||
default:
|
||||
err = zerrors.ThrowUnimplementedf(nil, "USERv2-SFdf2", "verification oneOf %T in method SetPasswordRequest not implemented", v)
|
||||
}
|
||||
@@ -63,7 +65,7 @@ func (s *Server) SetPassword(ctx context.Context, req *user.SetPasswordRequest)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.SetPasswordResponse{
|
||||
return connect.NewResponse(&user.SetPasswordResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -11,18 +12,18 @@ import (
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) SetPhone(ctx context.Context, req *user.SetPhoneRequest) (resp *user.SetPhoneResponse, err error) {
|
||||
func (s *Server) SetPhone(ctx context.Context, req *connect.Request[user.SetPhoneRequest]) (resp *connect.Response[user.SetPhoneResponse], err error) {
|
||||
var phone *domain.Phone
|
||||
|
||||
switch v := req.GetVerification().(type) {
|
||||
switch v := req.Msg.GetVerification().(type) {
|
||||
case *user.SetPhoneRequest_SendCode:
|
||||
phone, err = s.command.ChangeUserPhone(ctx, req.GetUserId(), req.GetPhone(), s.userCodeAlg)
|
||||
phone, err = s.command.ChangeUserPhone(ctx, req.Msg.GetUserId(), req.Msg.GetPhone(), s.userCodeAlg)
|
||||
case *user.SetPhoneRequest_ReturnCode:
|
||||
phone, err = s.command.ChangeUserPhoneReturnCode(ctx, req.GetUserId(), req.GetPhone(), s.userCodeAlg)
|
||||
phone, err = s.command.ChangeUserPhoneReturnCode(ctx, req.Msg.GetUserId(), req.Msg.GetPhone(), s.userCodeAlg)
|
||||
case *user.SetPhoneRequest_IsVerified:
|
||||
phone, err = s.command.ChangeUserPhoneVerified(ctx, req.GetUserId(), req.GetPhone())
|
||||
phone, err = s.command.ChangeUserPhoneVerified(ctx, req.Msg.GetUserId(), req.Msg.GetPhone())
|
||||
case nil:
|
||||
phone, err = s.command.ChangeUserPhone(ctx, req.GetUserId(), req.GetPhone(), s.userCodeAlg)
|
||||
phone, err = s.command.ChangeUserPhone(ctx, req.Msg.GetUserId(), req.Msg.GetPhone(), s.userCodeAlg)
|
||||
default:
|
||||
err = zerrors.ThrowUnimplementedf(nil, "USERv2-Ahng0", "verification oneOf %T in method SetPhone not implemented", v)
|
||||
}
|
||||
@@ -30,42 +31,42 @@ func (s *Server) SetPhone(ctx context.Context, req *user.SetPhoneRequest) (resp
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.SetPhoneResponse{
|
||||
return connect.NewResponse(&user.SetPhoneResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: phone.Sequence,
|
||||
ChangeDate: timestamppb.New(phone.ChangeDate),
|
||||
ResourceOwner: phone.ResourceOwner,
|
||||
},
|
||||
VerificationCode: phone.PlainCode,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) RemovePhone(ctx context.Context, req *user.RemovePhoneRequest) (resp *user.RemovePhoneResponse, err error) {
|
||||
func (s *Server) RemovePhone(ctx context.Context, req *connect.Request[user.RemovePhoneRequest]) (resp *connect.Response[user.RemovePhoneResponse], err error) {
|
||||
details, err := s.command.RemoveUserPhone(ctx,
|
||||
req.GetUserId(),
|
||||
req.Msg.GetUserId(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.RemovePhoneResponse{
|
||||
return connect.NewResponse(&user.RemovePhoneResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: details.Sequence,
|
||||
ChangeDate: timestamppb.New(details.EventDate),
|
||||
ResourceOwner: details.ResourceOwner,
|
||||
},
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ResendPhoneCode(ctx context.Context, req *user.ResendPhoneCodeRequest) (resp *user.ResendPhoneCodeResponse, err error) {
|
||||
func (s *Server) ResendPhoneCode(ctx context.Context, req *connect.Request[user.ResendPhoneCodeRequest]) (resp *connect.Response[user.ResendPhoneCodeResponse], err error) {
|
||||
var phone *domain.Phone
|
||||
switch v := req.GetVerification().(type) {
|
||||
switch v := req.Msg.GetVerification().(type) {
|
||||
case *user.ResendPhoneCodeRequest_SendCode:
|
||||
phone, err = s.command.ResendUserPhoneCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||
phone, err = s.command.ResendUserPhoneCode(ctx, req.Msg.GetUserId(), s.userCodeAlg)
|
||||
case *user.ResendPhoneCodeRequest_ReturnCode:
|
||||
phone, err = s.command.ResendUserPhoneCodeReturnCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||
phone, err = s.command.ResendUserPhoneCodeReturnCode(ctx, req.Msg.GetUserId(), s.userCodeAlg)
|
||||
case nil:
|
||||
phone, err = s.command.ResendUserPhoneCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||
phone, err = s.command.ResendUserPhoneCode(ctx, req.Msg.GetUserId(), s.userCodeAlg)
|
||||
default:
|
||||
err = zerrors.ThrowUnimplementedf(nil, "USERv2-ResendUserPhoneCode", "verification oneOf %T in method SetPhone not implemented", v)
|
||||
}
|
||||
@@ -73,30 +74,30 @@ func (s *Server) ResendPhoneCode(ctx context.Context, req *user.ResendPhoneCodeR
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user.ResendPhoneCodeResponse{
|
||||
return connect.NewResponse(&user.ResendPhoneCodeResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: phone.Sequence,
|
||||
ChangeDate: timestamppb.New(phone.ChangeDate),
|
||||
ResourceOwner: phone.ResourceOwner,
|
||||
},
|
||||
VerificationCode: phone.PlainCode,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) VerifyPhone(ctx context.Context, req *user.VerifyPhoneRequest) (*user.VerifyPhoneResponse, error) {
|
||||
func (s *Server) VerifyPhone(ctx context.Context, req *connect.Request[user.VerifyPhoneRequest]) (*connect.Response[user.VerifyPhoneResponse], error) {
|
||||
details, err := s.command.VerifyUserPhone(ctx,
|
||||
req.GetUserId(),
|
||||
req.GetVerificationCode(),
|
||||
req.Msg.GetUserId(),
|
||||
req.Msg.GetVerificationCode(),
|
||||
s.userCodeAlg,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyPhoneResponse{
|
||||
return connect.NewResponse(&user.VerifyPhoneResponse{
|
||||
Details: &object.Details{
|
||||
Sequence: details.Sequence,
|
||||
ChangeDate: timestamppb.New(details.EventDate),
|
||||
ResourceOwner: details.ResourceOwner,
|
||||
},
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/muhlemmer/gu"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
@@ -13,34 +14,34 @@ import (
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) GetUserByID(ctx context.Context, req *user.GetUserByIDRequest) (_ *user.GetUserByIDResponse, err error) {
|
||||
resp, err := s.query.GetUserByIDWithPermission(ctx, true, req.GetUserId(), s.checkPermission)
|
||||
func (s *Server) GetUserByID(ctx context.Context, req *connect.Request[user.GetUserByIDRequest]) (_ *connect.Response[user.GetUserByIDResponse], err error) {
|
||||
resp, err := s.query.GetUserByIDWithPermission(ctx, true, req.Msg.GetUserId(), s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.GetUserByIDResponse{
|
||||
return connect.NewResponse(&user.GetUserByIDResponse{
|
||||
Details: object.DomainToDetailsPb(&domain.ObjectDetails{
|
||||
Sequence: resp.Sequence,
|
||||
EventDate: resp.ChangeDate,
|
||||
ResourceOwner: resp.ResourceOwner,
|
||||
}),
|
||||
User: userToPb(resp, s.assetAPIPrefix(ctx)),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ListUsers(ctx context.Context, req *user.ListUsersRequest) (*user.ListUsersResponse, error) {
|
||||
queries, filterOrgIds, err := listUsersRequestToModel(req)
|
||||
func (s *Server) ListUsers(ctx context.Context, req *connect.Request[user.ListUsersRequest]) (*connect.Response[user.ListUsersResponse], error) {
|
||||
queries, err := listUsersRequestToModel(req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := s.query.SearchUsers(ctx, queries, filterOrgIds, s.checkPermission)
|
||||
res, err := s.query.SearchUsers(ctx, queries, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ListUsersResponse{
|
||||
return connect.NewResponse(&user.ListUsersResponse{
|
||||
Result: UsersToPb(res.Users, s.assetAPIPrefix(ctx)),
|
||||
Details: object.ToListDetails(res.SearchResponse),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func UsersToPb(users []*query.User, assetPrefix string) []*user.User {
|
||||
@@ -165,11 +166,11 @@ func accessTokenTypeToPb(accessTokenType domain.OIDCTokenType) user.AccessTokenT
|
||||
}
|
||||
}
|
||||
|
||||
func listUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueries, string, error) {
|
||||
func listUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueries, error) {
|
||||
offset, limit, asc := object.ListQueryToQuery(req.Query)
|
||||
queries, filterOrgId, err := userQueriesToQuery(req.Queries, 0 /*start from level 0*/)
|
||||
queries, err := userQueriesToQuery(req.Queries, 0 /*start from level 0*/)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, err
|
||||
}
|
||||
return &query.UserSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
@@ -179,7 +180,7 @@ func listUsersRequestToModel(req *user.ListUsersRequest) (*query.UserSearchQueri
|
||||
SortingColumn: userFieldNameToSortingColumn(req.SortingColumn),
|
||||
},
|
||||
Queries: queries,
|
||||
}, filterOrgId, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func userFieldNameToSortingColumn(field user.UserFieldName) query.Column {
|
||||
@@ -209,18 +210,15 @@ func userFieldNameToSortingColumn(field user.UserFieldName) query.Column {
|
||||
}
|
||||
}
|
||||
|
||||
func userQueriesToQuery(queries []*user.SearchQuery, level uint8) (_ []query.SearchQuery, filterOrgId string, err error) {
|
||||
func userQueriesToQuery(queries []*user.SearchQuery, level uint8) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
if orgFilter := query.GetOrganizationIdQuery(); orgFilter != nil {
|
||||
filterOrgId = orgFilter.OrganizationId
|
||||
}
|
||||
q[i], err = userQueryToQuery(query, level)
|
||||
if err != nil {
|
||||
return nil, filterOrgId, err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, filterOrgId, nil
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func userQueryToQuery(query *user.SearchQuery, level uint8) (query.SearchQuery, error) {
|
||||
@@ -314,14 +312,14 @@ 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)
|
||||
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)
|
||||
mappedQueries, err := userQueriesToQuery(q.Queries, level+1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -2,8 +2,10 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
@@ -12,12 +14,12 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2beta/userconnect"
|
||||
)
|
||||
|
||||
var _ user.UserServiceServer = (*Server)(nil)
|
||||
var _ userconnect.UserServiceHandler = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
user.UnimplementedUserServiceServer
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
userCodeAlg crypto.EncryptionAlgorithm
|
||||
@@ -54,8 +56,12 @@ func CreateServer(
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||
user.RegisterUserServiceServer(grpcServer, s)
|
||||
func (s *Server) RegisterConnectServer(interceptors ...connect.Interceptor) (string, http.Handler) {
|
||||
return userconnect.NewUserServiceHandler(s, connect.WithInterceptors(interceptors...))
|
||||
}
|
||||
|
||||
func (s *Server) FileDescriptor() protoreflect.FileDescriptor {
|
||||
return user.File_zitadel_user_v2beta_user_service_proto
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
|
@@ -3,42 +3,44 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
|
||||
object "github.com/zitadel/zitadel/internal/api/grpc/object/v2beta"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) RegisterTOTP(ctx context.Context, req *user.RegisterTOTPRequest) (*user.RegisterTOTPResponse, error) {
|
||||
func (s *Server) RegisterTOTP(ctx context.Context, req *connect.Request[user.RegisterTOTPRequest]) (*connect.Response[user.RegisterTOTPResponse], error) {
|
||||
return totpDetailsToPb(
|
||||
s.command.AddUserTOTP(ctx, req.GetUserId(), ""),
|
||||
s.command.AddUserTOTP(ctx, req.Msg.GetUserId(), ""),
|
||||
)
|
||||
}
|
||||
|
||||
func totpDetailsToPb(totp *domain.TOTP, err error) (*user.RegisterTOTPResponse, error) {
|
||||
func totpDetailsToPb(totp *domain.TOTP, err error) (*connect.Response[user.RegisterTOTPResponse], error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RegisterTOTPResponse{
|
||||
return connect.NewResponse(&user.RegisterTOTPResponse{
|
||||
Details: object.DomainToDetailsPb(totp.ObjectDetails),
|
||||
Uri: totp.URI,
|
||||
Secret: totp.Secret,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) VerifyTOTPRegistration(ctx context.Context, req *user.VerifyTOTPRegistrationRequest) (*user.VerifyTOTPRegistrationResponse, error) {
|
||||
objectDetails, err := s.command.CheckUserTOTP(ctx, req.GetUserId(), req.GetCode(), "")
|
||||
func (s *Server) VerifyTOTPRegistration(ctx context.Context, req *connect.Request[user.VerifyTOTPRegistrationRequest]) (*connect.Response[user.VerifyTOTPRegistrationResponse], error) {
|
||||
objectDetails, err := s.command.CheckUserTOTP(ctx, req.Msg.GetUserId(), req.Msg.GetCode(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyTOTPRegistrationResponse{
|
||||
return connect.NewResponse(&user.VerifyTOTPRegistrationResponse{
|
||||
Details: object.DomainToDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveTOTP(ctx context.Context, req *user.RemoveTOTPRequest) (*user.RemoveTOTPResponse, error) {
|
||||
objectDetails, err := s.command.HumanRemoveTOTP(ctx, req.GetUserId(), "")
|
||||
func (s *Server) RemoveTOTP(ctx context.Context, req *connect.Request[user.RemoveTOTPRequest]) (*connect.Response[user.RemoveTOTPResponse], error) {
|
||||
objectDetails, err := s.command.HumanRemoveTOTP(ctx, req.Msg.GetUserId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RemoveTOTPResponse{Details: object.DomainToDetailsPb(objectDetails)}, nil
|
||||
return connect.NewResponse(&user.RemoveTOTPResponse{Details: object.DomainToDetailsPb(objectDetails)}), nil
|
||||
}
|
||||
|
@@ -63,7 +63,7 @@ func Test_totpDetailsToPb(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := totpDetailsToPb(tt.args.otp, tt.args.err)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
if !proto.Equal(tt.want, got) {
|
||||
if tt.want != nil && !proto.Equal(tt.want, got.Msg) {
|
||||
t.Errorf("RegisterTOTPResponse =\n%v\nwant\n%v", got, tt.want)
|
||||
}
|
||||
})
|
||||
|
@@ -3,40 +3,42 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
|
||||
object "github.com/zitadel/zitadel/internal/api/grpc/object/v2beta"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) RegisterU2F(ctx context.Context, req *user.RegisterU2FRequest) (*user.RegisterU2FResponse, error) {
|
||||
func (s *Server) RegisterU2F(ctx context.Context, req *connect.Request[user.RegisterU2FRequest]) (*connect.Response[user.RegisterU2FResponse], error) {
|
||||
return u2fRegistrationDetailsToPb(
|
||||
s.command.RegisterUserU2F(ctx, req.GetUserId(), "", req.GetDomain()),
|
||||
s.command.RegisterUserU2F(ctx, req.Msg.GetUserId(), "", req.Msg.GetDomain()),
|
||||
)
|
||||
}
|
||||
|
||||
func u2fRegistrationDetailsToPb(details *domain.WebAuthNRegistrationDetails, err error) (*user.RegisterU2FResponse, error) {
|
||||
func u2fRegistrationDetailsToPb(details *domain.WebAuthNRegistrationDetails, err error) (*connect.Response[user.RegisterU2FResponse], error) {
|
||||
objectDetails, options, err := webAuthNRegistrationDetailsToPb(details, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.RegisterU2FResponse{
|
||||
return connect.NewResponse(&user.RegisterU2FResponse{
|
||||
Details: objectDetails,
|
||||
U2FId: details.ID,
|
||||
PublicKeyCredentialCreationOptions: options,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) VerifyU2FRegistration(ctx context.Context, req *user.VerifyU2FRegistrationRequest) (*user.VerifyU2FRegistrationResponse, error) {
|
||||
pkc, err := req.GetPublicKeyCredential().MarshalJSON()
|
||||
func (s *Server) VerifyU2FRegistration(ctx context.Context, req *connect.Request[user.VerifyU2FRegistrationRequest]) (*connect.Response[user.VerifyU2FRegistrationResponse], error) {
|
||||
pkc, err := req.Msg.GetPublicKeyCredential().MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "USERv2-IeTh4", "Errors.Internal")
|
||||
}
|
||||
objectDetails, err := s.command.HumanVerifyU2FSetup(ctx, req.GetUserId(), "", req.GetTokenName(), "", pkc)
|
||||
objectDetails, err := s.command.HumanVerifyU2FSetup(ctx, req.Msg.GetUserId(), "", req.Msg.GetTokenName(), "", pkc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyU2FRegistrationResponse{
|
||||
return connect.NewResponse(&user.VerifyU2FRegistrationResponse{
|
||||
Details: object.DomainToDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
@@ -92,11 +92,11 @@ func Test_u2fRegistrationDetailsToPb(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := u2fRegistrationDetailsToPb(tt.args.details, tt.args.err)
|
||||
require.ErrorIs(t, err, tt.wantErr)
|
||||
if !proto.Equal(tt.want, got) {
|
||||
if tt.want != nil && !proto.Equal(tt.want, got.Msg) {
|
||||
t.Errorf("Not equal:\nExpected\n%s\nActual:%s", tt.want, got)
|
||||
}
|
||||
if tt.want != nil {
|
||||
grpc.AllFieldsSet(t, got.ProtoReflect())
|
||||
grpc.AllFieldsSet(t, got.Msg.ProtoReflect())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"golang.org/x/text/language"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
@@ -23,8 +24,8 @@ import (
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) AddHumanUser(ctx context.Context, req *user.AddHumanUserRequest) (_ *user.AddHumanUserResponse, err error) {
|
||||
human, err := AddUserRequestToAddHuman(req)
|
||||
func (s *Server) AddHumanUser(ctx context.Context, req *connect.Request[user.AddHumanUserRequest]) (_ *connect.Response[user.AddHumanUserResponse], err error) {
|
||||
human, err := AddUserRequestToAddHuman(req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -32,12 +33,12 @@ func (s *Server) AddHumanUser(ctx context.Context, req *user.AddHumanUserRequest
|
||||
if err = s.command.AddUserHuman(ctx, orgID, human, false, s.userCodeAlg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.AddHumanUserResponse{
|
||||
return connect.NewResponse(&user.AddHumanUserResponse{
|
||||
UserId: human.ID,
|
||||
Details: object.DomainToDetailsPb(human.Details),
|
||||
EmailCode: human.EmailCode,
|
||||
PhoneCode: human.PhoneCode,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func AddUserRequestToAddHuman(req *user.AddHumanUserRequest) (*command.AddHuman, error) {
|
||||
@@ -115,8 +116,8 @@ func genderToDomain(gender user.Gender) domain.Gender {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) UpdateHumanUser(ctx context.Context, req *user.UpdateHumanUserRequest) (_ *user.UpdateHumanUserResponse, err error) {
|
||||
human, err := UpdateUserRequestToChangeHuman(req)
|
||||
func (s *Server) UpdateHumanUser(ctx context.Context, req *connect.Request[user.UpdateHumanUserRequest]) (_ *connect.Response[user.UpdateHumanUserResponse], err error) {
|
||||
human, err := UpdateUserRequestToChangeHuman(req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -124,51 +125,51 @@ func (s *Server) UpdateHumanUser(ctx context.Context, req *user.UpdateHumanUserR
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.UpdateHumanUserResponse{
|
||||
return connect.NewResponse(&user.UpdateHumanUserResponse{
|
||||
Details: object.DomainToDetailsPb(human.Details),
|
||||
EmailCode: human.EmailCode,
|
||||
PhoneCode: human.PhoneCode,
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) LockUser(ctx context.Context, req *user.LockUserRequest) (_ *user.LockUserResponse, err error) {
|
||||
details, err := s.command.LockUserV2(ctx, req.UserId)
|
||||
func (s *Server) LockUser(ctx context.Context, req *connect.Request[user.LockUserRequest]) (_ *connect.Response[user.LockUserResponse], err error) {
|
||||
details, err := s.command.LockUserV2(ctx, req.Msg.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.LockUserResponse{
|
||||
return connect.NewResponse(&user.LockUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) UnlockUser(ctx context.Context, req *user.UnlockUserRequest) (_ *user.UnlockUserResponse, err error) {
|
||||
details, err := s.command.UnlockUserV2(ctx, req.UserId)
|
||||
func (s *Server) UnlockUser(ctx context.Context, req *connect.Request[user.UnlockUserRequest]) (_ *connect.Response[user.UnlockUserResponse], err error) {
|
||||
details, err := s.command.UnlockUserV2(ctx, req.Msg.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.UnlockUserResponse{
|
||||
return connect.NewResponse(&user.UnlockUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateUser(ctx context.Context, req *user.DeactivateUserRequest) (_ *user.DeactivateUserResponse, err error) {
|
||||
details, err := s.command.DeactivateUserV2(ctx, req.UserId)
|
||||
func (s *Server) DeactivateUser(ctx context.Context, req *connect.Request[user.DeactivateUserRequest]) (_ *connect.Response[user.DeactivateUserResponse], err error) {
|
||||
details, err := s.command.DeactivateUserV2(ctx, req.Msg.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.DeactivateUserResponse{
|
||||
return connect.NewResponse(&user.DeactivateUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ReactivateUser(ctx context.Context, req *user.ReactivateUserRequest) (_ *user.ReactivateUserResponse, err error) {
|
||||
details, err := s.command.ReactivateUserV2(ctx, req.UserId)
|
||||
func (s *Server) ReactivateUser(ctx context.Context, req *connect.Request[user.ReactivateUserRequest]) (_ *connect.Response[user.ReactivateUserResponse], err error) {
|
||||
details, err := s.command.ReactivateUserV2(ctx, req.Msg.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ReactivateUserResponse{
|
||||
return connect.NewResponse(&user.ReactivateUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func ifNotNilPtr[v, p any](value *v, conv func(v) p) *p {
|
||||
@@ -260,32 +261,32 @@ func SetHumanPasswordToPassword(password *user.SetPassword) *command.Password {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) AddIDPLink(ctx context.Context, req *user.AddIDPLinkRequest) (_ *user.AddIDPLinkResponse, err error) {
|
||||
details, err := s.command.AddUserIDPLink(ctx, req.UserId, "", &command.AddLink{
|
||||
IDPID: req.GetIdpLink().GetIdpId(),
|
||||
DisplayName: req.GetIdpLink().GetUserName(),
|
||||
IDPExternalID: req.GetIdpLink().GetUserId(),
|
||||
func (s *Server) AddIDPLink(ctx context.Context, req *connect.Request[user.AddIDPLinkRequest]) (_ *connect.Response[user.AddIDPLinkResponse], err error) {
|
||||
details, err := s.command.AddUserIDPLink(ctx, req.Msg.GetUserId(), "", &command.AddLink{
|
||||
IDPID: req.Msg.GetIdpLink().GetIdpId(),
|
||||
DisplayName: req.Msg.GetIdpLink().GetUserName(),
|
||||
IDPExternalID: req.Msg.GetIdpLink().GetUserId(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.AddIDPLinkResponse{
|
||||
return connect.NewResponse(&user.AddIDPLinkResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteUser(ctx context.Context, req *user.DeleteUserRequest) (_ *user.DeleteUserResponse, err error) {
|
||||
memberships, grants, err := s.removeUserDependencies(ctx, req.GetUserId())
|
||||
func (s *Server) DeleteUser(ctx context.Context, req *connect.Request[user.DeleteUserRequest]) (_ *connect.Response[user.DeleteUserResponse], err error) {
|
||||
memberships, grants, err := s.removeUserDependencies(ctx, req.Msg.GetUserId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.RemoveUserV2(ctx, req.UserId, "", memberships, grants...)
|
||||
details, err := s.command.RemoveUserV2(ctx, req.Msg.GetUserId(), "", memberships, grants...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.DeleteUserResponse{
|
||||
return connect.NewResponse(&user.DeleteUserResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) removeUserDependencies(ctx context.Context, userID string) ([]*command.CascadingMembership, []string, error) {
|
||||
@@ -360,18 +361,18 @@ func userGrantsToIDs(userGrants []*query.UserGrant) []string {
|
||||
return converted
|
||||
}
|
||||
|
||||
func (s *Server) StartIdentityProviderIntent(ctx context.Context, req *user.StartIdentityProviderIntentRequest) (_ *user.StartIdentityProviderIntentResponse, err error) {
|
||||
switch t := req.GetContent().(type) {
|
||||
func (s *Server) StartIdentityProviderIntent(ctx context.Context, req *connect.Request[user.StartIdentityProviderIntentRequest]) (_ *connect.Response[user.StartIdentityProviderIntentResponse], err error) {
|
||||
switch t := req.Msg.GetContent().(type) {
|
||||
case *user.StartIdentityProviderIntentRequest_Urls:
|
||||
return s.startIDPIntent(ctx, req.GetIdpId(), t.Urls)
|
||||
return s.startIDPIntent(ctx, req.Msg.GetIdpId(), t.Urls)
|
||||
case *user.StartIdentityProviderIntentRequest_Ldap:
|
||||
return s.startLDAPIntent(ctx, req.GetIdpId(), t.Ldap)
|
||||
return s.startLDAPIntent(ctx, req.Msg.GetIdpId(), t.Ldap)
|
||||
default:
|
||||
return nil, zerrors.ThrowUnimplementedf(nil, "USERv2-S2g21", "type oneOf %T in method StartIdentityProviderIntent not implemented", t)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.RedirectURLs) (*user.StartIdentityProviderIntentResponse, error) {
|
||||
func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.RedirectURLs) (*connect.Response[user.StartIdentityProviderIntentResponse], error) {
|
||||
state, session, err := s.command.AuthFromProvider(ctx, idpID, s.idpCallback(ctx), s.samlRootURL(ctx, idpID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -380,22 +381,31 @@ func (s *Server) startIDPIntent(ctx context.Context, idpID string, urls *user.Re
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content, redirect := session.GetAuth(ctx)
|
||||
if redirect {
|
||||
return &user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_AuthUrl{AuthUrl: content},
|
||||
}, nil
|
||||
auth, err := session.GetAuth(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_PostForm{
|
||||
PostForm: []byte(content),
|
||||
},
|
||||
}, nil
|
||||
switch a := auth.(type) {
|
||||
case *idp.RedirectAuth:
|
||||
return connect.NewResponse(&user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_AuthUrl{AuthUrl: a.RedirectURL},
|
||||
}), nil
|
||||
case *idp.FormAuth:
|
||||
return connect.NewResponse(&user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_FormData{
|
||||
FormData: &user.FormData{
|
||||
Url: a.URL,
|
||||
Fields: a.Fields,
|
||||
},
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "USERv2-3g2j3", "type oneOf %T in method StartIdentityProviderIntent not implemented", auth)
|
||||
}
|
||||
|
||||
func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*user.StartIdentityProviderIntentResponse, error) {
|
||||
func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredentials *user.LDAPCredentials) (*connect.Response[user.StartIdentityProviderIntentResponse], error) {
|
||||
intentWriteModel, details, err := s.command.CreateIntent(ctx, "", idpID, "", "", authz.GetInstance(ctx).InstanceID(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -411,7 +421,7 @@ func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredenti
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.StartIdentityProviderIntentResponse{
|
||||
return connect.NewResponse(&user.StartIdentityProviderIntentResponse{
|
||||
Details: object.DomainToDetailsPb(details),
|
||||
NextStep: &user.StartIdentityProviderIntentResponse_IdpIntent{
|
||||
IdpIntent: &user.IDPIntent{
|
||||
@@ -420,7 +430,7 @@ func (s *Server) startLDAPIntent(ctx context.Context, idpID string, ldapCredenti
|
||||
UserId: userID,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) checkLinkedExternalUser(ctx context.Context, idpID, externalUserID string) (string, error) {
|
||||
@@ -474,12 +484,12 @@ func (s *Server) ldapLogin(ctx context.Context, idpID, username, password string
|
||||
return externalUser, userID, session, nil
|
||||
}
|
||||
|
||||
func (s *Server) RetrieveIdentityProviderIntent(ctx context.Context, req *user.RetrieveIdentityProviderIntentRequest) (_ *user.RetrieveIdentityProviderIntentResponse, err error) {
|
||||
intent, err := s.command.GetIntentWriteModel(ctx, req.GetIdpIntentId(), "")
|
||||
func (s *Server) RetrieveIdentityProviderIntent(ctx context.Context, req *connect.Request[user.RetrieveIdentityProviderIntentRequest]) (_ *connect.Response[user.RetrieveIdentityProviderIntentResponse], err error) {
|
||||
intent, err := s.command.GetIntentWriteModel(ctx, req.Msg.GetIdpIntentId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.checkIntentToken(req.GetIdpIntentToken(), intent.AggregateID); err != nil {
|
||||
if err := s.checkIntentToken(req.Msg.GetIdpIntentToken(), intent.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if intent.State != domain.IDPIntentStateSucceeded {
|
||||
@@ -491,7 +501,7 @@ func (s *Server) RetrieveIdentityProviderIntent(ctx context.Context, req *user.R
|
||||
return idpIntentToIDPIntentPb(intent, s.idpAlg)
|
||||
}
|
||||
|
||||
func idpIntentToIDPIntentPb(intent *command.IDPIntentWriteModel, alg crypto.EncryptionAlgorithm) (_ *user.RetrieveIdentityProviderIntentResponse, err error) {
|
||||
func idpIntentToIDPIntentPb(intent *command.IDPIntentWriteModel, alg crypto.EncryptionAlgorithm) (_ *connect.Response[user.RetrieveIdentityProviderIntentResponse], err error) {
|
||||
rawInformation := new(structpb.Struct)
|
||||
err = rawInformation.UnmarshalJSON(intent.IDPUser)
|
||||
if err != nil {
|
||||
@@ -530,7 +540,7 @@ func idpIntentToIDPIntentPb(intent *command.IDPIntentWriteModel, alg crypto.Encr
|
||||
information.IdpInformation.Access = IDPSAMLResponseToPb(assertion)
|
||||
}
|
||||
|
||||
return information, nil
|
||||
return connect.NewResponse(information), nil
|
||||
}
|
||||
|
||||
func idpOAuthTokensToPb(idpIDToken string, idpAccessToken *crypto.CryptoValue, alg crypto.EncryptionAlgorithm) (_ *user.IDPInformation_Oauth, err error) {
|
||||
@@ -593,15 +603,15 @@ func (s *Server) checkIntentToken(token string, intentID string) error {
|
||||
return crypto.CheckToken(s.idpAlg, token, intentID)
|
||||
}
|
||||
|
||||
func (s *Server) ListAuthenticationMethodTypes(ctx context.Context, req *user.ListAuthenticationMethodTypesRequest) (*user.ListAuthenticationMethodTypesResponse, error) {
|
||||
authMethods, err := s.query.ListUserAuthMethodTypes(ctx, req.GetUserId(), true, false, "")
|
||||
func (s *Server) ListAuthenticationMethodTypes(ctx context.Context, req *connect.Request[user.ListAuthenticationMethodTypesRequest]) (*connect.Response[user.ListAuthenticationMethodTypesResponse], error) {
|
||||
authMethods, err := s.query.ListUserAuthMethodTypes(ctx, req.Msg.GetUserId(), true, false, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ListAuthenticationMethodTypesResponse{
|
||||
return connect.NewResponse(&user.ListAuthenticationMethodTypesResponse{
|
||||
Details: object.ToListDetails(authMethods.SearchResponse),
|
||||
AuthMethodTypes: authMethodTypesToPb(authMethods.AuthMethodTypes),
|
||||
}, nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
func authMethodTypesToPb(methodTypes []domain.UserAuthMethodType) []user.AuthenticationMethodType {
|
||||
|
@@ -322,7 +322,9 @@ func Test_idpIntentToIDPIntentPb(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := idpIntentToIDPIntentPb(tt.args.intent, tt.args.alg)
|
||||
require.ErrorIs(t, err, tt.res.err)
|
||||
grpc.AllFieldsEqual(t, tt.res.resp.ProtoReflect(), got.ProtoReflect(), grpc.CustomMappers)
|
||||
if tt.res.resp != nil {
|
||||
grpc.AllFieldsEqual(t, tt.res.resp.ProtoReflect(), got.Msg.ProtoReflect(), grpc.CustomMappers)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user