Files
zitadel/internal/api/grpc/settings/v2/integration_test/settings_test.go
T
Livio SpringandGitHub 8d5f92f80a feat(api): move organization settings endpoints to v2 (#10910)
# Which Problems Are Solved

As part of our efforts to simplify the structure and versions of our
APIs, were moving all existing v2beta endpoints to v2 and deprecate
them. They will be removed in Zitadel V5.

# How the Problems Are Solved

- This PR adds the organization settings endpoints to the settings
service v2. This was split from #10909, since that PR targets v4.x and
the corresponding feature is not yet available in v4.x, but only v5.
- The comments and have been improved and, where not already done, moved
from swagger annotations to proto.

# Additional Changes

None

# Additional Context

- relates to #10909 
- relates to #10772
2025-10-17 11:21:01 +02:00

470 lines
13 KiB
Go

//go:build integration
package settings_test
import (
"context"
"crypto/md5"
"encoding/hex"
"fmt"
"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/integration"
object_pb "github.com/zitadel/zitadel/pkg/grpc/object/v2"
"github.com/zitadel/zitadel/pkg/grpc/settings/v2"
)
func TestServer_SetSecuritySettings(t *testing.T) {
type args struct {
ctx context.Context
req *settings.SetSecuritySettingsRequest
}
tests := []struct {
name string
args args
want *settings.SetSecuritySettingsResponse
wantErr bool
}{
{
name: "permission error",
args: args{
ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
req: &settings.SetSecuritySettingsRequest{
EmbeddedIframe: &settings.EmbeddedIframeSettings{
Enabled: true,
AllowedOrigins: []string{"foo.com", "bar.com"},
},
EnableImpersonation: true,
},
},
wantErr: true,
},
{
name: "success allowed origins",
args: args{
ctx: AdminCTX,
req: &settings.SetSecuritySettingsRequest{
EmbeddedIframe: &settings.EmbeddedIframeSettings{
AllowedOrigins: []string{"foo.com", "bar.com"},
},
},
},
want: &settings.SetSecuritySettingsResponse{
Details: &object_pb.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
},
{
name: "success enable iframe embedding",
args: args{
ctx: AdminCTX,
req: &settings.SetSecuritySettingsRequest{
EmbeddedIframe: &settings.EmbeddedIframeSettings{
Enabled: true,
},
},
},
want: &settings.SetSecuritySettingsResponse{
Details: &object_pb.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
},
{
name: "success impersonation",
args: args{
ctx: AdminCTX,
req: &settings.SetSecuritySettingsRequest{
EnableImpersonation: true,
},
},
want: &settings.SetSecuritySettingsResponse{
Details: &object_pb.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
},
{
name: "success all",
args: args{
ctx: AdminCTX,
req: &settings.SetSecuritySettingsRequest{
EmbeddedIframe: &settings.EmbeddedIframeSettings{
Enabled: true,
AllowedOrigins: []string{"foo.com", "bar.com"},
},
EnableImpersonation: true,
},
},
want: &settings.SetSecuritySettingsResponse{
Details: &object_pb.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.SetSecuritySettings(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
integration.AssertDetails(t, tt.want, got)
})
}
}
func TestSetHostedLoginTranslation(t *testing.T) {
translations := map[string]any{"loginTitle": "Welcome to our service"}
protoTranslations, err := structpb.NewStruct(translations)
require.Nil(t, err)
hash := md5.Sum(fmt.Append(nil, translations))
tt := []struct {
testName string
inputCtx context.Context
inputRequest *settings.SetHostedLoginTranslationRequest
expectedErrorCode codes.Code
expectedErrorMsg string
expectedResponse *settings.SetHostedLoginTranslationResponse
}{
{
testName: "when unauthN context should return unauthN error",
inputCtx: CTX,
expectedErrorCode: codes.Unauthenticated,
expectedErrorMsg: "auth header missing",
},
{
testName: "when unauthZ context should return unauthZ error",
inputCtx: UserTypeLoginCtx,
expectedErrorCode: codes.PermissionDenied,
expectedErrorMsg: "No matching permissions found (AUTH-5mWD2)",
},
{
testName: "when authZ request should save to db and return etag",
inputCtx: AdminCTX,
inputRequest: &settings.SetHostedLoginTranslationRequest{
Level: &settings.SetHostedLoginTranslationRequest_OrganizationId{
OrganizationId: Instance.DefaultOrg.GetId(),
},
Translations: protoTranslations,
Locale: "en-US",
},
expectedResponse: &settings.SetHostedLoginTranslationResponse{
Etag: hex.EncodeToString(hash[:]),
},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// When
res, err := Client.SetHostedLoginTranslation(tc.inputCtx, tc.inputRequest)
// Then
assert.Equal(t, tc.expectedErrorCode, status.Code(err))
assert.Equal(t, tc.expectedErrorMsg, status.Convert(err).Message())
if tc.expectedErrorMsg == "" {
require.NoError(t, err)
assert.Equal(t, tc.expectedResponse.GetEtag(), res.GetEtag())
}
})
}
}
func TestServer_SetOrganizationSettings(t *testing.T) {
instance := integration.NewInstance(CTX)
iamOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
type args struct {
ctx context.Context
req *settings.SetOrganizationSettingsRequest
}
type want struct {
set bool
setDate bool
}
tests := []struct {
name string
prepare func(req *settings.SetOrganizationSettingsRequest)
args args
want want
wantErr bool
}{
{
name: "permission error",
args: args{
ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
req: &settings.SetOrganizationSettingsRequest{
OrganizationId: Instance.DefaultOrg.GetId(),
OrganizationScopedUsernames: gu.Ptr(true),
},
},
wantErr: true,
},
{
name: "org not provided",
args: args{
ctx: iamOwnerCTX,
req: &settings.SetOrganizationSettingsRequest{
OrganizationId: "",
OrganizationScopedUsernames: gu.Ptr(true),
},
},
wantErr: true,
},
{
name: "org not existing",
args: args{
ctx: iamOwnerCTX,
req: &settings.SetOrganizationSettingsRequest{
OrganizationId: "notexisting",
OrganizationScopedUsernames: gu.Ptr(true),
},
},
wantErr: true,
},
{
name: "success no changes",
prepare: func(req *settings.SetOrganizationSettingsRequest) {
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
req.OrganizationId = orgResp.GetOrganizationId()
},
args: args{
ctx: iamOwnerCTX,
req: &settings.SetOrganizationSettingsRequest{},
},
want: want{
set: false,
setDate: true,
},
},
{
name: "success user uniqueness",
prepare: func(req *settings.SetOrganizationSettingsRequest) {
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
req.OrganizationId = orgResp.GetOrganizationId()
},
args: args{
ctx: iamOwnerCTX,
req: &settings.SetOrganizationSettingsRequest{
OrganizationScopedUsernames: gu.Ptr(true),
},
},
want: want{
set: true,
setDate: true,
},
},
{
name: "success no change",
prepare: func(req *settings.SetOrganizationSettingsRequest) {
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
req.OrganizationId = orgResp.GetOrganizationId()
},
args: args{
ctx: iamOwnerCTX,
req: &settings.SetOrganizationSettingsRequest{
OrganizationScopedUsernames: gu.Ptr(false),
},
},
want: want{
set: false,
setDate: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
creationDate := time.Now().UTC()
if tt.prepare != nil {
tt.prepare(tt.args.req)
}
got, err := instance.Client.SettingsV2.SetOrganizationSettings(tt.args.ctx, tt.args.req)
if tt.wantErr {
assert.Error(t, err)
return
}
setDate := time.Time{}
if tt.want.set {
setDate = time.Now().UTC()
}
assert.NoError(t, err)
assertOrganizationSettingsResponse(t, creationDate, setDate, tt.want.setDate, got)
})
}
}
func assertOrganizationSettingsResponse(t *testing.T, creationDate, setDate time.Time, expectedSetDate bool, actualResp *settings.SetOrganizationSettingsResponse) {
if expectedSetDate {
if !setDate.IsZero() {
assert.WithinRange(t, actualResp.GetSetDate().AsTime(), creationDate, setDate)
} else {
assert.WithinRange(t, actualResp.GetSetDate().AsTime(), creationDate, time.Now().UTC())
}
} else {
assert.Nil(t, actualResp.SetDate)
}
}
func TestServer_DeleteOrganizationSettings(t *testing.T) {
instance := integration.NewInstance(CTX)
iamOwnerCTX := instance.WithAuthorizationToken(CTX, integration.UserTypeIAMOwner)
type args struct {
ctx context.Context
req *settings.DeleteOrganizationSettingsRequest
}
type want struct {
deletion bool
deletionDate bool
}
tests := []struct {
name string
prepare func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest)
args args
want want
wantErr bool
}{
{
name: "permission error",
prepare: func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest) {
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
req.OrganizationId = orgResp.GetOrganizationId()
instance.SetOrganizationSettings(iamOwnerCTX, t, orgResp.GetOrganizationId(), true)
},
args: args{
ctx: instance.WithAuthorizationToken(CTX, integration.UserTypeOrgOwner),
req: &settings.DeleteOrganizationSettingsRequest{
OrganizationId: Instance.DefaultOrg.GetId(),
},
},
wantErr: true,
},
{
name: "org not provided",
args: args{
ctx: iamOwnerCTX,
req: &settings.DeleteOrganizationSettingsRequest{
OrganizationId: "",
},
},
wantErr: true,
},
{
name: "org not existing",
args: args{
ctx: iamOwnerCTX,
req: &settings.DeleteOrganizationSettingsRequest{
OrganizationId: "notexisting",
},
},
want: want{
deletion: false,
deletionDate: false,
},
},
{
name: "success user uniqueness",
prepare: func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest) {
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
req.OrganizationId = orgResp.GetOrganizationId()
instance.SetOrganizationSettings(iamOwnerCTX, t, orgResp.GetOrganizationId(), true)
},
args: args{
ctx: iamOwnerCTX,
req: &settings.DeleteOrganizationSettingsRequest{},
},
want: want{
deletion: true,
deletionDate: true,
},
},
{
name: "success no existing",
prepare: func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest) {
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
req.OrganizationId = orgResp.GetOrganizationId()
},
args: args{
ctx: iamOwnerCTX,
req: &settings.DeleteOrganizationSettingsRequest{},
},
want: want{
deletion: false,
deletionDate: true,
},
},
{
name: "success already deleted",
prepare: func(t *testing.T, req *settings.DeleteOrganizationSettingsRequest) {
orgResp := instance.CreateOrganization(iamOwnerCTX, integration.OrganizationName(), integration.Email())
req.OrganizationId = orgResp.GetOrganizationId()
instance.SetOrganizationSettings(iamOwnerCTX, t, orgResp.GetOrganizationId(), true)
instance.DeleteOrganizationSettings(iamOwnerCTX, t, orgResp.GetOrganizationId())
},
args: args{
ctx: iamOwnerCTX,
req: &settings.DeleteOrganizationSettingsRequest{},
},
want: want{
deletion: false,
deletionDate: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
creationDate := time.Now().UTC()
if tt.prepare != nil {
tt.prepare(t, tt.args.req)
}
got, err := instance.Client.SettingsV2.DeleteOrganizationSettings(tt.args.ctx, tt.args.req)
if tt.wantErr {
assert.Error(t, err)
return
}
deletionDate := time.Time{}
if tt.want.deletion {
deletionDate = time.Now().UTC()
}
assert.NoError(t, err)
assertDeleteOrganizationSettingsResponse(t, creationDate, deletionDate, tt.want.deletionDate, got)
})
}
}
func assertDeleteOrganizationSettingsResponse(t *testing.T, creationDate, deletionDate time.Time, expectedDeletionDate bool, actualResp *settings.DeleteOrganizationSettingsResponse) {
if expectedDeletionDate {
if !deletionDate.IsZero() {
assert.WithinRange(t, actualResp.GetDeletionDate().AsTime(), creationDate, deletionDate)
} else {
assert.WithinRange(t, actualResp.GetDeletionDate().AsTime(), creationDate, time.Now().UTC())
}
} else {
assert.Nil(t, actualResp.DeletionDate)
}
}