1
0
mirror of https://github.com/zitadel/zitadel.git synced 2025-06-13 02:08:32 +00:00
zitadel/internal/api/grpc/user/v2/user_integration_test.go
Stefan Benz 319ebe7898
fix: add organizationID query for user v2 ListUsers and clean up depeprecated attribute ()
Add organizationID as query for ListUsers and clean up the deprecated Organisation attributes in other queries.

This PR removes the following fields from API requests (user service v2):

organisation from AddHumanUser (deprecated some time ago, organization still exists)
organization from GetUserByID
2024-03-21 08:07:00 +00:00

2346 lines
59 KiB
Go

//go:build integration
package user_test
import (
"context"
"fmt"
"net/url"
"os"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/api/grpc"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/idp"
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
)
var (
CTX context.Context
IamCTX context.Context
UserCTX context.Context
SystemCTX context.Context
ErrCTX context.Context
Tester *integration.Tester
Client user.UserServiceClient
)
func TestMain(m *testing.M) {
os.Exit(func() int {
ctx, errCtx, cancel := integration.Contexts(time.Hour)
defer cancel()
Tester = integration.NewTester(ctx)
defer Tester.Done()
UserCTX = Tester.WithAuthorization(ctx, integration.Login)
IamCTX = Tester.WithAuthorization(ctx, integration.IAMOwner)
SystemCTX = Tester.WithAuthorization(ctx, integration.SystemUser)
CTX, ErrCTX = Tester.WithAuthorization(ctx, integration.OrgOwner), errCtx
Client = Tester.Client.UserV2
return m.Run()
}())
}
func TestServer_AddHumanUser(t *testing.T) {
idpID := Tester.AddGenericOAuthProvider(t)
type args struct {
ctx context.Context
req *user.AddHumanUserRequest
}
tests := []struct {
name string
args args
want *user.AddHumanUserResponse
wantErr bool
}{
{
name: "default verification",
args: args{
CTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: Tester.Organisation.ID,
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Email: &user.SetHumanEmail{},
Phone: &user.SetHumanPhone{},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_Password{
Password: &user.Password{
Password: "DifficultPW666!",
ChangeRequired: true,
},
},
},
},
want: &user.AddHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "return email verification code",
args: args{
CTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: Tester.Organisation.ID,
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Email: &user.SetHumanEmail{
Verification: &user.SetHumanEmail_ReturnCode{
ReturnCode: &user.ReturnEmailVerificationCode{},
},
},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_Password{
Password: &user.Password{
Password: "DifficultPW666!",
ChangeRequired: true,
},
},
},
},
want: &user.AddHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
EmailCode: gu.Ptr("something"),
},
},
{
name: "custom template",
args: args{
CTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: Tester.Organisation.ID,
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Email: &user.SetHumanEmail{
Verification: &user.SetHumanEmail_SendCode{
SendCode: &user.SendEmailVerificationCode{
UrlTemplate: gu.Ptr("https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}"),
},
},
},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_Password{
Password: &user.Password{
Password: "DifficultPW666!",
ChangeRequired: true,
},
},
},
},
want: &user.AddHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "return phone verification code",
args: args{
CTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: Tester.Organisation.ID,
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Email: &user.SetHumanEmail{},
Phone: &user.SetHumanPhone{
Phone: "+41791234567",
Verification: &user.SetHumanPhone_ReturnCode{
ReturnCode: &user.ReturnPhoneVerificationCode{},
},
},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_Password{
Password: &user.Password{
Password: "DifficultPW666!",
ChangeRequired: true,
},
},
},
},
want: &user.AddHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
PhoneCode: gu.Ptr("something"),
},
},
{
name: "custom template error",
args: args{
CTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: Tester.Organisation.ID,
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Email: &user.SetHumanEmail{
Verification: &user.SetHumanEmail_SendCode{
SendCode: &user.SendEmailVerificationCode{
UrlTemplate: gu.Ptr("{{"),
},
},
},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_Password{
Password: &user.Password{
Password: "DifficultPW666!",
ChangeRequired: true,
},
},
},
},
wantErr: true,
},
{
name: "missing REQUIRED profile",
args: args{
CTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: Tester.Organisation.ID,
},
},
Email: &user.SetHumanEmail{
Verification: &user.SetHumanEmail_ReturnCode{
ReturnCode: &user.ReturnEmailVerificationCode{},
},
},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_Password{
Password: &user.Password{
Password: "DifficultPW666!",
ChangeRequired: true,
},
},
},
},
wantErr: true,
},
{
name: "missing REQUIRED email",
args: args{
CTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: Tester.Organisation.ID,
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_Password{
Password: &user.Password{
Password: "DifficultPW666!",
ChangeRequired: true,
},
},
},
},
wantErr: true,
},
{
name: "missing idp",
args: args{
CTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: Tester.Organisation.ID,
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Email: &user.SetHumanEmail{
Email: "livio@zitadel.com",
Verification: &user.SetHumanEmail_IsVerified{
IsVerified: true,
},
},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_Password{
Password: &user.Password{
Password: "DifficultPW666!",
ChangeRequired: false,
},
},
IdpLinks: []*user.IDPLink{
{
IdpId: "idpID",
UserId: "userID",
UserName: "username",
},
},
},
},
wantErr: true,
},
{
name: "with idp",
args: args{
CTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: Tester.Organisation.ID,
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Email: &user.SetHumanEmail{
Email: "livio@zitadel.com",
Verification: &user.SetHumanEmail_IsVerified{
IsVerified: true,
},
},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_Password{
Password: &user.Password{
Password: "DifficultPW666!",
ChangeRequired: false,
},
},
IdpLinks: []*user.IDPLink{
{
IdpId: idpID,
UserId: "userID",
UserName: "username",
},
},
},
},
want: &user.AddHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "hashed password",
args: args{
CTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: Tester.Organisation.ID,
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Email: &user.SetHumanEmail{},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_HashedPassword{
HashedPassword: &user.HashedPassword{
Hash: "$2y$12$hXUrnqdq1RIIYZ2HPytIIe5lXdIvbhqrTvdPsSF7o.jFh817Z6lwm",
},
},
},
},
want: &user.AddHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "unsupported hashed password",
args: args{
CTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: Tester.Organisation.ID,
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Email: &user.SetHumanEmail{},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_HashedPassword{
HashedPassword: &user.HashedPassword{
Hash: "$scrypt$ln=16,r=8,p=1$cmFuZG9tc2FsdGlzaGFyZA$Rh+NnJNo1I6nRwaNqbDm6kmADswD1+7FTKZ7Ln9D8nQ",
},
},
},
},
wantErr: true,
},
}
for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
userID := fmt.Sprint(time.Now().UnixNano() + int64(i))
tt.args.req.UserId = &userID
if email := tt.args.req.GetEmail(); email != nil {
email.Email = fmt.Sprintf("%s@me.now", userID)
}
if tt.want != nil {
tt.want.UserId = userID
}
got, err := Client.AddHumanUser(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Equal(t, tt.want.GetUserId(), got.GetUserId())
if tt.want.GetEmailCode() != "" {
assert.NotEmpty(t, got.GetEmailCode())
}
if tt.want.GetPhoneCode() != "" {
assert.NotEmpty(t, got.GetPhoneCode())
}
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_AddHumanUser_Permission(t *testing.T) {
newOrgOwnerEmail := fmt.Sprintf("%d@permission.com", time.Now().UnixNano())
newOrg := Tester.CreateOrganization(IamCTX, fmt.Sprintf("AddHuman%d", time.Now().UnixNano()), newOrgOwnerEmail)
type args struct {
ctx context.Context
req *user.AddHumanUserRequest
}
tests := []struct {
name string
args args
want *user.AddHumanUserResponse
wantErr bool
}{
{
name: "System, ok",
args: args{
SystemCTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: newOrg.GetOrganizationId(),
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Email: &user.SetHumanEmail{},
Phone: &user.SetHumanPhone{},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_Password{
Password: &user.Password{
Password: "DifficultPW666!",
ChangeRequired: true,
},
},
},
},
want: &user.AddHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: newOrg.GetOrganizationId(),
},
},
},
{
name: "Instance, ok",
args: args{
IamCTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: newOrg.GetOrganizationId(),
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Email: &user.SetHumanEmail{},
Phone: &user.SetHumanPhone{},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_Password{
Password: &user.Password{
Password: "DifficultPW666!",
ChangeRequired: true,
},
},
},
},
want: &user.AddHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: newOrg.GetOrganizationId(),
},
},
},
{
name: "Org, error",
args: args{
CTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: newOrg.GetOrganizationId(),
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Email: &user.SetHumanEmail{},
Phone: &user.SetHumanPhone{},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_Password{
Password: &user.Password{
Password: "DifficultPW666!",
ChangeRequired: true,
},
},
},
},
wantErr: true,
},
{
name: "User, error",
args: args{
UserCTX,
&user.AddHumanUserRequest{
Organization: &object.Organization{
Org: &object.Organization_OrgId{
OrgId: newOrg.GetOrganizationId(),
},
},
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
Email: &user.SetHumanEmail{},
Phone: &user.SetHumanPhone{},
Metadata: []*user.SetMetadataEntry{
{
Key: "somekey",
Value: []byte("somevalue"),
},
},
PasswordType: &user.AddHumanUserRequest_Password{
Password: &user.Password{
Password: "DifficultPW666!",
ChangeRequired: true,
},
},
},
},
wantErr: true,
},
}
for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
userID := fmt.Sprint(time.Now().UnixNano() + int64(i))
tt.args.req.UserId = &userID
if email := tt.args.req.GetEmail(); email != nil {
email.Email = fmt.Sprintf("%s@me.now", userID)
}
if tt.want != nil {
tt.want.UserId = userID
}
got, err := Client.AddHumanUser(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Equal(t, tt.want.GetUserId(), got.GetUserId())
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_UpdateHumanUser(t *testing.T) {
type args struct {
ctx context.Context
req *user.UpdateHumanUserRequest
}
tests := []struct {
name string
prepare func(request *user.UpdateHumanUserRequest) error
args args
want *user.UpdateHumanUserResponse
wantErr bool
}{
{
name: "not exisiting",
prepare: func(request *user.UpdateHumanUserRequest) error {
request.UserId = "notexisiting"
return nil
},
args: args{
CTX,
&user.UpdateHumanUserRequest{
Username: gu.Ptr("changed"),
},
},
wantErr: true,
},
{
name: "change username, ok",
prepare: func(request *user.UpdateHumanUserRequest) error {
userID := Tester.CreateHumanUser(CTX).GetUserId()
request.UserId = userID
return nil
},
args: args{
CTX,
&user.UpdateHumanUserRequest{
Username: gu.Ptr(fmt.Sprint(time.Now().UnixNano() + 1)),
},
},
want: &user.UpdateHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "change profile, ok",
prepare: func(request *user.UpdateHumanUserRequest) error {
userID := Tester.CreateHumanUser(CTX).GetUserId()
request.UserId = userID
return nil
},
args: args{
CTX,
&user.UpdateHumanUserRequest{
Profile: &user.SetHumanProfile{
GivenName: "Donald",
FamilyName: "Duck",
NickName: gu.Ptr("Dukkie"),
DisplayName: gu.Ptr("Donald Duck"),
PreferredLanguage: gu.Ptr("en"),
Gender: user.Gender_GENDER_DIVERSE.Enum(),
},
},
},
want: &user.UpdateHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "change email, ok",
prepare: func(request *user.UpdateHumanUserRequest) error {
userID := Tester.CreateHumanUser(CTX).GetUserId()
request.UserId = userID
return nil
},
args: args{
CTX,
&user.UpdateHumanUserRequest{
Email: &user.SetHumanEmail{
Email: "changed@test.com",
Verification: &user.SetHumanEmail_IsVerified{IsVerified: true},
},
},
},
want: &user.UpdateHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "change email, code, ok",
prepare: func(request *user.UpdateHumanUserRequest) error {
userID := Tester.CreateHumanUser(CTX).GetUserId()
request.UserId = userID
return nil
},
args: args{
CTX,
&user.UpdateHumanUserRequest{
Email: &user.SetHumanEmail{
Email: "changed@test.com",
Verification: &user.SetHumanEmail_ReturnCode{},
},
},
},
want: &user.UpdateHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
EmailCode: gu.Ptr("something"),
},
},
{
name: "change phone, ok",
prepare: func(request *user.UpdateHumanUserRequest) error {
userID := Tester.CreateHumanUser(CTX).GetUserId()
request.UserId = userID
return nil
},
args: args{
CTX,
&user.UpdateHumanUserRequest{
Phone: &user.SetHumanPhone{
Phone: "+41791234567",
Verification: &user.SetHumanPhone_IsVerified{IsVerified: true},
},
},
},
want: &user.UpdateHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "change phone, code, ok",
prepare: func(request *user.UpdateHumanUserRequest) error {
userID := Tester.CreateHumanUser(CTX).GetUserId()
request.UserId = userID
return nil
},
args: args{
CTX,
&user.UpdateHumanUserRequest{
Phone: &user.SetHumanPhone{
Phone: "+41791234568",
Verification: &user.SetHumanPhone_ReturnCode{},
},
},
},
want: &user.UpdateHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
PhoneCode: gu.Ptr("something"),
},
},
{
name: "change password, code, ok",
prepare: func(request *user.UpdateHumanUserRequest) error {
userID := Tester.CreateHumanUser(CTX).GetUserId()
request.UserId = userID
resp, err := Client.PasswordReset(CTX, &user.PasswordResetRequest{
UserId: userID,
Medium: &user.PasswordResetRequest_ReturnCode{
ReturnCode: &user.ReturnPasswordResetCode{},
},
})
if err != nil {
return err
}
request.Password.Verification = &user.SetPassword_VerificationCode{
VerificationCode: resp.GetVerificationCode(),
}
return nil
},
args: args{
CTX,
&user.UpdateHumanUserRequest{
Password: &user.SetPassword{
PasswordType: &user.SetPassword_Password{
Password: &user.Password{
Password: "Password1!",
ChangeRequired: true,
},
},
},
},
},
want: &user.UpdateHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "change hashed password, code, ok",
prepare: func(request *user.UpdateHumanUserRequest) error {
userID := Tester.CreateHumanUser(CTX).GetUserId()
request.UserId = userID
resp, err := Client.PasswordReset(CTX, &user.PasswordResetRequest{
UserId: userID,
Medium: &user.PasswordResetRequest_ReturnCode{
ReturnCode: &user.ReturnPasswordResetCode{},
},
})
if err != nil {
return err
}
request.Password.Verification = &user.SetPassword_VerificationCode{
VerificationCode: resp.GetVerificationCode(),
}
return nil
},
args: args{
CTX,
&user.UpdateHumanUserRequest{
Password: &user.SetPassword{
PasswordType: &user.SetPassword_HashedPassword{
HashedPassword: &user.HashedPassword{
Hash: "$2y$12$hXUrnqdq1RIIYZ2HPytIIe5lXdIvbhqrTvdPsSF7o.jFh817Z6lwm",
},
},
},
},
},
want: &user.UpdateHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "change hashed password, code, not supported",
prepare: func(request *user.UpdateHumanUserRequest) error {
userID := Tester.CreateHumanUser(CTX).GetUserId()
request.UserId = userID
resp, err := Client.PasswordReset(CTX, &user.PasswordResetRequest{
UserId: userID,
Medium: &user.PasswordResetRequest_ReturnCode{
ReturnCode: &user.ReturnPasswordResetCode{},
},
})
if err != nil {
return err
}
request.Password = &user.SetPassword{
Verification: &user.SetPassword_VerificationCode{
VerificationCode: resp.GetVerificationCode(),
},
}
return nil
},
args: args{
CTX,
&user.UpdateHumanUserRequest{
Password: &user.SetPassword{
PasswordType: &user.SetPassword_HashedPassword{
HashedPassword: &user.HashedPassword{
Hash: "$scrypt$ln=16,r=8,p=1$cmFuZG9tc2FsdGlzaGFyZA$Rh+NnJNo1I6nRwaNqbDm6kmADswD1+7FTKZ7Ln9D8nQ",
},
},
},
},
},
wantErr: true,
},
{
name: "change password, old password, ok",
prepare: func(request *user.UpdateHumanUserRequest) error {
userID := Tester.CreateHumanUser(CTX).GetUserId()
request.UserId = userID
resp, err := Client.PasswordReset(CTX, &user.PasswordResetRequest{
UserId: userID,
Medium: &user.PasswordResetRequest_ReturnCode{
ReturnCode: &user.ReturnPasswordResetCode{},
},
})
if err != nil {
return err
}
pw := "Password1."
_, err = Client.SetPassword(CTX, &user.SetPasswordRequest{
UserId: userID,
NewPassword: &user.Password{
Password: pw,
ChangeRequired: true,
},
Verification: &user.SetPasswordRequest_VerificationCode{
VerificationCode: resp.GetVerificationCode(),
},
})
if err != nil {
return err
}
request.Password.Verification = &user.SetPassword_CurrentPassword{
CurrentPassword: pw,
}
return nil
},
args: args{
CTX,
&user.UpdateHumanUserRequest{
Password: &user.SetPassword{
PasswordType: &user.SetPassword_Password{
Password: &user.Password{
Password: "Password1!",
ChangeRequired: true,
},
},
},
},
},
want: &user.UpdateHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.UpdateHumanUser(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
if tt.want.GetEmailCode() != "" {
assert.NotEmpty(t, got.GetEmailCode())
}
if tt.want.GetPhoneCode() != "" {
assert.NotEmpty(t, got.GetPhoneCode())
}
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_UpdateHumanUser_Permission(t *testing.T) {
newOrgOwnerEmail := fmt.Sprintf("%d@permission.update.com", time.Now().UnixNano())
newOrg := Tester.CreateOrganization(IamCTX, fmt.Sprintf("UpdateHuman%d", time.Now().UnixNano()), newOrgOwnerEmail)
newUserID := newOrg.CreatedAdmins[0].GetUserId()
type args struct {
ctx context.Context
req *user.UpdateHumanUserRequest
}
tests := []struct {
name string
args args
want *user.UpdateHumanUserResponse
wantErr bool
}{
{
name: "system, ok",
args: args{
SystemCTX,
&user.UpdateHumanUserRequest{
UserId: newUserID,
Username: gu.Ptr(fmt.Sprint("system", time.Now().UnixNano()+1)),
},
},
want: &user.UpdateHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: newOrg.GetOrganizationId(),
},
},
},
{
name: "instance, ok",
args: args{
IamCTX,
&user.UpdateHumanUserRequest{
UserId: newUserID,
Username: gu.Ptr(fmt.Sprint("instance", time.Now().UnixNano()+1)),
},
},
want: &user.UpdateHumanUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: newOrg.GetOrganizationId(),
},
},
},
{
name: "org, error",
args: args{
CTX,
&user.UpdateHumanUserRequest{
UserId: newUserID,
Username: gu.Ptr(fmt.Sprint("org", time.Now().UnixNano()+1)),
},
},
wantErr: true,
},
{
name: "user, error",
args: args{
UserCTX,
&user.UpdateHumanUserRequest{
UserId: newUserID,
Username: gu.Ptr(fmt.Sprint("user", time.Now().UnixNano()+1)),
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.UpdateHumanUser(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_LockUser(t *testing.T) {
type args struct {
ctx context.Context
req *user.LockUserRequest
prepare func(request *user.LockUserRequest) error
}
tests := []struct {
name string
args args
want *user.LockUserResponse
wantErr bool
}{
{
name: "lock, not existing",
args: args{
CTX,
&user.LockUserRequest{
UserId: "notexisting",
},
func(request *user.LockUserRequest) error { return nil },
},
wantErr: true,
},
{
name: "lock, ok",
args: args{
CTX,
&user.LockUserRequest{},
func(request *user.LockUserRequest) error {
resp := Tester.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
want: &user.LockUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "lock machine, ok",
args: args{
CTX,
&user.LockUserRequest{},
func(request *user.LockUserRequest) error {
resp := Tester.CreateMachineUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
want: &user.LockUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "lock, already locked",
args: args{
CTX,
&user.LockUserRequest{},
func(request *user.LockUserRequest) error {
resp := Tester.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
_, err := Client.LockUser(CTX, &user.LockUserRequest{
UserId: resp.GetUserId(),
})
return err
},
},
wantErr: true,
},
{
name: "lock machine, already locked",
args: args{
CTX,
&user.LockUserRequest{},
func(request *user.LockUserRequest) error {
resp := Tester.CreateMachineUser(CTX)
request.UserId = resp.GetUserId()
_, err := Client.LockUser(CTX, &user.LockUserRequest{
UserId: resp.GetUserId(),
})
return err
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.LockUser(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_UnLockUser(t *testing.T) {
type args struct {
ctx context.Context
req *user.UnlockUserRequest
prepare func(request *user.UnlockUserRequest) error
}
tests := []struct {
name string
args args
want *user.UnlockUserResponse
wantErr bool
}{
{
name: "unlock, not existing",
args: args{
CTX,
&user.UnlockUserRequest{
UserId: "notexisting",
},
func(request *user.UnlockUserRequest) error { return nil },
},
wantErr: true,
},
{
name: "unlock, not locked",
args: args{
ctx: CTX,
req: &user.UnlockUserRequest{},
prepare: func(request *user.UnlockUserRequest) error {
resp := Tester.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
wantErr: true,
},
{
name: "unlock machine, not locked",
args: args{
ctx: CTX,
req: &user.UnlockUserRequest{},
prepare: func(request *user.UnlockUserRequest) error {
resp := Tester.CreateMachineUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
wantErr: true,
},
{
name: "unlock, ok",
args: args{
ctx: CTX,
req: &user.UnlockUserRequest{},
prepare: func(request *user.UnlockUserRequest) error {
resp := Tester.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
_, err := Client.LockUser(CTX, &user.LockUserRequest{
UserId: resp.GetUserId(),
})
return err
},
},
want: &user.UnlockUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "unlock machine, ok",
args: args{
ctx: CTX,
req: &user.UnlockUserRequest{},
prepare: func(request *user.UnlockUserRequest) error {
resp := Tester.CreateMachineUser(CTX)
request.UserId = resp.GetUserId()
_, err := Client.LockUser(CTX, &user.LockUserRequest{
UserId: resp.GetUserId(),
})
return err
},
},
want: &user.UnlockUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.UnlockUser(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_DeactivateUser(t *testing.T) {
type args struct {
ctx context.Context
req *user.DeactivateUserRequest
prepare func(request *user.DeactivateUserRequest) error
}
tests := []struct {
name string
args args
want *user.DeactivateUserResponse
wantErr bool
}{
{
name: "deactivate, not existing",
args: args{
CTX,
&user.DeactivateUserRequest{
UserId: "notexisting",
},
func(request *user.DeactivateUserRequest) error { return nil },
},
wantErr: true,
},
{
name: "deactivate, ok",
args: args{
CTX,
&user.DeactivateUserRequest{},
func(request *user.DeactivateUserRequest) error {
resp := Tester.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
want: &user.DeactivateUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "deactivate machine, ok",
args: args{
CTX,
&user.DeactivateUserRequest{},
func(request *user.DeactivateUserRequest) error {
resp := Tester.CreateMachineUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
want: &user.DeactivateUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "deactivate, already deactivated",
args: args{
CTX,
&user.DeactivateUserRequest{},
func(request *user.DeactivateUserRequest) error {
resp := Tester.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
_, err := Client.DeactivateUser(CTX, &user.DeactivateUserRequest{
UserId: resp.GetUserId(),
})
return err
},
},
wantErr: true,
},
{
name: "deactivate machine, already deactivated",
args: args{
CTX,
&user.DeactivateUserRequest{},
func(request *user.DeactivateUserRequest) error {
resp := Tester.CreateMachineUser(CTX)
request.UserId = resp.GetUserId()
_, err := Client.DeactivateUser(CTX, &user.DeactivateUserRequest{
UserId: resp.GetUserId(),
})
return err
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.DeactivateUser(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_ReactivateUser(t *testing.T) {
type args struct {
ctx context.Context
req *user.ReactivateUserRequest
prepare func(request *user.ReactivateUserRequest) error
}
tests := []struct {
name string
args args
want *user.ReactivateUserResponse
wantErr bool
}{
{
name: "reactivate, not existing",
args: args{
CTX,
&user.ReactivateUserRequest{
UserId: "notexisting",
},
func(request *user.ReactivateUserRequest) error { return nil },
},
wantErr: true,
},
{
name: "reactivate, not deactivated",
args: args{
ctx: CTX,
req: &user.ReactivateUserRequest{},
prepare: func(request *user.ReactivateUserRequest) error {
resp := Tester.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
wantErr: true,
},
{
name: "reactivate machine, not deactivated",
args: args{
ctx: CTX,
req: &user.ReactivateUserRequest{},
prepare: func(request *user.ReactivateUserRequest) error {
resp := Tester.CreateMachineUser(CTX)
request.UserId = resp.GetUserId()
return nil
},
},
wantErr: true,
},
{
name: "reactivate, ok",
args: args{
ctx: CTX,
req: &user.ReactivateUserRequest{},
prepare: func(request *user.ReactivateUserRequest) error {
resp := Tester.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
_, err := Client.DeactivateUser(CTX, &user.DeactivateUserRequest{
UserId: resp.GetUserId(),
})
return err
},
},
want: &user.ReactivateUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "reactivate machine, ok",
args: args{
ctx: CTX,
req: &user.ReactivateUserRequest{},
prepare: func(request *user.ReactivateUserRequest) error {
resp := Tester.CreateMachineUser(CTX)
request.UserId = resp.GetUserId()
_, err := Client.DeactivateUser(CTX, &user.DeactivateUserRequest{
UserId: resp.GetUserId(),
})
return err
},
},
want: &user.ReactivateUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.ReactivateUser(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_DeleteUser(t *testing.T) {
projectResp, err := Tester.CreateProject(CTX)
require.NoError(t, err)
type args struct {
ctx context.Context
req *user.DeleteUserRequest
prepare func(request *user.DeleteUserRequest) error
}
tests := []struct {
name string
args args
want *user.DeleteUserResponse
wantErr bool
}{
{
name: "remove, not existing",
args: args{
CTX,
&user.DeleteUserRequest{
UserId: "notexisting",
},
func(request *user.DeleteUserRequest) error { return nil },
},
wantErr: true,
},
{
name: "remove human, ok",
args: args{
ctx: CTX,
req: &user.DeleteUserRequest{},
prepare: func(request *user.DeleteUserRequest) error {
resp := Tester.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
return err
},
},
want: &user.DeleteUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "remove machine, ok",
args: args{
ctx: CTX,
req: &user.DeleteUserRequest{},
prepare: func(request *user.DeleteUserRequest) error {
resp := Tester.CreateMachineUser(CTX)
request.UserId = resp.GetUserId()
return err
},
},
want: &user.DeleteUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
{
name: "remove dependencies, ok",
args: args{
ctx: CTX,
req: &user.DeleteUserRequest{},
prepare: func(request *user.DeleteUserRequest) error {
resp := Tester.CreateHumanUser(CTX)
request.UserId = resp.GetUserId()
Tester.CreateProjectUserGrant(t, CTX, projectResp.GetId(), request.UserId)
Tester.CreateProjectMembership(t, CTX, projectResp.GetId(), request.UserId)
Tester.CreateOrgMembership(t, CTX, request.UserId)
return err
},
},
want: &user.DeleteUserResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.DeleteUser(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_AddIDPLink(t *testing.T) {
idpID := Tester.AddGenericOAuthProvider(t)
type args struct {
ctx context.Context
req *user.AddIDPLinkRequest
}
tests := []struct {
name string
args args
want *user.AddIDPLinkResponse
wantErr bool
}{
{
name: "user does not exist",
args: args{
CTX,
&user.AddIDPLinkRequest{
UserId: "userID",
IdpLink: &user.IDPLink{
IdpId: idpID,
UserId: "userID",
UserName: "username",
},
},
},
want: nil,
wantErr: true,
},
{
name: "idp does not exist",
args: args{
CTX,
&user.AddIDPLinkRequest{
UserId: Tester.Users[integration.FirstInstanceUsersKey][integration.OrgOwner].ID,
IdpLink: &user.IDPLink{
IdpId: "idpID",
UserId: "userID",
UserName: "username",
},
},
},
want: nil,
wantErr: true,
},
{
name: "add link",
args: args{
CTX,
&user.AddIDPLinkRequest{
UserId: Tester.Users[integration.FirstInstanceUsersKey][integration.OrgOwner].ID,
IdpLink: &user.IDPLink{
IdpId: idpID,
UserId: "userID",
UserName: "username",
},
},
},
want: &user.AddIDPLinkResponse{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.AddIDPLink(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestServer_StartIdentityProviderIntent(t *testing.T) {
idpID := Tester.AddGenericOAuthProvider(t)
samlIdpID := Tester.AddSAMLProvider(t)
samlRedirectIdpID := Tester.AddSAMLRedirectProvider(t)
samlPostIdpID := Tester.AddSAMLPostProvider(t)
type args struct {
ctx context.Context
req *user.StartIdentityProviderIntentRequest
}
type want struct {
details *object.Details
url string
parametersExisting []string
parametersEqual map[string]string
postForm bool
}
tests := []struct {
name string
args args
want want
wantErr bool
}{
{
name: "missing urls",
args: args{
CTX,
&user.StartIdentityProviderIntentRequest{
IdpId: idpID,
},
},
wantErr: true,
},
{
name: "next step oauth auth url",
args: args{
CTX,
&user.StartIdentityProviderIntentRequest{
IdpId: idpID,
Content: &user.StartIdentityProviderIntentRequest_Urls{
Urls: &user.RedirectURLs{
SuccessUrl: "https://example.com/success",
FailureUrl: "https://example.com/failure",
},
},
},
},
want: want{
details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
url: "https://example.com/oauth/v2/authorize",
parametersEqual: map[string]string{
"client_id": "clientID",
"prompt": "select_account",
"redirect_uri": "http://" + Tester.Config.ExternalDomain + ":8080/idps/callback",
"response_type": "code",
"scope": "openid profile email",
},
parametersExisting: []string{"state"},
},
wantErr: false,
},
{
name: "next step saml default",
args: args{
CTX,
&user.StartIdentityProviderIntentRequest{
IdpId: samlIdpID,
Content: &user.StartIdentityProviderIntentRequest_Urls{
Urls: &user.RedirectURLs{
SuccessUrl: "https://example.com/success",
FailureUrl: "https://example.com/failure",
},
},
},
},
want: want{
details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
url: "http://" + Tester.Config.ExternalDomain + ":8000/sso",
parametersExisting: []string{"RelayState", "SAMLRequest"},
},
wantErr: false,
},
{
name: "next step saml auth url",
args: args{
CTX,
&user.StartIdentityProviderIntentRequest{
IdpId: samlRedirectIdpID,
Content: &user.StartIdentityProviderIntentRequest_Urls{
Urls: &user.RedirectURLs{
SuccessUrl: "https://example.com/success",
FailureUrl: "https://example.com/failure",
},
},
},
},
want: want{
details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
url: "http://" + Tester.Config.ExternalDomain + ":8000/sso",
parametersExisting: []string{"RelayState", "SAMLRequest"},
},
wantErr: false,
},
{
name: "next step saml form",
args: args{
CTX,
&user.StartIdentityProviderIntentRequest{
IdpId: samlPostIdpID,
Content: &user.StartIdentityProviderIntentRequest_Urls{
Urls: &user.RedirectURLs{
SuccessUrl: "https://example.com/success",
FailureUrl: "https://example.com/failure",
},
},
},
},
want: want{
details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Tester.Organisation.ID,
},
postForm: true,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.StartIdentityProviderIntent(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
if tt.want.url != "" {
authUrl, err := url.Parse(got.GetAuthUrl())
assert.NoError(t, err)
assert.Len(t, authUrl.Query(), len(tt.want.parametersEqual)+len(tt.want.parametersExisting))
for _, existing := range tt.want.parametersExisting {
assert.True(t, authUrl.Query().Has(existing))
}
for key, equal := range tt.want.parametersEqual {
assert.Equal(t, equal, authUrl.Query().Get(key))
}
}
if tt.want.postForm {
assert.NotEmpty(t, got.GetPostForm())
}
integration.AssertDetails(t, &user.StartIdentityProviderIntentResponse{
Details: tt.want.details,
}, got)
})
}
}
func TestServer_RetrieveIdentityProviderIntent(t *testing.T) {
idpID := Tester.AddGenericOAuthProvider(t)
intentID := Tester.CreateIntent(t, idpID)
successfulID, token, changeDate, sequence := Tester.CreateSuccessfulOAuthIntent(t, idpID, "", "id")
successfulWithUserID, WithUsertoken, WithUserchangeDate, WithUsersequence := Tester.CreateSuccessfulOAuthIntent(t, idpID, "user", "id")
ldapSuccessfulID, ldapToken, ldapChangeDate, ldapSequence := Tester.CreateSuccessfulLDAPIntent(t, idpID, "", "id")
ldapSuccessfulWithUserID, ldapWithUserToken, ldapWithUserChangeDate, ldapWithUserSequence := Tester.CreateSuccessfulLDAPIntent(t, idpID, "user", "id")
samlSuccessfulID, samlToken, samlChangeDate, samlSequence := Tester.CreateSuccessfulSAMLIntent(t, idpID, "", "id")
type args struct {
ctx context.Context
req *user.RetrieveIdentityProviderIntentRequest
}
tests := []struct {
name string
args args
want *user.RetrieveIdentityProviderIntentResponse
wantErr bool
}{
{
name: "failed intent",
args: args{
CTX,
&user.RetrieveIdentityProviderIntentRequest{
IdpIntentId: intentID,
IdpIntentToken: "",
},
},
wantErr: true,
},
{
name: "wrong token",
args: args{
CTX,
&user.RetrieveIdentityProviderIntentRequest{
IdpIntentId: successfulID,
IdpIntentToken: "wrong token",
},
},
wantErr: true,
},
{
name: "retrieve successful intent",
args: args{
CTX,
&user.RetrieveIdentityProviderIntentRequest{
IdpIntentId: successfulID,
IdpIntentToken: token,
},
},
want: &user.RetrieveIdentityProviderIntentResponse{
Details: &object.Details{
ChangeDate: timestamppb.New(changeDate),
ResourceOwner: Tester.Organisation.ID,
Sequence: sequence,
},
IdpInformation: &user.IDPInformation{
Access: &user.IDPInformation_Oauth{
Oauth: &user.IDPOAuthAccessInformation{
AccessToken: "accessToken",
IdToken: gu.Ptr("idToken"),
},
},
IdpId: idpID,
UserId: "id",
UserName: "username",
RawInformation: func() *structpb.Struct {
s, err := structpb.NewStruct(map[string]interface{}{
"sub": "id",
"preferred_username": "username",
})
require.NoError(t, err)
return s
}(),
},
},
wantErr: false,
},
{
name: "retrieve successful intent with linked user",
args: args{
CTX,
&user.RetrieveIdentityProviderIntentRequest{
IdpIntentId: successfulWithUserID,
IdpIntentToken: WithUsertoken,
},
},
want: &user.RetrieveIdentityProviderIntentResponse{
Details: &object.Details{
ChangeDate: timestamppb.New(WithUserchangeDate),
ResourceOwner: Tester.Organisation.ID,
Sequence: WithUsersequence,
},
UserId: "user",
IdpInformation: &user.IDPInformation{
Access: &user.IDPInformation_Oauth{
Oauth: &user.IDPOAuthAccessInformation{
AccessToken: "accessToken",
IdToken: gu.Ptr("idToken"),
},
},
IdpId: idpID,
UserId: "id",
UserName: "username",
RawInformation: func() *structpb.Struct {
s, err := structpb.NewStruct(map[string]interface{}{
"sub": "id",
"preferred_username": "username",
})
require.NoError(t, err)
return s
}(),
},
},
wantErr: false,
},
{
name: "retrieve successful ldap intent",
args: args{
CTX,
&user.RetrieveIdentityProviderIntentRequest{
IdpIntentId: ldapSuccessfulID,
IdpIntentToken: ldapToken,
},
},
want: &user.RetrieveIdentityProviderIntentResponse{
Details: &object.Details{
ChangeDate: timestamppb.New(ldapChangeDate),
ResourceOwner: Tester.Organisation.ID,
Sequence: ldapSequence,
},
IdpInformation: &user.IDPInformation{
Access: &user.IDPInformation_Ldap{
Ldap: &user.IDPLDAPAccessInformation{
Attributes: func() *structpb.Struct {
s, err := structpb.NewStruct(map[string]interface{}{
"id": []interface{}{"id"},
"username": []interface{}{"username"},
"language": []interface{}{"en"},
})
require.NoError(t, err)
return s
}(),
},
},
IdpId: idpID,
UserId: "id",
UserName: "username",
RawInformation: func() *structpb.Struct {
s, err := structpb.NewStruct(map[string]interface{}{
"id": "id",
"preferredUsername": "username",
"preferredLanguage": "en",
})
require.NoError(t, err)
return s
}(),
},
},
wantErr: false,
},
{
name: "retrieve successful ldap intent with linked user",
args: args{
CTX,
&user.RetrieveIdentityProviderIntentRequest{
IdpIntentId: ldapSuccessfulWithUserID,
IdpIntentToken: ldapWithUserToken,
},
},
want: &user.RetrieveIdentityProviderIntentResponse{
Details: &object.Details{
ChangeDate: timestamppb.New(ldapWithUserChangeDate),
ResourceOwner: Tester.Organisation.ID,
Sequence: ldapWithUserSequence,
},
UserId: "user",
IdpInformation: &user.IDPInformation{
Access: &user.IDPInformation_Ldap{
Ldap: &user.IDPLDAPAccessInformation{
Attributes: func() *structpb.Struct {
s, err := structpb.NewStruct(map[string]interface{}{
"id": []interface{}{"id"},
"username": []interface{}{"username"},
"language": []interface{}{"en"},
})
require.NoError(t, err)
return s
}(),
},
},
IdpId: idpID,
UserId: "id",
UserName: "username",
RawInformation: func() *structpb.Struct {
s, err := structpb.NewStruct(map[string]interface{}{
"id": "id",
"preferredUsername": "username",
"preferredLanguage": "en",
})
require.NoError(t, err)
return s
}(),
},
},
wantErr: false,
},
{
name: "retrieve successful saml intent",
args: args{
CTX,
&user.RetrieveIdentityProviderIntentRequest{
IdpIntentId: samlSuccessfulID,
IdpIntentToken: samlToken,
},
},
want: &user.RetrieveIdentityProviderIntentResponse{
Details: &object.Details{
ChangeDate: timestamppb.New(samlChangeDate),
ResourceOwner: Tester.Organisation.ID,
Sequence: samlSequence,
},
IdpInformation: &user.IDPInformation{
Access: &user.IDPInformation_Saml{
Saml: &user.IDPSAMLAccessInformation{
Assertion: []byte("<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"id\" IssueInstant=\"0001-01-01T00:00:00Z\" Version=\"\"><Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" NameQualifier=\"\" SPNameQualifier=\"\" Format=\"\" SPProvidedID=\"\"></Issuer></Assertion>"),
},
},
IdpId: idpID,
UserId: "id",
UserName: "",
RawInformation: func() *structpb.Struct {
s, err := structpb.NewStruct(map[string]interface{}{
"id": "id",
"attributes": map[string]interface{}{
"attribute1": []interface{}{"value1"},
},
})
require.NoError(t, err)
return s
}(),
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.RetrieveIdentityProviderIntent(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
grpc.AllFieldsEqual(t, tt.want.ProtoReflect(), got.ProtoReflect(), grpc.CustomMappers)
})
}
}
func TestServer_ListAuthenticationMethodTypes(t *testing.T) {
userIDWithoutAuth := Tester.CreateHumanUser(CTX).GetUserId()
userIDWithPasskey := Tester.CreateHumanUser(CTX).GetUserId()
Tester.RegisterUserPasskey(CTX, userIDWithPasskey)
userMultipleAuth := Tester.CreateHumanUser(CTX).GetUserId()
Tester.RegisterUserPasskey(CTX, userMultipleAuth)
provider, err := Tester.Client.Mgmt.AddGenericOIDCProvider(CTX, &mgmt.AddGenericOIDCProviderRequest{
Name: "ListAuthenticationMethodTypes",
Issuer: "https://example.com",
ClientId: "client_id",
ClientSecret: "client_secret",
})
require.NoError(t, err)
_, err = Tester.Client.Mgmt.AddCustomLoginPolicy(CTX, &mgmt.AddCustomLoginPolicyRequest{})
require.Condition(t, func() bool {
code := status.Convert(err).Code()
return code == codes.AlreadyExists || code == codes.OK
})
_, err = Tester.Client.Mgmt.AddIDPToLoginPolicy(CTX, &mgmt.AddIDPToLoginPolicyRequest{
IdpId: provider.GetId(),
OwnerType: idp.IDPOwnerType_IDP_OWNER_TYPE_ORG,
})
require.NoError(t, err)
idpLink, err := Tester.Client.UserV2.AddIDPLink(CTX, &user.AddIDPLinkRequest{UserId: userMultipleAuth, IdpLink: &user.IDPLink{
IdpId: provider.GetId(),
UserId: "external-id",
UserName: "displayName",
}})
require.NoError(t, err)
// This should not remove the user IDP links
_, err = Tester.Client.Mgmt.RemoveIDPFromLoginPolicy(CTX, &mgmt.RemoveIDPFromLoginPolicyRequest{
IdpId: provider.GetId(),
})
require.NoError(t, err)
type args struct {
ctx context.Context
req *user.ListAuthenticationMethodTypesRequest
}
tests := []struct {
name string
args args
want *user.ListAuthenticationMethodTypesResponse
}{
{
name: "no auth",
args: args{
CTX,
&user.ListAuthenticationMethodTypesRequest{
UserId: userIDWithoutAuth,
},
},
want: &user.ListAuthenticationMethodTypesResponse{
Details: &object.ListDetails{
TotalResult: 0,
},
},
},
{
name: "with auth (passkey)",
args: args{
CTX,
&user.ListAuthenticationMethodTypesRequest{
UserId: userIDWithPasskey,
},
},
want: &user.ListAuthenticationMethodTypesResponse{
Details: &object.ListDetails{
TotalResult: 1,
},
AuthMethodTypes: []user.AuthenticationMethodType{
user.AuthenticationMethodType_AUTHENTICATION_METHOD_TYPE_PASSKEY,
},
},
},
{
name: "multiple auth",
args: args{
CTX,
&user.ListAuthenticationMethodTypesRequest{
UserId: userMultipleAuth,
},
},
want: &user.ListAuthenticationMethodTypesResponse{
Details: &object.ListDetails{
TotalResult: 2,
},
AuthMethodTypes: []user.AuthenticationMethodType{
user.AuthenticationMethodType_AUTHENTICATION_METHOD_TYPE_PASSKEY,
user.AuthenticationMethodType_AUTHENTICATION_METHOD_TYPE_IDP,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var got *user.ListAuthenticationMethodTypesResponse
var err error
for {
got, err = Client.ListAuthenticationMethodTypes(tt.args.ctx, tt.args.req)
if err == nil && !got.GetDetails().GetTimestamp().AsTime().Before(idpLink.GetDetails().GetChangeDate().AsTime()) {
break
}
select {
case <-CTX.Done():
t.Fatal(CTX.Err(), err)
case <-time.After(time.Second):
t.Log("retrying ListAuthenticationMethodTypes")
continue
}
}
require.NoError(t, err)
assert.Equal(t, tt.want.GetDetails().GetTotalResult(), got.GetDetails().GetTotalResult())
require.Equal(t, tt.want.GetAuthMethodTypes(), got.GetAuthMethodTypes())
})
}
}