feat: restrict languages (#6931)

* feat: return 404 or 409 if org reg disallowed

* fix: system limit permissions

* feat: add iam limits api

* feat: disallow public org registrations on default instance

* add integration test

* test: integration

* fix test

* docs: describe public org registrations

* avoid updating docs deps

* fix system limits integration test

* silence integration tests

* fix linting

* ignore strange linter complaints

* review

* improve reset properties naming

* redefine the api

* use restrictions aggregate

* test query

* simplify and test projection

* test commands

* fix unit tests

* move integration test

* support restrictions on default instance

* also test GetRestrictions

* self review

* lint

* abstract away resource owner

* fix tests

* configure supported languages

* fix allowed languages

* fix tests

* default lang must not be restricted

* preferred language must be allowed

* change preferred languages

* check languages everywhere

* lint

* test command side

* lint

* add integration test

* add integration test

* restrict supported ui locales

* lint

* lint

* cleanup

* lint

* allow undefined preferred language

* fix integration tests

* update main

* fix env var

* ignore linter

* ignore linter

* improve integration test config

* reduce cognitive complexity

* compile

* check for duplicates

* remove useless restriction checks

* review

* revert restriction renaming

* fix language restrictions

* lint

* generate

* allow custom texts for supported langs for now

* fix tests

* cleanup

* cleanup

* cleanup

* lint

* unsupported preferred lang is allowed

* fix integration test

* finish reverting to old property name

* finish reverting to old property name

* load languages

* refactor(i18n): centralize translators and fs

* lint

* amplify no validations on preferred languages

* fix integration test

* lint

* fix resetting allowed languages

* test unchanged restrictions
This commit is contained in:
Elio Bischof
2023-12-05 12:12:01 +01:00
committed by GitHub
parent 236930f109
commit dd33538c0a
123 changed files with 4133 additions and 2058 deletions

View File

@@ -0,0 +1,22 @@
package command
import (
"testing"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/i18n"
)
var (
SupportedLanguages = []language.Tag{language.English, language.German}
OnlyAllowedLanguages = []language.Tag{language.English}
AllowedLanguage = language.English
DisallowedLanguage = language.German
UnsupportedLanguage = language.Spanish
)
func TestMain(m *testing.M) {
i18n.SupportLanguages(SupportedLanguages...)
m.Run()
}

View File

@@ -13,6 +13,7 @@ import (
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/i18n"
"github.com/zitadel/zitadel/internal/id"
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
"github.com/zitadel/zitadel/internal/repository/feature"
@@ -107,18 +108,22 @@ type InstanceSetup struct {
EmailTemplate []byte
MessageTexts []*domain.CustomMessageText
SMTPConfiguration *smtp.Config
OIDCSettings *struct {
AccessTokenLifetime time.Duration
IdTokenLifetime time.Duration
RefreshTokenIdleExpiration time.Duration
RefreshTokenExpiration time.Duration
}
Quotas *struct {
Items []*SetQuota
}
Features map[domain.Feature]any
Limits *SetLimits
Restrictions *SetRestrictions
OIDCSettings *OIDCSettings
Quotas *SetQuotas
Features map[domain.Feature]any
Limits *SetLimits
Restrictions *SetRestrictions
}
type OIDCSettings struct {
AccessTokenLifetime time.Duration
IdTokenLifetime time.Duration
RefreshTokenIdleExpiration time.Duration
RefreshTokenExpiration time.Duration
}
type SetQuotas struct {
Items []*SetQuota
}
type SecretGenerators struct {
@@ -289,183 +294,32 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
prepareAddDefaultEmailTemplate(instanceAgg, setup.EmailTemplate),
}
if setup.Quotas != nil {
for _, q := range setup.Quotas.Items {
quotaId, err := c.idGenerator.Next()
if err != nil {
return "", "", nil, nil, err
}
validations = append(validations, c.SetQuotaCommand(quota.NewAggregate(quotaId, instanceID), nil, true, q))
}
if err := setupQuotas(c, &validations, setup.Quotas, instanceID); err != nil {
return "", "", nil, nil, err
}
for _, msg := range setup.MessageTexts {
validations = append(validations, prepareSetInstanceCustomMessageTexts(instanceAgg, msg))
}
console := &addOIDCApp{
AddApp: AddApp{
Aggregate: *projectAgg,
ID: setup.zitadel.consoleAppID,
Name: consoleAppName,
},
Version: domain.OIDCVersionV1,
RedirectUris: []string{},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeUserAgent,
AuthMethodType: domain.OIDCAuthMethodTypeNone,
PostLogoutRedirectUris: []string{},
DevMode: !c.externalSecure,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: false,
IDTokenRoleAssertion: false,
IDTokenUserinfoAssertion: false,
ClockSkew: 0,
}
setupMessageTexts(&validations, setup.MessageTexts, instanceAgg)
validations = append(validations,
AddOrgCommand(ctx, orgAgg, setup.Org.Name),
c.prepareSetDefaultOrg(instanceAgg, orgAgg.ID),
)
var pat *PersonalAccessToken
var machineKey *MachineKey
// only a human or a machine user should be created as owner
if setup.Org.Machine != nil && setup.Org.Machine.Machine != nil && !setup.Org.Machine.Machine.IsZero() {
validations = append(validations,
AddMachineCommand(userAgg, setup.Org.Machine.Machine),
)
if setup.Org.Machine.Pat != nil {
pat = NewPersonalAccessToken(orgID, userID, setup.Org.Machine.Pat.ExpirationDate, setup.Org.Machine.Pat.Scopes, domain.UserTypeMachine)
pat.TokenID, err = c.idGenerator.Next()
if err != nil {
return "", "", nil, nil, err
}
validations = append(validations, prepareAddPersonalAccessToken(pat, c.keyAlgorithm))
}
if setup.Org.Machine.MachineKey != nil {
machineKey = NewMachineKey(orgID, userID, setup.Org.Machine.MachineKey.ExpirationDate, setup.Org.Machine.MachineKey.Type)
machineKey.KeyID, err = c.idGenerator.Next()
if err != nil {
return "", "", nil, nil, err
}
validations = append(validations, prepareAddUserMachineKey(machineKey, c.machineKeySize))
}
} else if setup.Org.Human != nil {
setup.Org.Human.ID = userID
validations = append(validations,
c.AddHumanCommand(setup.Org.Human, orgID, c.userPasswordHasher, c.userEncryption, true),
)
}
validations = append(validations,
c.AddOrgMemberCommand(orgAgg, userID, domain.RoleOrgOwner),
c.AddInstanceMemberCommand(instanceAgg, userID, domain.RoleIAMOwner),
AddProjectCommand(projectAgg, zitadelProjectName, userID, false, false, false, domain.PrivateLabelingSettingUnspecified),
SetIAMProject(instanceAgg, projectAgg.ID),
c.AddAPIAppCommand(
&addAPIApp{
AddApp: AddApp{
Aggregate: *projectAgg,
ID: setup.zitadel.mgmtAppID,
Name: mgmtAppName,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
nil,
),
c.AddAPIAppCommand(
&addAPIApp{
AddApp: AddApp{
Aggregate: *projectAgg,
ID: setup.zitadel.adminAppID,
Name: adminAppName,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
nil,
),
c.AddAPIAppCommand(
&addAPIApp{
AddApp: AddApp{
Aggregate: *projectAgg,
ID: setup.zitadel.authAppID,
Name: authAppName,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
nil,
),
c.AddOIDCAppCommand(console, nil),
SetIAMConsoleID(instanceAgg, &console.ClientID, &setup.zitadel.consoleAppID),
)
addGeneratedDomain, err := c.addGeneratedInstanceDomain(ctx, instanceAgg, setup.InstanceName)
pat, machineKey, err := setupAdmin(c, &validations, setup.Org.Machine, setup.Org.Human, orgID, userID, userAgg)
if err != nil {
return "", "", nil, nil, err
}
validations = append(validations, addGeneratedDomain...)
if setup.CustomDomain != "" {
validations = append(validations,
c.addInstanceDomain(instanceAgg, setup.CustomDomain, false),
setPrimaryInstanceDomain(instanceAgg, setup.CustomDomain),
)
setupMinimalInterfaces(c, &validations, instanceAgg, projectAgg, orgAgg, userID, setup.zitadel)
if err := setupGeneratedDomain(ctx, c, &validations, instanceAgg, setup.InstanceName); err != nil {
return "", "", nil, nil, err
}
if setup.SMTPConfiguration != nil {
validations = append(validations,
c.prepareAddSMTPConfig(
instanceAgg,
setup.SMTPConfiguration.From,
setup.SMTPConfiguration.FromName,
setup.SMTPConfiguration.ReplyToAddress,
setup.SMTPConfiguration.SMTP.Host,
setup.SMTPConfiguration.SMTP.User,
[]byte(setup.SMTPConfiguration.SMTP.Password),
setup.SMTPConfiguration.Tls,
),
)
}
if setup.OIDCSettings != nil {
validations = append(validations,
c.prepareAddOIDCSettings(
instanceAgg,
setup.OIDCSettings.AccessTokenLifetime,
setup.OIDCSettings.IdTokenLifetime,
setup.OIDCSettings.RefreshTokenIdleExpiration,
setup.OIDCSettings.RefreshTokenExpiration,
),
)
}
for f, value := range setup.Features {
switch v := value.(type) {
case bool:
wm, err := NewInstanceFeatureWriteModel[feature.Boolean](instanceID, f)
if err != nil {
return "", "", nil, nil, err
}
validations = append(validations, prepareSetFeature(wm, feature.Boolean{Boolean: v}, c.idGenerator))
default:
return "", "", nil, nil, errors.ThrowInvalidArgument(nil, "INST-GE4tg", "Errors.Feature.TypeNotSupported")
}
}
if setup.Limits != nil {
validations = append(validations, c.SetLimitsCommand(limitsAgg, &limitsWriteModel{}, setup.Limits))
}
if setup.Restrictions != nil {
validations = append(validations, c.SetRestrictionsCommand(restrictionsAgg, &restrictionsWriteModel{}, setup.Restrictions))
setupCustomDomain(c, &validations, instanceAgg, setup.CustomDomain)
setupSMTPSettings(c, &validations, setup.SMTPConfiguration, instanceAgg)
setupOIDCSettings(c, &validations, setup.OIDCSettings, instanceAgg)
if err := setupFeatures(c, &validations, setup.Features, instanceID); err != nil {
return "", "", nil, nil, err
}
setupLimits(c, &validations, limitsAgg, setup.Limits)
setupRestrictions(c, &validations, restrictionsAgg, setup.Restrictions)
//nolint:staticcheck
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validations...)
if err != nil {
return "", "", nil, nil, err
@@ -488,6 +342,205 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
}, nil
}
func setupLimits(commands *Commands, validations *[]preparation.Validation, limitsAgg *limits.Aggregate, setLimits *SetLimits) {
if setLimits != nil {
*validations = append(*validations, commands.SetLimitsCommand(limitsAgg, &limitsWriteModel{}, setLimits))
}
}
func setupRestrictions(commands *Commands, validations *[]preparation.Validation, restrictionsAgg *restrictions.Aggregate, setRestrictions *SetRestrictions) {
if setRestrictions != nil {
*validations = append(*validations, commands.SetRestrictionsCommand(restrictionsAgg, &restrictionsWriteModel{}, setRestrictions))
}
}
func setupQuotas(commands *Commands, validations *[]preparation.Validation, setQuotas *SetQuotas, instanceID string) error {
if setQuotas == nil {
return nil
}
for _, q := range setQuotas.Items {
quotaId, err := commands.idGenerator.Next()
if err != nil {
return err
}
*validations = append(*validations, commands.SetQuotaCommand(quota.NewAggregate(quotaId, instanceID), nil, true, q))
}
return nil
}
func setupFeatures(commands *Commands, validations *[]preparation.Validation, enableFeatures map[domain.Feature]any, instanceID string) error {
for f, value := range enableFeatures {
switch v := value.(type) {
case bool:
wm, err := NewInstanceFeatureWriteModel[feature.Boolean](instanceID, f)
if err != nil {
return err
}
*validations = append(*validations, prepareSetFeature(wm, feature.Boolean{Boolean: v}, commands.idGenerator))
default:
return errors.ThrowInvalidArgument(nil, "INST-GE4tg", "Errors.Feature.TypeNotSupported")
}
}
return nil
}
func setupOIDCSettings(commands *Commands, validations *[]preparation.Validation, oidcSettings *OIDCSettings, instanceAgg *instance.Aggregate) {
if oidcSettings == nil {
return
}
*validations = append(*validations,
commands.prepareAddOIDCSettings(
instanceAgg,
oidcSettings.AccessTokenLifetime,
oidcSettings.IdTokenLifetime,
oidcSettings.RefreshTokenIdleExpiration,
oidcSettings.RefreshTokenExpiration,
),
)
}
func setupSMTPSettings(commands *Commands, validations *[]preparation.Validation, smtpConfig *smtp.Config, instanceAgg *instance.Aggregate) {
if smtpConfig == nil {
return
}
*validations = append(*validations,
commands.prepareAddSMTPConfig(
instanceAgg,
smtpConfig.From,
smtpConfig.FromName,
smtpConfig.ReplyToAddress,
smtpConfig.SMTP.Host,
smtpConfig.SMTP.User,
[]byte(smtpConfig.SMTP.Password),
smtpConfig.Tls,
),
)
}
func setupCustomDomain(commands *Commands, validations *[]preparation.Validation, instanceAgg *instance.Aggregate, customDomain string) {
if customDomain == "" {
return
}
*validations = append(*validations,
commands.addInstanceDomain(instanceAgg, customDomain, false),
setPrimaryInstanceDomain(instanceAgg, customDomain),
)
}
func setupGeneratedDomain(ctx context.Context, commands *Commands, validations *[]preparation.Validation, instanceAgg *instance.Aggregate, instanceName string) error {
addGeneratedDomain, err := commands.addGeneratedInstanceDomain(ctx, instanceAgg, instanceName)
if err != nil {
return err
}
*validations = append(*validations, addGeneratedDomain...)
return nil
}
func setupMinimalInterfaces(commands *Commands, validations *[]preparation.Validation, instanceAgg *instance.Aggregate, projectAgg *project.Aggregate, orgAgg *org.Aggregate, userID string, ids ZitadelConfig) {
cnsl := &addOIDCApp{
AddApp: AddApp{
Aggregate: *projectAgg,
ID: ids.consoleAppID,
Name: consoleAppName,
},
Version: domain.OIDCVersionV1,
RedirectUris: []string{},
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
ApplicationType: domain.OIDCApplicationTypeUserAgent,
AuthMethodType: domain.OIDCAuthMethodTypeNone,
PostLogoutRedirectUris: []string{},
DevMode: !commands.externalSecure,
AccessTokenType: domain.OIDCTokenTypeBearer,
AccessTokenRoleAssertion: false,
IDTokenRoleAssertion: false,
IDTokenUserinfoAssertion: false,
ClockSkew: 0,
}
*validations = append(*validations,
commands.AddOrgMemberCommand(orgAgg, userID, domain.RoleOrgOwner),
commands.AddInstanceMemberCommand(instanceAgg, userID, domain.RoleIAMOwner),
AddProjectCommand(projectAgg, zitadelProjectName, userID, false, false, false, domain.PrivateLabelingSettingUnspecified),
SetIAMProject(instanceAgg, projectAgg.ID),
commands.AddAPIAppCommand(
&addAPIApp{
AddApp: AddApp{
Aggregate: *projectAgg,
ID: ids.mgmtAppID,
Name: mgmtAppName,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
nil,
),
commands.AddAPIAppCommand(
&addAPIApp{
AddApp: AddApp{
Aggregate: *projectAgg,
ID: ids.adminAppID,
Name: adminAppName,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
nil,
),
commands.AddAPIAppCommand(
&addAPIApp{
AddApp: AddApp{
Aggregate: *projectAgg,
ID: ids.authAppID,
Name: authAppName,
},
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
},
nil,
),
commands.AddOIDCAppCommand(cnsl, nil),
SetIAMConsoleID(instanceAgg, &cnsl.ClientID, &ids.consoleAppID),
)
}
func setupAdmin(commands *Commands, validations *[]preparation.Validation, machine *AddMachine, human *AddHuman, orgID, userID string, userAgg *user.Aggregate) (pat *PersonalAccessToken, machineKey *MachineKey, err error) {
// only a human or a machine user should be created as owner
if machine != nil && machine.Machine != nil && !machine.Machine.IsZero() {
*validations = append(*validations,
AddMachineCommand(userAgg, machine.Machine),
)
if machine.Pat != nil {
pat = NewPersonalAccessToken(orgID, userID, machine.Pat.ExpirationDate, machine.Pat.Scopes, domain.UserTypeMachine)
pat.TokenID, err = commands.idGenerator.Next()
if err != nil {
return nil, nil, err
}
*validations = append(*validations, prepareAddPersonalAccessToken(pat, commands.keyAlgorithm))
}
if machine.MachineKey != nil {
machineKey = NewMachineKey(orgID, userID, machine.MachineKey.ExpirationDate, machine.MachineKey.Type)
machineKey.KeyID, err = commands.idGenerator.Next()
if err != nil {
return nil, nil, err
}
*validations = append(*validations, prepareAddUserMachineKey(machineKey, commands.machineKeySize))
}
} else if human != nil {
human.ID = userID
*validations = append(*validations,
commands.AddHumanCommand(human, orgID, commands.userPasswordHasher, commands.userEncryption, true),
)
}
return pat, machineKey, nil
}
func setupMessageTexts(validations *[]preparation.Validation, setupMessageTexts []*domain.CustomMessageText, instanceAgg *instance.Aggregate) {
for _, msg := range setupMessageTexts {
*validations = append(*validations, prepareSetInstanceCustomMessageTexts(instanceAgg, msg))
}
}
func (c *Commands) UpdateInstance(ctx context.Context, name string) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
validation := c.prepareUpdateInstance(instanceAgg, name)
@@ -656,16 +709,27 @@ func (c *Commands) prepareUpdateInstance(a *instance.Aggregate, name string) pre
func (c *Commands) prepareSetDefaultLanguage(a *instance.Aggregate, defaultLanguage language.Tag) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if defaultLanguage == language.Und {
return nil, errors.ThrowInvalidArgument(nil, "INST-28nlD", "Errors.Invalid.Argument")
if err := domain.LanguageIsDefined(defaultLanguage); err != nil {
return nil, err
}
if err := domain.LanguagesAreSupported(i18n.SupportedLanguages(), defaultLanguage); err != nil {
return nil, err
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
writeModel, err := getInstanceWriteModel(ctx, filter)
if writeModel.DefaultLanguage == defaultLanguage {
return nil, errors.ThrowPreconditionFailed(nil, "INST-DS3rq", "Errors.Instance.NotChanged")
}
instanceID := authz.GetInstance(ctx).InstanceID()
restrictionsWM, err := c.getRestrictionsWriteModel(ctx, instanceID, instanceID)
if err != nil {
return nil, err
}
if writeModel.DefaultLanguage == defaultLanguage {
return nil, errors.ThrowPreconditionFailed(nil, "INST-DS3rq", "Errors.Instance.NotChanged")
if err := domain.LanguageIsAllowed(false, restrictionsWM.allowedLanguages, defaultLanguage); err != nil {
return nil, err
}
if err != nil {
return nil, err
}
return []eventstore.Command{instance.NewDefaultLanguageSetEvent(ctx, &a.Aggregate, defaultLanguage)}, nil
}, nil

View File

@@ -2,16 +2,18 @@ package command
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/i18n"
"github.com/zitadel/zitadel/internal/repository/instance"
)
// SetCustomInstanceLoginText only validates if the language is supported, not if it is allowed.
// This enables setting texts before allowing a language
func (c *Commands) SetCustomInstanceLoginText(ctx context.Context, loginText *domain.CustomLoginText) (*domain.ObjectDetails, error) {
iamAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
events, existingMailText, err := c.setCustomInstanceLoginText(ctx, &iamAgg.Aggregate, loginText)
@@ -53,8 +55,8 @@ func (c *Commands) RemoveCustomInstanceLoginTexts(ctx context.Context, lang lang
}
func (c *Commands) setCustomInstanceLoginText(ctx context.Context, instanceAgg *eventstore.Aggregate, text *domain.CustomLoginText) ([]eventstore.Command, *InstanceCustomLoginTextReadModel, error) {
if !text.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "Instance-kd9fs", "Errors.CustomText.Invalid")
if err := text.IsValid(i18n.SupportedLanguages()); err != nil {
return nil, nil, err
}
existingLoginText, err := c.defaultLoginTextWriteModelByID(ctx, text.Language)
if err != nil {

View File

@@ -33,20 +33,54 @@ func TestCommandSide_SetCustomIAMLoginText(t *testing.T) {
res res
}{
{
name: "invalid custom login text, error",
name: "empty custom login text, success",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(),
),
},
args: args{
ctx: context.Background(),
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
config: &domain.CustomLoginText{
Language: AllowedLanguage,
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
},
},
{
name: "undefined language, error",
fields: fields{
eventstore: eventstoreExpect(t),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
config: &domain.CustomLoginText{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "unsupported language, error",
fields: fields{
eventstore: eventstoreExpect(t),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
config: &domain.CustomLoginText{
Language: UnsupportedLanguage,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "custom login text set all fields, ok",
fields: fields{

View File

@@ -9,9 +9,12 @@ import (
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/i18n"
"github.com/zitadel/zitadel/internal/repository/instance"
)
// SetDefaultMessageText only validates if the language is supported, not if it is allowed.
// This enables setting texts before allowing a language
func (c *Commands) SetDefaultMessageText(ctx context.Context, instanceID string, messageText *domain.CustomMessageText) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(instanceID)
events, existingMessageText, err := c.setDefaultMessageText(ctx, &instanceAgg.Aggregate, messageText)
@@ -30,8 +33,8 @@ func (c *Commands) SetDefaultMessageText(ctx context.Context, instanceID string,
}
func (c *Commands) setDefaultMessageText(ctx context.Context, instanceAgg *eventstore.Aggregate, msg *domain.CustomMessageText) ([]eventstore.Command, *InstanceCustomMessageTextWriteModel, error) {
if !msg.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "INSTANCE-kd9fs", "Errors.CustomMessageText.Invalid")
if err := msg.IsValid(i18n.SupportedLanguages()); err != nil {
return nil, nil, err
}
existingMessageText, err := c.defaultCustomMessageTextWriteModelByID(ctx, msg.MessageTextType, msg.Language)
@@ -129,8 +132,8 @@ func prepareSetInstanceCustomMessageTexts(
msg *domain.CustomMessageText,
) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if !msg.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "INSTANCE-kd9fs", "Errors.CustomMessageText.Invalid")
if err := msg.IsValid(i18n.SupportedLanguages()); err != nil {
return nil, err
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
existing, err := existingInstanceCustomMessageText(ctx, filter, msg.MessageTextType, msg.Language)

View File

@@ -9,7 +9,7 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
zitadel_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/instance"
)
@@ -34,19 +34,68 @@ func TestCommandSide_SetDefaultMessageText(t *testing.T) {
res res
}{
{
name: "invalid custom text, error",
name: "empty message type, error",
fields: fields{
eventstore: eventstoreExpect(t),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
config: &domain.CustomMessageText{
Language: AllowedLanguage,
},
},
res: res{
err: zitadel_errs.IsErrorInvalidArgument,
},
},
{
name: "empty custom message text, success",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(),
),
},
args: args{
ctx: context.Background(),
instanceID: "INSTANCE",
config: &domain.CustomMessageText{},
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
config: &domain.CustomMessageText{
MessageTextType: "Some type", // TODO: check the type!
Language: AllowedLanguage,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
},
},
{
name: "undefined language, error",
fields: fields{
eventstore: eventstoreExpect(t),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
config: &domain.CustomMessageText{},
},
res: res{
err: zitadel_errs.IsErrorInvalidArgument,
},
},
{
name: "unsupported language, error",
fields: fields{
eventstore: eventstoreExpect(t),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
config: &domain.CustomMessageText{
Language: UnsupportedLanguage,
},
},
res: res{
err: zitadel_errs.IsErrorInvalidArgument,
},
},
{

View File

@@ -212,7 +212,7 @@ func (m *mockInstance) ConsoleApplicationID() string {
}
func (m *mockInstance) DefaultLanguage() language.Tag {
return language.English
return AllowedLanguage
}
func (m *mockInstance) DefaultOrganisationID() string {

View File

@@ -8,9 +8,12 @@ import (
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/i18n"
"github.com/zitadel/zitadel/internal/repository/org"
)
// SetOrgLoginText only validates if the language is supported, not if it is allowed.
// This enables setting texts before allowing a language
func (c *Commands) SetOrgLoginText(ctx context.Context, resourceOwner string, loginText *domain.CustomLoginText) (*domain.ObjectDetails, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-m29rF", "Errors.ResourceOwnerMissing")
@@ -32,10 +35,9 @@ func (c *Commands) SetOrgLoginText(ctx context.Context, resourceOwner string, lo
}
func (c *Commands) setOrgLoginText(ctx context.Context, orgAgg *eventstore.Aggregate, loginText *domain.CustomLoginText) ([]eventstore.Command, *OrgCustomLoginTextReadModel, error) {
if !loginText.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "ORG-PPo2w", "Errors.CustomText.Invalid")
if err := loginText.IsValid(i18n.SupportedLanguages()); err != nil {
return nil, nil, err
}
existingLoginText, err := c.orgCustomLoginTextWriteModelByID(ctx, orgAgg.ID, loginText.Language)
if err != nil {
return nil, nil, err

View File

@@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
@@ -40,22 +41,44 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
),
},
args: args{
ctx: context.Background(),
config: &domain.CustomLoginText{},
ctx: authz.WithInstanceID(context.Background(), "org1"),
config: &domain.CustomLoginText{
Language: AllowedLanguage,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid custom login text, error",
name: "empty custom login text, success",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(),
),
},
args: args{
ctx: context.Background(),
ctx: authz.WithInstanceID(context.Background(), "org1"),
resourceOwner: "org1",
config: &domain.CustomLoginText{
Language: AllowedLanguage,
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "undefined language, error",
fields: fields{
eventstore: eventstoreExpect(t),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "org1"),
resourceOwner: "org1",
config: &domain.CustomLoginText{},
},
@@ -63,6 +86,22 @@ func TestCommandSide_SetCustomOrgLoginText(t *testing.T) {
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "unsupported language, error",
fields: fields{
eventstore: eventstoreExpect(t),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "org1"),
resourceOwner: "org1",
config: &domain.CustomLoginText{
Language: UnsupportedLanguage,
},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "custom login text set all fields, ok",
fields: fields{

View File

@@ -8,9 +8,12 @@ import (
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/i18n"
"github.com/zitadel/zitadel/internal/repository/org"
)
// SetOrgMessageText only validates if the language is supported, not if it is allowed.
// This enables setting texts before allowing a language
func (c *Commands) SetOrgMessageText(ctx context.Context, resourceOwner string, messageText *domain.CustomMessageText) (*domain.ObjectDetails, error) {
if resourceOwner == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-2biiR", "Errors.ResourceOwnerMissing")
@@ -32,10 +35,9 @@ func (c *Commands) SetOrgMessageText(ctx context.Context, resourceOwner string,
}
func (c *Commands) setOrgMessageText(ctx context.Context, orgAgg *eventstore.Aggregate, message *domain.CustomMessageText) ([]eventstore.Command, *OrgCustomMessageTextReadModel, error) {
if !message.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "ORG-2jfsf", "Errors.CustomText.Invalid")
if err := message.IsValid(i18n.SupportedLanguages()); err != nil {
return nil, nil, err
}
existingMessageText, err := c.orgCustomMessageTextWriteModelByID(ctx, orgAgg.ID, message.MessageTextType, message.Language)
if err != nil {
return nil, nil, err

View File

@@ -8,7 +8,7 @@ import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
zitadel_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/org"
)
@@ -35,32 +35,83 @@ func TestCommandSide_SetCustomMessageText(t *testing.T) {
{
name: "no resource owner, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
eventstore: eventstoreExpect(t),
},
args: args{
ctx: context.Background(),
config: &domain.CustomMessageText{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: zitadel_errs.IsErrorInvalidArgument,
},
},
{
name: "invalid custom text, error",
name: "empty message type, error",
fields: fields{
eventstore: eventstoreExpect(t),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
config: &domain.CustomMessageText{
Language: AllowedLanguage,
},
},
res: res{
err: zitadel_errs.IsErrorInvalidArgument,
},
},
{
name: "empty custom message text, success",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
expectPush(),
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
config: &domain.CustomMessageText{
MessageTextType: "Some type", // TODO: check the type!
Language: AllowedLanguage,
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
{
name: "undefined language, error",
fields: fields{
eventstore: eventstoreExpect(t),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
config: &domain.CustomMessageText{},
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: zitadel_errs.IsErrorInvalidArgument,
},
},
{
name: "unsupported language, error",
fields: fields{
eventstore: eventstoreExpect(t),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
config: &domain.CustomMessageText{
Language: UnsupportedLanguage,
},
},
res: res{
err: zitadel_errs.IsErrorInvalidArgument,
},
},
{
@@ -345,7 +396,7 @@ func TestCommandSide_RemoveCustomMessageText(t *testing.T) {
lang: language.English,
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: zitadel_errs.IsErrorInvalidArgument,
},
},
{
@@ -361,7 +412,7 @@ func TestCommandSide_RemoveCustomMessageText(t *testing.T) {
lang: language.English,
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: zitadel_errs.IsErrorInvalidArgument,
},
},
{
@@ -377,7 +428,7 @@ func TestCommandSide_RemoveCustomMessageText(t *testing.T) {
mailTextType: "Template",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
err: zitadel_errs.IsErrorInvalidArgument,
},
},
{
@@ -471,6 +522,43 @@ func TestCommandSide_RemoveCustomMessageText(t *testing.T) {
},
},
},
{
name: "remove unsupported language ok, especially because we never validated whether a language is supported in previous ZITADEL versions",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
org.NewCustomTextSetEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"Template",
domain.MessageGreeting,
"Greeting",
UnsupportedLanguage,
),
),
),
expectPush(
org.NewCustomTextTemplateRemovedEvent(context.Background(),
&org.NewAggregate("org1").Aggregate,
"Template",
UnsupportedLanguage,
),
),
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
mailTextType: "Template",
lang: UnsupportedLanguage,
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "org1",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@@ -141,7 +141,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
"lastname1",
"nickname1",
"displayname1",
language.German,
language.English,
domain.GenderMale,
"email1",
true,
@@ -185,7 +185,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
"lastname1",
"nickname1",
"displayname1",
language.German,
language.English,
domain.GenderMale,
"email1",
true,
@@ -253,7 +253,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
"lastname1",
"nickname1",
"displayname1",
language.German,
language.English,
domain.GenderMale,
"email1",
true,
@@ -321,7 +321,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
"lastname1",
"nickname1",
"displayname1",
language.German,
language.English,
domain.GenderMale,
"email1",
true,
@@ -392,7 +392,7 @@ func TestCommandSide_AddOrg(t *testing.T) {
"lastname1",
"nickname1",
"displayname1",
language.German,
language.English,
domain.GenderMale,
"email1",
true,
@@ -1181,7 +1181,7 @@ func TestCommandSide_RemoveOrg(t *testing.T) {
"lastname1",
"nickname1",
"displayname1",
language.German,
language.English,
domain.GenderMale,
"email1",
false,

View File

@@ -3,16 +3,38 @@ package command
import (
"context"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
zitadel_errors "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/i18n"
"github.com/zitadel/zitadel/internal/repository/restrictions"
)
type SetRestrictions struct {
DisallowPublicOrgRegistration *bool
AllowedLanguages []language.Tag
}
func (s *SetRestrictions) Validate(defaultLanguage language.Tag) error {
if s == nil || (s.DisallowPublicOrgRegistration == nil && s.AllowedLanguages == nil) {
return zitadel_errors.ThrowInvalidArgument(nil, "COMMAND-oASwj", "Errors.Restrictions.NoneSpecified")
}
if s.AllowedLanguages != nil {
if err := domain.LanguagesHaveDuplicates(s.AllowedLanguages); err != nil {
return err
}
if err := domain.LanguagesAreSupported(i18n.SupportedLanguages(), s.AllowedLanguages...); err != nil {
return err
}
if err := domain.LanguageIsAllowed(false, s.AllowedLanguages, defaultLanguage); err != nil {
return zitadel_errors.ThrowPreconditionFailedf(err, "COMMAND-L0m2u", "Errors.Restrictions.DefaultLanguageMustBeAllowed")
}
}
return nil
}
// SetRestrictions creates new restrictions or updates existing restrictions.
@@ -60,10 +82,10 @@ func (c *Commands) getRestrictionsWriteModel(ctx context.Context, instanceId, re
func (c *Commands) SetRestrictionsCommand(a *restrictions.Aggregate, wm *restrictionsWriteModel, setRestrictions *SetRestrictions) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if setRestrictions == nil || setRestrictions.DisallowPublicOrgRegistration == nil {
return nil, errors.ThrowInvalidArgument(nil, "COMMAND-oASwj", "Errors.Restrictions.NoneSpecified")
}
return func(ctx context.Context, _ preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
if err := setRestrictions.Validate(authz.GetInstance(ctx).DefaultLanguage()); err != nil {
return nil, err
}
changes := wm.NewChanges(setRestrictions)
if len(changes) == 0 {
return nil, nil

View File

@@ -1,13 +1,17 @@
package command
import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/restrictions"
)
type restrictionsWriteModel struct {
eventstore.WriteModel
disallowPublicOrgRegistrations bool
disallowPublicOrgRegistration bool
allowedLanguages []language.Tag
}
// newRestrictionsWriteModel aggregateId is filled by reducing unit matching events
@@ -34,8 +38,15 @@ func (wm *restrictionsWriteModel) Query() *eventstore.SearchQueryBuilder {
func (wm *restrictionsWriteModel) Reduce() error {
for _, event := range wm.Events {
wm.ChangeDate = event.CreatedAt()
if e, ok := event.(*restrictions.SetEvent); ok && e.DisallowPublicOrgRegistrations != nil {
wm.disallowPublicOrgRegistrations = *e.DisallowPublicOrgRegistrations
e, ok := event.(*restrictions.SetEvent)
if !ok {
continue
}
if e.DisallowPublicOrgRegistration != nil {
wm.disallowPublicOrgRegistration = *e.DisallowPublicOrgRegistration
}
if e.AllowedLanguages != nil {
wm.allowedLanguages = *e.AllowedLanguages
}
}
return wm.WriteModel.Reduce()
@@ -48,8 +59,11 @@ func (wm *restrictionsWriteModel) NewChanges(setRestrictions *SetRestrictions) (
return nil
}
changes = make([]restrictions.RestrictionsChange, 0, 1)
if setRestrictions.DisallowPublicOrgRegistration != nil && (wm.disallowPublicOrgRegistrations != *setRestrictions.DisallowPublicOrgRegistration) {
changes = append(changes, restrictions.ChangePublicOrgRegistrations(*setRestrictions.DisallowPublicOrgRegistration))
if setRestrictions.DisallowPublicOrgRegistration != nil && (wm.disallowPublicOrgRegistration != *setRestrictions.DisallowPublicOrgRegistration) {
changes = append(changes, restrictions.ChangeDisallowPublicOrgRegistration(*setRestrictions.DisallowPublicOrgRegistration))
}
if setRestrictions.AllowedLanguages != nil && domain.LanguagesDiffer(wm.allowedLanguages, setRestrictions.AllowedLanguages) {
changes = append(changes, restrictions.ChangeAllowedLanguages(setRestrictions.AllowedLanguages))
}
return changes
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/domain"
@@ -19,7 +20,6 @@ import (
func TestSetRestrictions(t *testing.T) {
type fields func(*testing.T) (*eventstore.Eventstore, id.Generator)
type args struct {
ctx context.Context
setRestrictions *SetRestrictions
}
type res struct {
@@ -40,14 +40,14 @@ func TestSetRestrictions(t *testing.T) {
expectFilter(),
expectPush(
eventFromEventPusherWithInstanceID(
"instance1",
"INSTANCE",
restrictions.NewSetEvent(
eventstore.NewBaseEventForPush(
context.Background(),
&restrictions.NewAggregate("restrictions1", "instance1", "instance1").Aggregate,
&restrictions.NewAggregate("restrictions1", "INSTANCE", "INSTANCE").Aggregate,
restrictions.SetEventType,
),
restrictions.ChangePublicOrgRegistrations(true),
restrictions.ChangeDisallowPublicOrgRegistration(true),
),
),
),
@@ -55,14 +55,13 @@ func TestSetRestrictions(t *testing.T) {
id_mock.NewIDGeneratorExpectIDs(t, "restrictions1")
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
setRestrictions: &SetRestrictions{
DisallowPublicOrgRegistration: gu.Ptr(true),
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "instance1",
ResourceOwner: "INSTANCE",
},
},
},
@@ -76,23 +75,23 @@ func TestSetRestrictions(t *testing.T) {
restrictions.NewSetEvent(
eventstore.NewBaseEventForPush(
context.Background(),
&restrictions.NewAggregate("restrictions1", "instance1", "instance1").Aggregate,
&restrictions.NewAggregate("restrictions1", "INSTANCE", "INSTANCE").Aggregate,
restrictions.SetEventType,
),
restrictions.ChangePublicOrgRegistrations(true),
restrictions.ChangeDisallowPublicOrgRegistration(true),
),
),
),
expectPush(
eventFromEventPusherWithInstanceID(
"instance1",
"INSTANCE",
restrictions.NewSetEvent(
eventstore.NewBaseEventForPush(
context.Background(),
&restrictions.NewAggregate("restrictions1", "instance1", "instance1").Aggregate,
&restrictions.NewAggregate("restrictions1", "INSTANCE", "INSTANCE").Aggregate,
restrictions.SetEventType,
),
restrictions.ChangePublicOrgRegistrations(false),
restrictions.ChangeDisallowPublicOrgRegistration(false),
),
),
),
@@ -100,14 +99,13 @@ func TestSetRestrictions(t *testing.T) {
nil
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
setRestrictions: &SetRestrictions{
DisallowPublicOrgRegistration: gu.Ptr(false),
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "instance1",
ResourceOwner: "INSTANCE",
},
},
},
@@ -121,10 +119,10 @@ func TestSetRestrictions(t *testing.T) {
restrictions.NewSetEvent(
eventstore.NewBaseEventForPush(
context.Background(),
&restrictions.NewAggregate("restrictions1", "instance1", "instance1").Aggregate,
&restrictions.NewAggregate("restrictions1", "INSTANCE", "INSTANCE").Aggregate,
restrictions.SetEventType,
),
restrictions.ChangePublicOrgRegistrations(true),
restrictions.ChangeDisallowPublicOrgRegistration(true),
),
),
),
@@ -132,14 +130,13 @@ func TestSetRestrictions(t *testing.T) {
nil
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
setRestrictions: &SetRestrictions{
DisallowPublicOrgRegistration: gu.Ptr(true),
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "instance1",
ResourceOwner: "INSTANCE",
},
},
},
@@ -152,29 +149,82 @@ func TestSetRestrictions(t *testing.T) {
restrictions.NewSetEvent(
eventstore.NewBaseEventForPush(
context.Background(),
&restrictions.NewAggregate("restrictions1", "instance1", "instance1").Aggregate,
&restrictions.NewAggregate("restrictions1", "INSTANCE", "INSTANCE").Aggregate,
restrictions.SetEventType,
),
restrictions.ChangePublicOrgRegistrations(true),
restrictions.ChangeDisallowPublicOrgRegistration(true),
),
),
),
), nil
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
setRestrictions: &SetRestrictions{},
},
res: res{
err: zitadel_errs.IsErrorInvalidArgument,
},
},
{
name: "unsupported language restricted",
fields: func(*testing.T) (*eventstore.Eventstore, id.Generator) {
return eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
restrictions.NewSetEvent(
eventstore.NewBaseEventForPush(
context.Background(),
&restrictions.NewAggregate("restrictions1", "INSTANCE", "INSTANCE").Aggregate,
restrictions.SetEventType,
),
restrictions.ChangeAllowedLanguages(SupportedLanguages),
),
),
),
), nil
},
args: args{
setRestrictions: &SetRestrictions{
AllowedLanguages: []language.Tag{AllowedLanguage, UnsupportedLanguage},
},
},
res: res{
err: zitadel_errs.IsErrorInvalidArgument,
},
},
{
name: "default language not allowed",
fields: func(*testing.T) (*eventstore.Eventstore, id.Generator) {
return eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
restrictions.NewSetEvent(
eventstore.NewBaseEventForPush(
context.Background(),
&restrictions.NewAggregate("restrictions1", "INSTANCE", "INSTANCE").Aggregate,
restrictions.SetEventType,
),
restrictions.ChangeAllowedLanguages(OnlyAllowedLanguages),
),
),
),
), nil
},
args: args{
setRestrictions: &SetRestrictions{
AllowedLanguages: []language.Tag{DisallowedLanguage},
},
},
res: res{
err: zitadel_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := new(Commands)
r.eventstore, r.idGenerator = tt.fields(t)
got, err := r.SetInstanceRestrictions(tt.args.ctx, tt.args.setRestrictions)
got, err := r.SetInstanceRestrictions(authz.WithInstance(context.Background(), &mockInstance{}), tt.args.setRestrictions)
if tt.res.err == nil {
assert.NoError(t, err)
}

View File

@@ -8,7 +8,7 @@ import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"
zitadel_errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/repository/user"
@@ -36,8 +36,7 @@ func TestCommandSide_ChangeHumanProfile(t *testing.T) {
{
name: "user not existing, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
eventstore: eventstoreExpect(t,
expectFilter(),
),
},
@@ -51,13 +50,13 @@ func TestCommandSide_ChangeHumanProfile(t *testing.T) {
LastName: "lastname",
NickName: "nickname",
DisplayName: "displayname",
PreferredLanguage: language.German,
PreferredLanguage: AllowedLanguage,
Gender: domain.GenderFemale,
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: zitadel_errs.IsPreconditionFailed,
},
},
{
@@ -74,7 +73,7 @@ func TestCommandSide_ChangeHumanProfile(t *testing.T) {
"lastname",
"nickname",
"displayname",
language.German,
AllowedLanguage,
domain.GenderFemale,
"email",
true,
@@ -93,13 +92,13 @@ func TestCommandSide_ChangeHumanProfile(t *testing.T) {
LastName: "lastname",
NickName: "nickname",
DisplayName: "displayname",
PreferredLanguage: language.German,
PreferredLanguage: AllowedLanguage,
Gender: domain.GenderFemale,
},
resourceOwner: "org1",
},
res: res{
err: caos_errs.IsPreconditionFailed,
err: zitadel_errs.IsPreconditionFailed,
},
},
{
@@ -116,7 +115,7 @@ func TestCommandSide_ChangeHumanProfile(t *testing.T) {
"lastname",
"nickname",
"displayname",
language.German,
DisallowedLanguage,
domain.GenderUnspecified,
"email",
true,
@@ -130,7 +129,7 @@ func TestCommandSide_ChangeHumanProfile(t *testing.T) {
"lastname2",
"nickname2",
"displayname2",
language.English,
AllowedLanguage,
domain.GenderMale,
),
),
@@ -146,7 +145,7 @@ func TestCommandSide_ChangeHumanProfile(t *testing.T) {
LastName: "lastname2",
NickName: "nickname2",
DisplayName: "displayname2",
PreferredLanguage: language.English,
PreferredLanguage: AllowedLanguage,
Gender: domain.GenderMale,
},
resourceOwner: "org1",
@@ -161,7 +160,133 @@ func TestCommandSide_ChangeHumanProfile(t *testing.T) {
LastName: "lastname2",
NickName: "nickname2",
DisplayName: "displayname2",
PreferredLanguage: language.English,
PreferredLanguage: AllowedLanguage,
Gender: domain.GenderMale,
},
},
},
{
name: "undefined preferred language, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
DisallowedLanguage,
domain.GenderUnspecified,
"email",
true,
),
),
),
expectPush(
newProfileChangedEvent(context.Background(),
"user1", "org1",
"firstname2",
"lastname2",
"nickname2",
"displayname2",
language.Und,
domain.GenderMale,
),
),
),
},
args: args{
ctx: context.Background(),
address: &domain.Profile{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
FirstName: "firstname2",
LastName: "lastname2",
NickName: "nickname2",
DisplayName: "displayname2",
Gender: domain.GenderMale,
},
resourceOwner: "org1",
},
res: res{
want: &domain.Profile{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
FirstName: "firstname2",
LastName: "lastname2",
NickName: "nickname2",
DisplayName: "displayname2",
PreferredLanguage: language.Und,
Gender: domain.GenderMale,
},
},
}, {
name: "unsupported preferred language, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
"username",
"firstname",
"lastname",
"nickname",
"displayname",
DisallowedLanguage,
domain.GenderUnspecified,
"email",
true,
),
),
),
expectPush(
newProfileChangedEvent(context.Background(),
"user1", "org1",
"firstname2",
"lastname2",
"nickname2",
"displayname2",
UnsupportedLanguage,
domain.GenderMale,
),
),
),
},
args: args{
ctx: context.Background(),
address: &domain.Profile{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
},
FirstName: "firstname2",
LastName: "lastname2",
NickName: "nickname2",
DisplayName: "displayname2",
PreferredLanguage: UnsupportedLanguage,
Gender: domain.GenderMale,
},
resourceOwner: "org1",
},
res: res{
want: &domain.Profile{
ObjectRoot: models.ObjectRoot{
AggregateID: "user1",
ResourceOwner: "org1",
},
FirstName: "firstname2",
LastName: "lastname2",
NickName: "nickname2",
DisplayName: "displayname2",
PreferredLanguage: UnsupportedLanguage,
Gender: domain.GenderMale,
},
},

File diff suppressed because it is too large Load Diff

View File

@@ -5,11 +5,11 @@ import (
"testing"
"time"
"golang.org/x/text/language"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
caos_errs "github.com/zitadel/zitadel/internal/errors"