mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-05 13:52:15 +00:00
feat(cli): setup (#3267)
* commander * commander * selber! * move to packages * fix(errors): implement Is interface * test: command * test: commands * add init steps * setup tenant * add default step yaml * possibility to set password * merge v2 into v2-commander * fix: rename iam command side to instance * fix: rename iam command side to instance * fix: rename iam command side to instance * fix: rename iam command side to instance * fix: search query builder can filter events in memory * fix: filters for add member * fix(setup): add `ExternalSecure` to config * chore: name iam to instance * fix: matching * remove unsued func * base url * base url * test(command): filter funcs * test: commands * fix: rename orgiampolicy to domain policy * start from init * commands * config * fix indexes and add constraints * fixes * fix: merge conflicts * fix: protos * fix: md files * setup * add deprecated org iam policy again * typo * fix search query * fix filter * Apply suggestions from code review * remove custom org from org setup * add todos for verification * change apps creation * simplify package structure * fix error * move preparation helper for tests * fix unique constraints * fix config mapping in setup * fix error handling in encryption_keys.go * fix projection config * fix query from old views to projection * fix setup of mgmt api * set iam project and fix instance projection * imports Co-authored-by: Livio Amstutz <[email protected]> Co-authored-by: fabi <[email protected]>
This commit is contained in:
co-authored by
Livio Amstutz
fabi
parent
9d4f296c62
commit
c5b99274d7
@@ -0,0 +1,36 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/action"
|
||||
iam_repo "github.com/caos/zitadel/internal/repository/instance"
|
||||
"github.com/caos/zitadel/internal/repository/keypair"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
proj_repo "github.com/caos/zitadel/internal/repository/project"
|
||||
usr_repo "github.com/caos/zitadel/internal/repository/user"
|
||||
usr_grant_repo "github.com/caos/zitadel/internal/repository/usergrant"
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
es *eventstore.Eventstore
|
||||
userPasswordAlg crypto.HashAlgorithm
|
||||
iamDomain string
|
||||
}
|
||||
|
||||
func New(es *eventstore.Eventstore, iamDomain string, defaults sd.SystemDefaults) *Command {
|
||||
iam_repo.RegisterEventMappers(es)
|
||||
org.RegisterEventMappers(es)
|
||||
usr_repo.RegisterEventMappers(es)
|
||||
usr_grant_repo.RegisterEventMappers(es)
|
||||
proj_repo.RegisterEventMappers(es)
|
||||
keypair.RegisterEventMappers(es)
|
||||
action.RegisterEventMappers(es)
|
||||
|
||||
return &Command{
|
||||
es: es,
|
||||
iamDomain: iamDomain,
|
||||
userPasswordAlg: crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/api/ui/console"
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
"github.com/caos/zitadel/internal/repository/project"
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
const (
|
||||
zitadelProjectName = "ZITADEL"
|
||||
mgmtAppName = "Management-API"
|
||||
adminAppName = "Admin-API"
|
||||
authAppName = "Auth-API"
|
||||
consoleAppName = "Console"
|
||||
consoleRedirectPath = console.HandlerPrefix + "/auth/callback"
|
||||
consolePostLogoutPath = console.HandlerPrefix + "/signedout"
|
||||
)
|
||||
|
||||
type InstanceSetup struct {
|
||||
Org OrgSetup
|
||||
Zitadel ZitadelConfig
|
||||
PasswordComplexityPolicy struct {
|
||||
MinLength uint64
|
||||
HasLowercase bool
|
||||
HasUppercase bool
|
||||
HasNumber bool
|
||||
HasSymbol bool
|
||||
}
|
||||
PasswordAgePolicy struct {
|
||||
ExpireWarnDays uint64
|
||||
MaxAgeDays uint64
|
||||
}
|
||||
DomainPolicy struct {
|
||||
UserLoginMustBeDomain bool
|
||||
}
|
||||
LoginPolicy struct {
|
||||
AllowUsernamePassword bool
|
||||
AllowRegister bool
|
||||
AllowExternalIDP bool
|
||||
ForceMFA bool
|
||||
HidePasswordReset bool
|
||||
PasswordlessType domain.PasswordlessType
|
||||
PasswordCheckLifetime time.Duration
|
||||
ExternalLoginCheckLifetime time.Duration
|
||||
MfaInitSkipLifetime time.Duration
|
||||
SecondFactorCheckLifetime time.Duration
|
||||
MultiFactorCheckLifetime time.Duration
|
||||
}
|
||||
PrivacyPolicy struct {
|
||||
TOSLink string
|
||||
PrivacyLink string
|
||||
HelpLink string
|
||||
}
|
||||
LockoutPolicy struct {
|
||||
MaxAttempts uint64
|
||||
ShouldShowLockoutFailure bool
|
||||
}
|
||||
EmailTemplate []byte
|
||||
MessageTexts []*domain.CustomMessageText
|
||||
}
|
||||
|
||||
type ZitadelConfig struct {
|
||||
IsDevMode bool
|
||||
BaseURL string
|
||||
|
||||
projectID string
|
||||
mgmtID string
|
||||
mgmtClientID string
|
||||
adminID string
|
||||
adminClientID string
|
||||
authID string
|
||||
authClientID string
|
||||
consoleID string
|
||||
consoleClientID string
|
||||
}
|
||||
|
||||
func (s *InstanceSetup) generateIDs() (err error) {
|
||||
s.Zitadel.projectID, err = id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Zitadel.mgmtID, err = id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Zitadel.mgmtClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Zitadel.adminID, err = id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Zitadel.adminClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Zitadel.authID, err = id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Zitadel.authClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Zitadel.consoleID, err = id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Zitadel.consoleClientID, err = domain.NewClientID(id.SonyFlakeGenerator, zitadelProjectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (command *Command) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*domain.ObjectDetails, error) {
|
||||
// TODO
|
||||
// instanceID, err := id.SonyFlakeGenerator.Next()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
ctx = authz.SetCtxData(authz.WithInstance(ctx, authz.Instance{ID: "system"}), authz.CtxData{OrgID: domain.IAMID, ResourceOwner: domain.IAMID})
|
||||
|
||||
orgID, err := id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userID, err := id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = setup.generateIDs(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setup.Org.Human.PasswordChangeRequired = true
|
||||
|
||||
instanceAgg := instance.NewAggregate()
|
||||
orgAgg := org.NewAggregate(orgID, orgID)
|
||||
userAgg := user.NewAggregate(userID, orgID)
|
||||
projectAgg := project.NewAggregate(setup.Zitadel.projectID, orgID)
|
||||
|
||||
validations := []preparation.Validation{
|
||||
AddPasswordComplexityPolicy(
|
||||
instanceAgg,
|
||||
setup.PasswordComplexityPolicy.MinLength,
|
||||
setup.PasswordComplexityPolicy.HasLowercase,
|
||||
setup.PasswordComplexityPolicy.HasUppercase,
|
||||
setup.PasswordComplexityPolicy.HasNumber,
|
||||
setup.PasswordComplexityPolicy.HasSymbol,
|
||||
),
|
||||
AddPasswordAgePolicy(
|
||||
instanceAgg,
|
||||
setup.PasswordAgePolicy.ExpireWarnDays,
|
||||
setup.PasswordAgePolicy.MaxAgeDays,
|
||||
),
|
||||
AddDefaultDomainPolicy(
|
||||
instanceAgg,
|
||||
setup.DomainPolicy.UserLoginMustBeDomain,
|
||||
),
|
||||
AddDefaultLoginPolicy(
|
||||
instanceAgg,
|
||||
setup.LoginPolicy.AllowUsernamePassword,
|
||||
setup.LoginPolicy.AllowRegister,
|
||||
setup.LoginPolicy.AllowExternalIDP,
|
||||
setup.LoginPolicy.ForceMFA,
|
||||
setup.LoginPolicy.HidePasswordReset,
|
||||
setup.LoginPolicy.PasswordlessType,
|
||||
setup.LoginPolicy.PasswordCheckLifetime,
|
||||
setup.LoginPolicy.ExternalLoginCheckLifetime,
|
||||
setup.LoginPolicy.MfaInitSkipLifetime,
|
||||
setup.LoginPolicy.SecondFactorCheckLifetime,
|
||||
setup.LoginPolicy.MultiFactorCheckLifetime,
|
||||
),
|
||||
AddSecondFactorToDefaultLoginPolicy(instanceAgg, domain.SecondFactorTypeOTP),
|
||||
AddSecondFactorToDefaultLoginPolicy(instanceAgg, domain.SecondFactorTypeU2F),
|
||||
AddMultiFactorToDefaultLoginPolicy(instanceAgg, domain.MultiFactorTypeU2FWithPIN),
|
||||
|
||||
AddPrivacyPolicy(instanceAgg, setup.PrivacyPolicy.TOSLink, setup.PrivacyPolicy.PrivacyLink, setup.PrivacyPolicy.HelpLink),
|
||||
AddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure),
|
||||
|
||||
AddEmailTemplate(instanceAgg, setup.EmailTemplate),
|
||||
}
|
||||
|
||||
for _, msg := range setup.MessageTexts {
|
||||
validations = append(validations, SetInstanceCustomTexts(instanceAgg, msg))
|
||||
}
|
||||
|
||||
validations = append(validations,
|
||||
AddOrg(orgAgg, setup.Org.Name, command.iamDomain),
|
||||
AddHumanCommand(userAgg, &setup.Org.Human, command.userPasswordAlg),
|
||||
AddOrgMember(orgAgg, userID, domain.RoleOrgOwner),
|
||||
|
||||
AddProject(projectAgg, zitadelProjectName, userID, false, false, false, domain.PrivateLabelingSettingUnspecified),
|
||||
|
||||
SetIAMProject(instanceAgg, projectAgg.ID),
|
||||
|
||||
AddAPIApp(
|
||||
*projectAgg,
|
||||
setup.Zitadel.mgmtID,
|
||||
mgmtAppName,
|
||||
setup.Zitadel.mgmtClientID,
|
||||
nil,
|
||||
domain.APIAuthMethodTypePrivateKeyJWT,
|
||||
),
|
||||
|
||||
AddAPIApp(
|
||||
*projectAgg,
|
||||
setup.Zitadel.adminID,
|
||||
adminAppName,
|
||||
setup.Zitadel.adminClientID,
|
||||
nil,
|
||||
domain.APIAuthMethodTypePrivateKeyJWT,
|
||||
),
|
||||
|
||||
AddAPIApp(
|
||||
*projectAgg,
|
||||
setup.Zitadel.authID,
|
||||
authAppName,
|
||||
setup.Zitadel.authClientID,
|
||||
nil,
|
||||
domain.APIAuthMethodTypePrivateKeyJWT,
|
||||
),
|
||||
|
||||
AddOIDCApp(
|
||||
*projectAgg,
|
||||
domain.OIDCVersionV1,
|
||||
setup.Zitadel.consoleID,
|
||||
consoleAppName,
|
||||
setup.Zitadel.consoleClientID,
|
||||
nil,
|
||||
[]string{setup.Zitadel.BaseURL + consoleRedirectPath},
|
||||
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||
domain.OIDCApplicationTypeUserAgent,
|
||||
domain.OIDCAuthMethodTypeNone,
|
||||
[]string{setup.Zitadel.BaseURL + consolePostLogoutPath},
|
||||
setup.Zitadel.IsDevMode,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
nil,
|
||||
),
|
||||
)
|
||||
|
||||
cmds, err := preparation.PrepareCommands(ctx, command.es.Filter, validations...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := command.es.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: orgID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//SetIAMProject defines the commands to set the id of the IAM project onto the instance
|
||||
func SetIAMProject(a *instance.Aggregate, projectID string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
return []eventstore.Command{
|
||||
instance.NewIAMProjectSetEvent(ctx, &a.Aggregate, projectID),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
func AddDefaultDomainPolicy(
|
||||
a *instance.Aggregate,
|
||||
userLoginMustBeDomain bool,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
//TODO: check if already exists
|
||||
return []eventstore.Command{
|
||||
instance.NewDomainPolicyAddedEvent(ctx, &a.Aggregate,
|
||||
userLoginMustBeDomain,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
func AddEmailTemplate(
|
||||
a *instance.Aggregate,
|
||||
tempalte []byte,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
//TODO: check if already exists
|
||||
return []eventstore.Command{
|
||||
instance.NewMailTemplateAddedEvent(ctx, &a.Aggregate,
|
||||
tempalte,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func SetInstanceCustomTexts(
|
||||
a *instance.Aggregate,
|
||||
msg *domain.CustomMessageText,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
existing, err := existingInstanceCustomMessageText(ctx, filter, msg.MessageTextType, msg.Language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmds := make([]eventstore.Command, 0, 7)
|
||||
if existing.Greeting != msg.Greeting {
|
||||
if msg.Greeting != "" {
|
||||
cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageGreeting, msg.Greeting, msg.Language))
|
||||
} else {
|
||||
cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageGreeting, msg.Language))
|
||||
}
|
||||
}
|
||||
if existing.Subject != msg.Subject {
|
||||
if msg.Subject != "" {
|
||||
cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageSubject, msg.Subject, msg.Language))
|
||||
} else {
|
||||
cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageSubject, msg.Language))
|
||||
}
|
||||
}
|
||||
if existing.Title != msg.Title {
|
||||
if msg.Title != "" {
|
||||
cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageTitle, msg.Title, msg.Language))
|
||||
} else {
|
||||
cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageTitle, msg.Language))
|
||||
}
|
||||
}
|
||||
if existing.PreHeader != msg.PreHeader {
|
||||
if msg.PreHeader != "" {
|
||||
cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessagePreHeader, msg.PreHeader, msg.Language))
|
||||
} else {
|
||||
cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessagePreHeader, msg.Language))
|
||||
}
|
||||
}
|
||||
if existing.Text != msg.Text {
|
||||
if msg.Text != "" {
|
||||
cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageText, msg.Text, msg.Language))
|
||||
} else {
|
||||
cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageText, msg.Language))
|
||||
}
|
||||
}
|
||||
if existing.ButtonText != msg.ButtonText {
|
||||
if msg.ButtonText != "" {
|
||||
cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageButtonText, msg.ButtonText, msg.Language))
|
||||
} else {
|
||||
cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageButtonText, msg.Language))
|
||||
}
|
||||
}
|
||||
if existing.FooterText != msg.FooterText {
|
||||
if msg.FooterText != "" {
|
||||
cmds = append(cmds, instance.NewCustomTextSetEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageFooterText, msg.FooterText, msg.Language))
|
||||
} else {
|
||||
cmds = append(cmds, instance.NewCustomTextRemovedEvent(ctx, &a.Aggregate, msg.MessageTextType, domain.MessageFooterText, msg.Language))
|
||||
}
|
||||
}
|
||||
// TODO: what if no text changed? len(events) == 0
|
||||
return cmds, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func existingInstanceCustomMessageText(ctx context.Context, filter preparation.FilterToQueryReducer, textType string, lang language.Tag) (*command.InstanceCustomMessageTextWriteModel, error) {
|
||||
writeModel := command.NewInstanceCustomMessageTextWriteModel(textType, lang)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
writeModel.Reduce()
|
||||
return writeModel, nil
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
func AddDefaultLabelPolicy(
|
||||
a *instance.Aggregate,
|
||||
primaryColor,
|
||||
backgroundColor,
|
||||
warnColor,
|
||||
fontColor,
|
||||
primaryColorDark,
|
||||
backgroundColorDark,
|
||||
warnColorDark,
|
||||
fontColorDark string,
|
||||
hideLoginNameSuffix,
|
||||
errorMsgPopup,
|
||||
disableWatermark bool,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
//TODO: check if already exists
|
||||
return []eventstore.Command{
|
||||
instance.NewLabelPolicyAddedEvent(ctx, &a.Aggregate,
|
||||
primaryColor,
|
||||
backgroundColor,
|
||||
warnColor,
|
||||
fontColor,
|
||||
primaryColorDark,
|
||||
backgroundColorDark,
|
||||
warnColorDark,
|
||||
fontColorDark,
|
||||
hideLoginNameSuffix,
|
||||
errorMsgPopup,
|
||||
disableWatermark,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
func AddDefaultLockoutPolicy(
|
||||
a *instance.Aggregate,
|
||||
maxAttempts uint64,
|
||||
showLockoutFailure bool,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
//TODO: check if already exists
|
||||
return []eventstore.Command{
|
||||
instance.NewLockoutPolicyAddedEvent(ctx, &a.Aggregate, maxAttempts, showLockoutFailure),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
func AddDefaultLoginPolicy(
|
||||
a *instance.Aggregate,
|
||||
allowUsernamePassword bool,
|
||||
allowRegister bool,
|
||||
allowExternalIDP bool,
|
||||
forceMFA bool,
|
||||
hidePasswordReset bool,
|
||||
passwordlessType domain.PasswordlessType,
|
||||
passwordCheckLifetime time.Duration,
|
||||
externalLoginCheckLifetime time.Duration,
|
||||
mfaInitSkipLifetime time.Duration,
|
||||
secondFactorCheckLifetime time.Duration,
|
||||
multiFactorCheckLifetime time.Duration,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
//TODO: check if already exists
|
||||
return []eventstore.Command{
|
||||
instance.NewLoginPolicyAddedEvent(ctx, &a.Aggregate,
|
||||
allowUsernamePassword,
|
||||
allowRegister,
|
||||
allowExternalIDP,
|
||||
forceMFA,
|
||||
hidePasswordReset,
|
||||
passwordlessType,
|
||||
passwordCheckLifetime,
|
||||
externalLoginCheckLifetime,
|
||||
mfaInitSkipLifetime,
|
||||
secondFactorCheckLifetime,
|
||||
multiFactorCheckLifetime,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddSecondFactorToDefaultLoginPolicy(a *instance.Aggregate, factor domain.SecondFactorType) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
//TODO: check if already exists
|
||||
return []eventstore.Command{
|
||||
instance.NewLoginPolicySecondFactorAddedEvent(ctx, &a.Aggregate, factor),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddMultiFactorToDefaultLoginPolicy(a *instance.Aggregate, factor domain.MultiFactorType) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
//TODO: check if already exists
|
||||
return []eventstore.Command{
|
||||
instance.NewLoginPolicyMultiFactorAddedEvent(ctx, &a.Aggregate, factor),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
func AddPasswordAgePolicy(
|
||||
a *instance.Aggregate,
|
||||
expireWarnDays,
|
||||
maxAgeDays uint64,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
//TODO: check if already exists
|
||||
return []eventstore.Command{
|
||||
instance.NewPasswordAgePolicyAddedEvent(ctx, &a.Aggregate,
|
||||
expireWarnDays,
|
||||
maxAgeDays,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
func AddPasswordComplexityPolicy(
|
||||
a *instance.Aggregate,
|
||||
minLength uint64,
|
||||
hasLowercase,
|
||||
hasUppercase,
|
||||
hasNumber,
|
||||
hasSymbol bool,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
//TODO: check if already exists
|
||||
return []eventstore.Command{
|
||||
instance.NewPasswordComplexityPolicyAddedEvent(ctx, &a.Aggregate,
|
||||
minLength,
|
||||
hasLowercase,
|
||||
hasUppercase,
|
||||
hasNumber,
|
||||
hasSymbol,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
func AddPrivacyPolicy(
|
||||
a *instance.Aggregate,
|
||||
tosLink,
|
||||
privacyLink,
|
||||
helpLink string,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
//TODO: check if already exists
|
||||
return []eventstore.Command{
|
||||
instance.NewPrivacyPolicyAddedEvent(ctx, &a.Aggregate, tosLink, privacyLink, helpLink),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
user_repo "github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
type OrgSetup struct {
|
||||
Name string
|
||||
Human AddHuman
|
||||
}
|
||||
|
||||
func (command *Command) SetUpOrg(ctx context.Context, o *OrgSetup) (*domain.ObjectDetails, error) {
|
||||
orgID, err := id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userID, err := id.SonyFlakeGenerator.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgAgg := org.NewAggregate(orgID, orgID)
|
||||
userAgg := user_repo.NewAggregate(userID, orgID)
|
||||
|
||||
cmds, err := preparation.PrepareCommands(ctx, command.es.Filter,
|
||||
AddOrg(orgAgg, o.Name, command.iamDomain),
|
||||
AddHumanCommand(userAgg, &o.Human, command.userPasswordAlg),
|
||||
AddOrgMember(orgAgg, userID, domain.RoleOrgOwner),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events, err := command.es.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &domain.ObjectDetails{
|
||||
Sequence: events[len(events)-1].Sequence(),
|
||||
EventDate: events[len(events)-1].CreationDate(),
|
||||
ResourceOwner: orgID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//AddOrg defines the commands to create a new org,
|
||||
// this includes the verified default domain
|
||||
func AddOrg(a *org.Aggregate, name, iamDomain string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if name = strings.TrimSpace(name); name == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-mruNY", "Errors.Invalid.Argument")
|
||||
}
|
||||
defaultDomain := domain.NewIAMDomainName(name, iamDomain)
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
return []eventstore.Command{
|
||||
org.NewOrgAddedEvent(ctx, &a.Aggregate, name),
|
||||
org.NewDomainAddedEvent(ctx, &a.Aggregate, defaultDomain),
|
||||
org.NewDomainVerifiedEvent(ctx, &a.Aggregate, defaultDomain),
|
||||
org.NewDomainPrimarySetEvent(ctx, &a.Aggregate, defaultDomain),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func AddOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if domain = strings.TrimSpace(domain); domain == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-r3h4J", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
return []eventstore.Command{org.NewDomainAddedEvent(ctx, &a.Aggregate, domain)}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func VerifyOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if domain = strings.TrimSpace(domain); domain == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-yqlVQ", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
//TODO: check if already exists
|
||||
return []eventstore.Command{org.NewDomainVerifiedEvent(ctx, &a.Aggregate, domain)}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func SetPrimaryOrgDomain(a *org.Aggregate, domain string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if domain = strings.TrimSpace(domain); domain == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-gmNqY", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
//TODO: check if already exists and verified
|
||||
return []eventstore.Command{org.NewDomainPrimarySetEvent(ctx, &a.Aggregate, domain)}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func TestAddDomain(t *testing.T) {
|
||||
type args struct {
|
||||
a *org.Aggregate
|
||||
domain string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid domain",
|
||||
args: args{
|
||||
a: org.NewAggregate("test", "test"),
|
||||
domain: "",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-r3h4J", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: org.NewAggregate("test", "test"),
|
||||
domain: "domain",
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
org.NewDomainAddedEvent(context.Background(), &org.NewAggregate("test", "test").Aggregate, "domain"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, AddOrgDomain(tt.args.a, tt.args.domain), nil, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyDomain(t *testing.T) {
|
||||
type args struct {
|
||||
a *org.Aggregate
|
||||
domain string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid domain",
|
||||
args: args{
|
||||
a: org.NewAggregate("test", "test"),
|
||||
domain: "",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-yqlVQ", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: org.NewAggregate("test", "test"),
|
||||
domain: "domain",
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
org.NewDomainVerifiedEvent(context.Background(), &org.NewAggregate("test", "test").Aggregate, "domain"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, VerifyOrgDomain(tt.args.a, tt.args.domain), nil, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDomainPrimary(t *testing.T) {
|
||||
type args struct {
|
||||
a *org.Aggregate
|
||||
domain string
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid domain",
|
||||
args: args{
|
||||
a: org.NewAggregate("test", "test"),
|
||||
domain: "",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-gmNqY", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: org.NewAggregate("test", "test"),
|
||||
domain: "domain",
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
org.NewDomainPrimarySetEvent(context.Background(), &org.NewAggregate("test", "test").Aggregate, "domain"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, SetPrimaryOrgDomain(tt.args.a, tt.args.domain), nil, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func AddOrgMember(a *org.Aggregate, userID string, roles ...string) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if userID == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument")
|
||||
}
|
||||
// TODO: check roles
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
if exists, err := ExistsUser(ctx, filter, userID, a.ID); err != nil || !exists {
|
||||
return nil, errors.ThrowNotFound(err, "ORG-GoXOn", "Errors.User.NotFound")
|
||||
}
|
||||
if isMember, err := IsOrgMember(ctx, filter, a.ID, userID); err != nil || isMember {
|
||||
return nil, errors.ThrowAlreadyExists(err, "ORG-poWwe", "Errors.Org.Member.AlreadyExists")
|
||||
}
|
||||
return []eventstore.Command{org.NewMemberAddedEvent(ctx, &a.Aggregate, userID, roles...)}, nil
|
||||
},
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
func IsOrgMember(ctx context.Context, filter preparation.FilterToQueryReducer, orgID, userID string) (isMember bool, err error) {
|
||||
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(orgID).
|
||||
OrderAsc().
|
||||
AddQuery().
|
||||
AggregateIDs(orgID).
|
||||
AggregateTypes(org.AggregateType).
|
||||
EventTypes(
|
||||
org.MemberAddedEventType,
|
||||
org.MemberRemovedEventType,
|
||||
org.MemberCascadeRemovedEventType,
|
||||
).Builder())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *org.MemberAddedEvent:
|
||||
if e.UserID == userID {
|
||||
isMember = true
|
||||
}
|
||||
case *org.MemberRemovedEvent:
|
||||
if e.UserID == userID {
|
||||
isMember = false
|
||||
}
|
||||
case *org.MemberCascadeRemovedEvent:
|
||||
if e.UserID == userID {
|
||||
isMember = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isMember, nil
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestAddMember(t *testing.T) {
|
||||
type args struct {
|
||||
a *org.Aggregate
|
||||
userID string
|
||||
roles []string
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
agg := org.NewAggregate("test", "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "no user id",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: "TODO: invalid roles",
|
||||
// args: args{
|
||||
// a: agg,
|
||||
// userID: "",
|
||||
// roles: []string{""},
|
||||
// },
|
||||
// want: preparation.Want{
|
||||
// ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-4Mlfs", "Errors.Invalid.Argument"),
|
||||
// },
|
||||
// },
|
||||
{
|
||||
name: "user not exists",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "userID",
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
CreateErr: errors.ThrowNotFound(nil, "ORG-GoXOn", "Errors.User.NotFound"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "already member",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "userID",
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
user.NewMachineAddedEvent(
|
||||
ctx,
|
||||
&user.NewAggregate("id", "ro").Aggregate,
|
||||
"userName",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewMemberAddedEvent(
|
||||
ctx,
|
||||
&org.NewAggregate("id", "ro").Aggregate,
|
||||
"userID",
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
CreateErr: errors.ThrowAlreadyExists(nil, "ORG-poWwe", "Errors.Org.Member.AlreadyExists"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: agg,
|
||||
userID: "userID",
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
user.NewMachineAddedEvent(
|
||||
ctx,
|
||||
&user.NewAggregate("id", "ro").Aggregate,
|
||||
"userName",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
org.NewMemberAddedEvent(ctx, &agg.Aggregate, "userID"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, AddOrgMember(tt.args.a, tt.args.userID, tt.args.roles...), tt.args.filter, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsMember(t *testing.T) {
|
||||
type args struct {
|
||||
filter preparation.FilterToQueryReducer
|
||||
orgID string
|
||||
userID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantExists bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no events",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{}, nil
|
||||
},
|
||||
orgID: "orgID",
|
||||
userID: "userID",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "member added",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewMemberAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("orgID", "ro").Aggregate,
|
||||
"userID",
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
orgID: "orgID",
|
||||
userID: "userID",
|
||||
},
|
||||
wantExists: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "member removed",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewMemberAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("orgID", "ro").Aggregate,
|
||||
"userID",
|
||||
),
|
||||
org.NewMemberRemovedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("orgID", "ro").Aggregate,
|
||||
"userID",
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
orgID: "orgID",
|
||||
userID: "userID",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "member cascade removed",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewMemberAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("orgID", "ro").Aggregate,
|
||||
"userID",
|
||||
),
|
||||
org.NewMemberCascadeRemovedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("orgID", "ro").Aggregate,
|
||||
"userID",
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
orgID: "orgID",
|
||||
userID: "userID",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "error durring filter",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "PROJE-Op26p", "Errors.Internal")
|
||||
},
|
||||
orgID: "orgID",
|
||||
userID: "userID",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotExists, err := IsOrgMember(context.Background(), tt.args.filter, tt.args.orgID, tt.args.userID)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotExists != tt.wantExists {
|
||||
t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func TestAddOrg(t *testing.T) {
|
||||
type args struct {
|
||||
a *org.Aggregate
|
||||
name string
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
agg := org.NewAggregate("test", "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid domain",
|
||||
args: args{
|
||||
a: agg,
|
||||
name: "",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "ORG-mruNY", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: agg,
|
||||
name: "caos ag",
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
org.NewOrgAddedEvent(ctx, &agg.Aggregate, "caos ag"),
|
||||
org.NewDomainAddedEvent(ctx, &agg.Aggregate, "caos-ag.localhost"),
|
||||
org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "caos-ag.localhost"),
|
||||
org.NewDomainPrimarySetEvent(ctx, &agg.Aggregate, "caos-ag.localhost"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, AddOrg(tt.args.a, tt.args.name, "localhost"), nil, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package preparation
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
// Validation of the input values of the command and if correct returns
|
||||
// the function to create commands or if not valid an error
|
||||
type Validation func() (CreateCommands, error)
|
||||
|
||||
// CreateCommands builds the commands
|
||||
// the filter param is an extended version of the eventstore filter method
|
||||
// it filters for events including the commands on the current context
|
||||
type CreateCommands func(context.Context, FilterToQueryReducer) ([]eventstore.Command, error)
|
||||
|
||||
// FilterToQueryReducer is an abstraction of the eventstore method
|
||||
type FilterToQueryReducer func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error)
|
||||
|
||||
var (
|
||||
//ErrNotExecutable is thrown if no command creator was created
|
||||
ErrNotExecutable = errors.ThrowInvalidArgument(nil, "PREPA-pH70n", "Errors.Internal")
|
||||
)
|
||||
|
||||
// PrepareCommands checks the passed validations and if ok creates the commands
|
||||
func PrepareCommands(ctx context.Context, filter FilterToQueryReducer, validations ...Validation) (cmds []eventstore.Command, err error) {
|
||||
commanders, err := validate(validations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return create(ctx, filter, commanders)
|
||||
}
|
||||
|
||||
func validate(validations []Validation) ([]CreateCommands, error) {
|
||||
creators := make([]CreateCommands, 0, len(validations))
|
||||
|
||||
for _, validate := range validations {
|
||||
cmds, err := validate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
creators = append(creators, cmds)
|
||||
}
|
||||
|
||||
if len(creators) == 0 {
|
||||
return nil, ErrNotExecutable
|
||||
}
|
||||
return creators, nil
|
||||
}
|
||||
|
||||
func create(ctx context.Context, filter FilterToQueryReducer, commanders []CreateCommands) (cmds []eventstore.Command, err error) {
|
||||
for _, command := range commanders {
|
||||
cmd, err := command(ctx, transactionFilter(filter, cmds))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmds = append(cmds, cmd...)
|
||||
}
|
||||
|
||||
return cmds, nil
|
||||
}
|
||||
|
||||
func transactionFilter(filter FilterToQueryReducer, commands []eventstore.Command) FilterToQueryReducer {
|
||||
return func(ctx context.Context, query *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
events, err := filter(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, command := range commands {
|
||||
event := command.(eventstore.Event)
|
||||
if !query.Matches(event, len(events)) {
|
||||
continue
|
||||
}
|
||||
events = append(events, event)
|
||||
}
|
||||
return events, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package preparation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
var errTest = errors.New("test")
|
||||
|
||||
func Test_validate(t *testing.T) {
|
||||
type args struct {
|
||||
validations []Validation
|
||||
}
|
||||
type want struct {
|
||||
len int
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "no validations",
|
||||
args: args{},
|
||||
want: want{
|
||||
err: ErrNotExecutable,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error in validation",
|
||||
args: args{
|
||||
validations: []Validation{
|
||||
func() (CreateCommands, error) {
|
||||
return nil, errTest
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
err: errTest,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
validations: []Validation{
|
||||
func() (CreateCommands, error) {
|
||||
return func(_ context.Context, _ FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
return nil, nil
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
len: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := validate(tt.args.validations)
|
||||
if !errors.Is(err, tt.want.err) {
|
||||
t.Errorf("validate() error = %v, wantErr %v", err, tt.want.err)
|
||||
return
|
||||
}
|
||||
if len(got) != tt.want.len {
|
||||
t.Errorf("validate() len = %v, want %v", len(got), tt.want.len)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_create(t *testing.T) {
|
||||
type args struct {
|
||||
commanders []CreateCommands
|
||||
}
|
||||
type want struct {
|
||||
err error
|
||||
len int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
{
|
||||
name: "error in command",
|
||||
want: want{
|
||||
err: errTest,
|
||||
},
|
||||
args: args{
|
||||
commanders: []CreateCommands{
|
||||
func(_ context.Context, _ FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
return nil, errTest
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no commands",
|
||||
want: want{},
|
||||
args: args{
|
||||
commanders: []CreateCommands{
|
||||
func(_ context.Context, _ FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple commands",
|
||||
want: want{
|
||||
len: 3,
|
||||
},
|
||||
args: args{
|
||||
commanders: []CreateCommands{
|
||||
func(_ context.Context, _ FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
return []eventstore.Command{new(testCommand), new(testCommand)}, nil
|
||||
},
|
||||
func(_ context.Context, _ FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
return []eventstore.Command{new(testCommand)}, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotCmds, err := create(context.Background(), nil, tt.args.commanders)
|
||||
if !errors.Is(err, tt.want.err) {
|
||||
t.Errorf("create() error = %v, wantErr %v", err, tt.want.err)
|
||||
return
|
||||
}
|
||||
if len(gotCmds) != tt.want.len {
|
||||
t.Errorf("create() len = %d, want %d", len(gotCmds), tt.want.len)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_transactionFilter(t *testing.T) {
|
||||
type args struct {
|
||||
filter FilterToQueryReducer
|
||||
commands []eventstore.Command
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want FilterToQueryReducer
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := transactionFilter(tt.args.filter, tt.args.commands); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("transactionFilter() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type testCommand struct {
|
||||
eventstore.BaseEvent
|
||||
}
|
||||
|
||||
func (c *testCommand) Data() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *testCommand) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// this is a helper file for tests
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
//Want represents the expected values for each step
|
||||
type Want struct {
|
||||
ValidationErr error
|
||||
CreateErr error
|
||||
Commands []eventstore.Command
|
||||
}
|
||||
|
||||
//AssertValidation checks if the validation works as inteded
|
||||
func AssertValidation(t *testing.T, validation preparation.Validation, filter preparation.FilterToQueryReducer, want Want) {
|
||||
t.Helper()
|
||||
|
||||
creates, err := validation()
|
||||
if !errors.Is(err, want.ValidationErr) {
|
||||
t.Errorf("wrong validation err = %v, want %v", err, want.ValidationErr)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cmds, err := creates(context.Background(), filter)
|
||||
if !errors.Is(err, want.CreateErr) {
|
||||
t.Errorf("wrong create err = %v, want %v", err, want.CreateErr)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(cmds) != len(want.Commands) {
|
||||
t.Errorf("wrong length of commands = %v, want %v", eventTypes(cmds), eventTypes(want.Commands))
|
||||
return
|
||||
}
|
||||
|
||||
for i, cmd := range want.Commands {
|
||||
if !reflect.DeepEqual(cmd, cmds[i]) {
|
||||
t.Errorf("unexpected command: = %v, want %v", cmds[i], cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func eventTypes(cmds []eventstore.Command) []eventstore.EventType {
|
||||
types := make([]eventstore.EventType, len(cmds))
|
||||
for i, cmd := range cmds {
|
||||
types[i] = cmd.Type()
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
type MultiFilter struct {
|
||||
count int
|
||||
filters []preparation.FilterToQueryReducer
|
||||
}
|
||||
|
||||
func NewMultiFilter() *MultiFilter {
|
||||
return new(MultiFilter)
|
||||
}
|
||||
|
||||
func (mf *MultiFilter) Append(filter preparation.FilterToQueryReducer) *MultiFilter {
|
||||
mf.filters = append(mf.filters, filter)
|
||||
return mf
|
||||
}
|
||||
|
||||
func (mf *MultiFilter) Filter() preparation.FilterToQueryReducer {
|
||||
return func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
mf.count++
|
||||
return mf.filters[mf.count-1](ctx, queryFactory)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/project"
|
||||
)
|
||||
|
||||
func AddProject(
|
||||
a *project.Aggregate,
|
||||
name string,
|
||||
owner string,
|
||||
projectRoleAssertion bool,
|
||||
projectRoleCheck bool,
|
||||
hasProjectCheck bool,
|
||||
privateLabelingSetting domain.PrivateLabelingSetting,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if name = strings.TrimSpace(name); name == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-C01yo", "Errors.Invalid.Argument")
|
||||
}
|
||||
if !privateLabelingSetting.Valid() {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-AO52V", "Errors.Invalid.Argument")
|
||||
}
|
||||
if owner == "" {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "PROJE-hzxwo", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
return []eventstore.Command{
|
||||
project.NewProjectAddedEvent(ctx, &a.Aggregate,
|
||||
name,
|
||||
projectRoleAssertion,
|
||||
projectRoleCheck,
|
||||
hasProjectCheck,
|
||||
privateLabelingSetting,
|
||||
),
|
||||
project.NewProjectMemberAddedEvent(ctx, &a.Aggregate,
|
||||
owner,
|
||||
domain.RoleProjectOwner),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func ExistsProject(ctx context.Context, filter preparation.FilterToQueryReducer, projectID, resourceOwner string) (exists bool, err error) {
|
||||
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(resourceOwner).
|
||||
OrderAsc().
|
||||
AddQuery().
|
||||
AggregateTypes(project.AggregateType).
|
||||
AggregateIDs(projectID).
|
||||
EventTypes(
|
||||
project.ProjectAddedType,
|
||||
project.ProjectRemovedType,
|
||||
).Builder())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
switch event.(type) {
|
||||
case *project.ProjectAddedEvent:
|
||||
exists = true
|
||||
case *project.ProjectRemovedEvent:
|
||||
exists = false
|
||||
}
|
||||
}
|
||||
|
||||
return exists, nil
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/project"
|
||||
)
|
||||
|
||||
func AddOIDCApp(
|
||||
a project.Aggregate,
|
||||
version domain.OIDCVersion,
|
||||
appID,
|
||||
name,
|
||||
clientID string,
|
||||
clientSecret *crypto.CryptoValue,
|
||||
redirectUris []string,
|
||||
responseTypes []domain.OIDCResponseType,
|
||||
grantTypes []domain.OIDCGrantType,
|
||||
applicationType domain.OIDCApplicationType,
|
||||
authMethodType domain.OIDCAuthMethodType,
|
||||
postLogoutRedirectUris []string,
|
||||
devMode bool,
|
||||
accessTokenType domain.OIDCTokenType,
|
||||
accessTokenRoleAssertion bool,
|
||||
idTokenRoleAssertion bool,
|
||||
idTokenUserinfoAssertion bool,
|
||||
clockSkew time.Duration,
|
||||
additionalOrigins []string,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if appID == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument")
|
||||
}
|
||||
if name = strings.TrimSpace(name); name == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-Fef31", "Errors.Invalid.Argument")
|
||||
}
|
||||
if clientID == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-ghTsJ", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
if exists, err := ExistsProject(ctx, filter, a.ID, a.ResourceOwner); !exists || err != nil {
|
||||
return nil, errors.ThrowNotFound(err, "PROJE-5LQ0U", "Errors.Project.NotFound")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
project.NewApplicationAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
appID,
|
||||
name,
|
||||
),
|
||||
project.NewOIDCConfigAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
version,
|
||||
appID,
|
||||
clientID,
|
||||
clientSecret,
|
||||
redirectUris,
|
||||
responseTypes,
|
||||
grantTypes,
|
||||
applicationType,
|
||||
authMethodType,
|
||||
postLogoutRedirectUris,
|
||||
devMode,
|
||||
accessTokenType,
|
||||
accessTokenRoleAssertion,
|
||||
idTokenRoleAssertion,
|
||||
idTokenUserinfoAssertion,
|
||||
clockSkew,
|
||||
additionalOrigins,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddAPIApp(
|
||||
a project.Aggregate,
|
||||
appID,
|
||||
name,
|
||||
clientID string,
|
||||
clientSecret *crypto.CryptoValue,
|
||||
authMethodType domain.APIAuthMethodType,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if appID == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument")
|
||||
}
|
||||
if name = strings.TrimSpace(name); name == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-F7g21", "Errors.Invalid.Argument")
|
||||
}
|
||||
if clientID == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "PROJE-XXED5", "Errors.Invalid.Argument")
|
||||
}
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
if exists, err := ExistsProject(ctx, filter, a.ID, a.ResourceOwner); !exists || err != nil {
|
||||
return nil, errors.ThrowNotFound(err, "PROJE-Sf2gb", "Errors.Project.NotFound")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
project.NewApplicationAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
appID,
|
||||
name,
|
||||
),
|
||||
project.NewAPIConfigAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
appID,
|
||||
clientID,
|
||||
clientSecret,
|
||||
authMethodType,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func ExistsApp(ctx context.Context, filter preparation.FilterToQueryReducer, projectID, appID, resourceOwner string) (exists bool, err error) {
|
||||
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(resourceOwner).
|
||||
OrderAsc().
|
||||
AddQuery().
|
||||
AggregateTypes(project.AggregateType).
|
||||
AggregateIDs(projectID).
|
||||
EventTypes(
|
||||
project.ApplicationAddedType,
|
||||
project.ApplicationRemovedType,
|
||||
).Builder())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *project.ApplicationAddedEvent:
|
||||
if e.AppID == appID {
|
||||
exists = true
|
||||
}
|
||||
case *project.ApplicationRemovedEvent:
|
||||
if e.AppID == appID {
|
||||
exists = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return exists, nil
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/project"
|
||||
)
|
||||
|
||||
func TestAddOIDCApp(t *testing.T) {
|
||||
type args struct {
|
||||
a *project.Aggregate
|
||||
appID string
|
||||
name string
|
||||
clientID string
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
agg := project.NewAggregate("test", "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid appID",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "",
|
||||
name: "name",
|
||||
clientID: "clientID",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-NnavI", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid name",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "appID",
|
||||
name: "",
|
||||
clientID: "clientID",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-Fef31", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid clientID",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "appID",
|
||||
name: "name",
|
||||
clientID: "",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-ghTsJ", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "project not exists",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "id",
|
||||
name: "name",
|
||||
clientID: "clientID",
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
CreateErr: errors.ThrowNotFound(nil, "PROJE-5LQ0U", "Errors.Project.NotFound"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "appID",
|
||||
name: "name",
|
||||
clientID: "clientID",
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
project.NewProjectAddedEvent(
|
||||
ctx,
|
||||
&agg.Aggregate,
|
||||
"project",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingUnspecified,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
project.NewApplicationAddedEvent(ctx, &agg.Aggregate,
|
||||
"appID",
|
||||
"name",
|
||||
),
|
||||
project.NewOIDCConfigAddedEvent(ctx, &agg.Aggregate,
|
||||
domain.OIDCVersionV1,
|
||||
"appID",
|
||||
"clientID",
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
domain.OIDCApplicationTypeWeb,
|
||||
domain.OIDCAuthMethodTypeBasic,
|
||||
nil,
|
||||
false,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
nil,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t,
|
||||
AddOIDCApp(*tt.args.a,
|
||||
domain.OIDCVersionV1,
|
||||
tt.args.appID,
|
||||
tt.args.name,
|
||||
tt.args.clientID,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
domain.OIDCApplicationTypeWeb,
|
||||
domain.OIDCAuthMethodTypeBasic,
|
||||
nil,
|
||||
false,
|
||||
domain.OIDCTokenTypeBearer,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
nil,
|
||||
), tt.args.filter, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddAPIConfig(t *testing.T) {
|
||||
type args struct {
|
||||
a *project.Aggregate
|
||||
appID string
|
||||
name string
|
||||
clientID string
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
agg := project.NewAggregate("test", "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid appID",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "",
|
||||
name: "name",
|
||||
clientID: "clientID",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-XHsKt", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid name",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "appID",
|
||||
name: "",
|
||||
clientID: "clientID",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-F7g21", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid clientID",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "appID",
|
||||
name: "name",
|
||||
clientID: "",
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-XXED5", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "project not exists",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "id",
|
||||
name: "name",
|
||||
clientID: "clientID",
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
CreateErr: errors.ThrowNotFound(nil, "PROJE-Sf2gb", "Errors.Project.NotFound"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: agg,
|
||||
appID: "appID",
|
||||
name: "name",
|
||||
clientID: "clientID",
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
project.NewProjectAddedEvent(
|
||||
ctx,
|
||||
&agg.Aggregate,
|
||||
"project",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingUnspecified,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
project.NewApplicationAddedEvent(
|
||||
ctx,
|
||||
&agg.Aggregate,
|
||||
"appID",
|
||||
"name",
|
||||
),
|
||||
project.NewAPIConfigAddedEvent(ctx, &agg.Aggregate,
|
||||
"appID",
|
||||
"clientID",
|
||||
nil,
|
||||
domain.APIAuthMethodTypeBasic,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t,
|
||||
AddAPIApp(*tt.args.a,
|
||||
tt.args.appID,
|
||||
tt.args.name,
|
||||
tt.args.clientID,
|
||||
nil,
|
||||
domain.APIAuthMethodTypeBasic,
|
||||
), tt.args.filter, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExistsApp(t *testing.T) {
|
||||
type args struct {
|
||||
filter preparation.FilterToQueryReducer
|
||||
appID string
|
||||
projectID string
|
||||
resourceOwner string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantExists bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no events",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{}, nil
|
||||
},
|
||||
appID: "appID",
|
||||
projectID: "projectID",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "app added",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
project.NewApplicationAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("id", "ro").Aggregate,
|
||||
"appID",
|
||||
"name",
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
appID: "appID",
|
||||
projectID: "projectID",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "app removed",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
project.NewApplicationAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("id", "ro").Aggregate,
|
||||
"appID",
|
||||
"name",
|
||||
),
|
||||
project.NewApplicationRemovedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("id", "ro").Aggregate,
|
||||
"appID",
|
||||
"name",
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
appID: "appID",
|
||||
projectID: "projectID",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "error durring filter",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "PROJE-Op26p", "Errors.Internal")
|
||||
},
|
||||
appID: "appID",
|
||||
projectID: "projectID",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotExists, err := ExistsApp(context.Background(), tt.args.filter, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotExists != tt.wantExists {
|
||||
t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/project"
|
||||
)
|
||||
|
||||
func TestAddProject(t *testing.T) {
|
||||
type args struct {
|
||||
a *project.Aggregate
|
||||
name string
|
||||
owner string
|
||||
privateLabelingSetting domain.PrivateLabelingSetting
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
agg := project.NewAggregate("test", "test")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid name",
|
||||
args: args{
|
||||
a: agg,
|
||||
name: "",
|
||||
owner: "owner",
|
||||
privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-C01yo", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid private labeling setting",
|
||||
args: args{
|
||||
a: agg,
|
||||
name: "name",
|
||||
owner: "owner",
|
||||
privateLabelingSetting: -1,
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "PROJE-AO52V", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid owner",
|
||||
args: args{
|
||||
a: agg,
|
||||
name: "name",
|
||||
owner: "",
|
||||
privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowPreconditionFailed(nil, "PROJE-hzxwo", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: agg,
|
||||
name: "ZITADEL",
|
||||
owner: "CAOS AG",
|
||||
privateLabelingSetting: domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
project.NewProjectAddedEvent(ctx, &agg.Aggregate,
|
||||
"ZITADEL",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingAllowLoginUserResourceOwnerPolicy,
|
||||
),
|
||||
project.NewProjectMemberAddedEvent(ctx, &agg.Aggregate,
|
||||
"CAOS AG",
|
||||
domain.RoleProjectOwner),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, AddProject(tt.args.a, tt.args.name, tt.args.owner, false, false, false, tt.args.privateLabelingSetting), nil, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExistsProject(t *testing.T) {
|
||||
type args struct {
|
||||
filter preparation.FilterToQueryReducer
|
||||
id string
|
||||
resourceOwner string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantExists bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no events",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{}, nil
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "project added",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
project.NewProjectAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("id", "ro").Aggregate,
|
||||
"name",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingEnforceProjectResourceOwnerPolicy,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "project removed",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
project.NewProjectAddedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("id", "ro").Aggregate,
|
||||
"name",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
domain.PrivateLabelingSettingEnforceProjectResourceOwnerPolicy,
|
||||
),
|
||||
project.NewProjectRemovedEvent(
|
||||
context.Background(),
|
||||
&project.NewAggregate("id", "ro").Aggregate,
|
||||
"name",
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "error durring filter",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "PROJE-Op26p", "Errors.Internal")
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotExists, err := ExistsProject(context.Background(), tt.args.filter, tt.args.id, tt.args.resourceOwner)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotExists != tt.wantExists {
|
||||
t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func ExistsUser(ctx context.Context, filter preparation.FilterToQueryReducer, id, resourceOwner string) (exists bool, err error) {
|
||||
events, err := filter(ctx, eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(resourceOwner).
|
||||
OrderAsc().
|
||||
AddQuery().
|
||||
AggregateTypes(user.AggregateType).
|
||||
AggregateIDs(id).
|
||||
EventTypes(
|
||||
user.HumanRegisteredType,
|
||||
user.UserV1RegisteredType,
|
||||
user.HumanAddedType,
|
||||
user.UserV1AddedType,
|
||||
user.MachineAddedEventType,
|
||||
user.UserRemovedType,
|
||||
).Builder())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, event := range events {
|
||||
switch event.(type) {
|
||||
case *user.HumanRegisteredEvent, *user.HumanAddedEvent, *user.MachineAddedEvent:
|
||||
exists = true
|
||||
case *user.UserRemovedEvent:
|
||||
exists = false
|
||||
}
|
||||
}
|
||||
|
||||
return exists, nil
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func domainPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) {
|
||||
wm, err := orgDomainPolicy(ctx, filter)
|
||||
if err != nil || wm != nil && wm.State.Exists() {
|
||||
return wm, err
|
||||
}
|
||||
wm, err = instanceDomainPolicy(ctx, filter)
|
||||
if err != nil || wm != nil {
|
||||
return wm, err
|
||||
}
|
||||
return nil, errors.ThrowInternal(nil, "USER-Ggk9n", "Errors.Internal")
|
||||
}
|
||||
|
||||
func orgDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) {
|
||||
policy := command.NewOrgDomainPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
|
||||
events, err := filter(ctx, policy.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
policy.AppendEvents(events...)
|
||||
err = policy.Reduce()
|
||||
return &policy.PolicyDomainWriteModel, err
|
||||
}
|
||||
|
||||
func instanceDomainPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PolicyDomainWriteModel, error) {
|
||||
policy := command.NewInstanceDomainPolicyWriteModel()
|
||||
events, err := filter(ctx, policy.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
policy.AppendEvents(events...)
|
||||
err = policy.Reduce()
|
||||
return &policy.PolicyDomainWriteModel, err
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func Test_customDomainPolicy(t *testing.T) {
|
||||
type args struct {
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.PolicyDomainWriteModel
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "err from filter",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "USER-IgYlN", "Errors.Internal")
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no events",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{}, nil
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "policy found",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("id", "ro").Aggregate,
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: &command.PolicyDomainWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "id",
|
||||
ResourceOwner: "ro",
|
||||
Events: []eventstore.Event{},
|
||||
},
|
||||
UserLoginMustBeDomain: true,
|
||||
State: domain.PolicyStateActive,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := orgDomainPolicy(context.Background(), tt.args.filter)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("customDomainPolicy() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("customDomainPolicy() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_defaultDomainPolicy(t *testing.T) {
|
||||
type args struct {
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.PolicyDomainWriteModel
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "err from filter",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "USER-IgYlN", "Errors.Internal")
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no events",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{}, nil
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "policy found",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
instance.NewDomainPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&instance.NewAggregate().Aggregate,
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: &command.PolicyDomainWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "IAM",
|
||||
ResourceOwner: "IAM",
|
||||
Events: []eventstore.Event{},
|
||||
},
|
||||
UserLoginMustBeDomain: true,
|
||||
State: domain.PolicyStateActive,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := instanceDomainPolicy(context.Background(), tt.args.filter)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("defaultDomainPolicy() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("defaultDomainPolicy() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_DomainPolicy(t *testing.T) {
|
||||
type args struct {
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.PolicyDomainWriteModel
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "err from filter custom",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "USER-IgYlN", "Errors.Internal")
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "custom found",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("id", "ro").Aggregate,
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: &command.PolicyDomainWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "id",
|
||||
ResourceOwner: "ro",
|
||||
Events: []eventstore.Event{},
|
||||
},
|
||||
UserLoginMustBeDomain: true,
|
||||
State: domain.PolicyStateActive,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "err from filter default",
|
||||
args: args{
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
}).
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "USER-6HnsD", "Errors.Internal")
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "default found",
|
||||
args: args{
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
}).
|
||||
Append(func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
instance.NewDomainPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&instance.NewAggregate().Aggregate,
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: &command.PolicyDomainWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "IAM",
|
||||
ResourceOwner: "IAM",
|
||||
Events: []eventstore.Event{},
|
||||
},
|
||||
UserLoginMustBeDomain: true,
|
||||
State: domain.PolicyStateActive,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "no policy found",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := domainPolicyWriteModel(context.Background(), tt.args.filter)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("defaultDomainPolicy() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("defaultDomainPolicy() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
type AddHuman struct {
|
||||
// Username is required
|
||||
Username string
|
||||
// FirstName is required
|
||||
FirstName string
|
||||
// LastName is required
|
||||
LastName string
|
||||
// NickName is required
|
||||
NickName string
|
||||
// DisplayName is required
|
||||
DisplayName string
|
||||
// Email is required
|
||||
Email string
|
||||
// PreferredLang is required
|
||||
PreferredLang language.Tag
|
||||
// Gender is required
|
||||
Gender domain.Gender
|
||||
//TODO: can it also be verified?
|
||||
Phone string
|
||||
//Password is optional
|
||||
Password string
|
||||
//PasswordChangeRequired is used if the `Password`-field is set
|
||||
PasswordChangeRequired bool
|
||||
}
|
||||
|
||||
func AddHumanCommand(a *user.Aggregate, human *AddHuman, passwordAlg crypto.HashAlgorithm) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if !domain.EmailRegex.MatchString(human.Email) {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "USER-Ec7dM", "Errors.Invalid.Argument")
|
||||
}
|
||||
if human.FirstName = strings.TrimSpace(human.FirstName); human.FirstName == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.Invalid.Argument")
|
||||
}
|
||||
if human.LastName = strings.TrimSpace(human.LastName); human.LastName == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "USER-DiAq8", "Errors.Invalid.Argument")
|
||||
}
|
||||
human.Phone = strings.TrimSpace(human.Phone)
|
||||
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
domainPolicy, err := domainPolicyWriteModel(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := user.NewHumanAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
human.Username,
|
||||
human.FirstName,
|
||||
human.LastName,
|
||||
human.NickName,
|
||||
human.DisplayName,
|
||||
human.PreferredLang,
|
||||
human.Gender,
|
||||
human.Email, //TODO: pass if verified
|
||||
domainPolicy.UserLoginMustBeDomain,
|
||||
)
|
||||
if human.Phone != "" {
|
||||
cmd.AddPhoneData(human.Phone) //TODO: pass if verified
|
||||
}
|
||||
if human.Password != "" {
|
||||
passwordComplexity, err := passwordComplexityPolicyWriteModel(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = passwordComplexity.Validate(human.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
secret, err := crypto.Hash([]byte(human.Password), passwordAlg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd.AddPasswordData(secret, human.PasswordChangeRequired)
|
||||
}
|
||||
|
||||
return []eventstore.Command{cmd}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestAddHumanCommand(t *testing.T) {
|
||||
type args struct {
|
||||
a *user.Aggregate
|
||||
human *AddHuman
|
||||
passwordAlg crypto.HashAlgorithm
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
agg := user.NewAggregate("id", "ro")
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Want
|
||||
}{
|
||||
{
|
||||
name: "invalid email",
|
||||
args: args{
|
||||
a: agg,
|
||||
human: &AddHuman{
|
||||
Email: "invalid",
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-Ec7dM", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid first name",
|
||||
args: args{
|
||||
a: agg,
|
||||
human: &AddHuman{
|
||||
Email: "[email protected]",
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid last name",
|
||||
args: args{
|
||||
a: agg,
|
||||
human: &AddHuman{
|
||||
Email: "[email protected]",
|
||||
FirstName: "hurst",
|
||||
},
|
||||
},
|
||||
want: Want{
|
||||
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-DiAq8", "Errors.Invalid.Argument"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid password",
|
||||
args: args{
|
||||
a: agg,
|
||||
human: &AddHuman{
|
||||
Email: "[email protected]",
|
||||
FirstName: "gigi",
|
||||
LastName: "giraffe",
|
||||
Password: "short",
|
||||
},
|
||||
filter: NewMultiFilter().Append(
|
||||
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("id", "ro").Aggregate,
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Append(
|
||||
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewPasswordComplexityPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("id", "ro").Aggregate,
|
||||
8,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
CreateErr: errors.ThrowInvalidArgument(nil, "COMMA-HuJf6", "Errors.User.PasswordComplexityPolicy.MinLength"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct",
|
||||
args: args{
|
||||
a: agg,
|
||||
human: &AddHuman{
|
||||
Email: "[email protected]",
|
||||
FirstName: "gigi",
|
||||
LastName: "giraffe",
|
||||
Password: "",
|
||||
},
|
||||
passwordAlg: crypto.CreateMockHashAlg(gomock.NewController(t)),
|
||||
filter: NewMultiFilter().Append(
|
||||
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("id", "ro").Aggregate,
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Append(
|
||||
func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewPasswordComplexityPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("id", "ro").Aggregate,
|
||||
2,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&agg.Aggregate,
|
||||
"",
|
||||
"gigi",
|
||||
"giraffe",
|
||||
"",
|
||||
"",
|
||||
language.Und,
|
||||
0,
|
||||
"[email protected]",
|
||||
true,
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
AssertValidation(t, AddHumanCommand(tt.args.a, tt.args.human, tt.args.passwordAlg), tt.args.filter, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/authz"
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func passwordComplexityPolicyWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) {
|
||||
wm, err := customPasswordComplexityPolicy(ctx, filter)
|
||||
if err != nil || wm != nil && wm.State.Exists() {
|
||||
return wm, err
|
||||
}
|
||||
wm, err = defaultPasswordComplexityPolicy(ctx, filter)
|
||||
if err != nil || wm != nil {
|
||||
return wm, err
|
||||
}
|
||||
return nil, errors.ThrowInternal(nil, "USER-uQ96e", "Errors.Internal")
|
||||
}
|
||||
|
||||
func customPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) {
|
||||
policy := command.NewOrgPasswordComplexityPolicyWriteModel(authz.GetCtxData(ctx).OrgID)
|
||||
events, err := filter(ctx, policy.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
policy.AppendEvents(events...)
|
||||
err = policy.Reduce()
|
||||
return &policy.PasswordComplexityPolicyWriteModel, err
|
||||
}
|
||||
|
||||
func defaultPasswordComplexityPolicy(ctx context.Context, filter preparation.FilterToQueryReducer) (*command.PasswordComplexityPolicyWriteModel, error) {
|
||||
policy := command.NewInstancePasswordComplexityPolicyWriteModel()
|
||||
events, err := filter(ctx, policy.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(events) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
policy.AppendEvents(events...)
|
||||
err = policy.Reduce()
|
||||
return &policy.PasswordComplexityPolicyWriteModel, err
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/command"
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/instance"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func Test_customPasswordComplexityPolicy(t *testing.T) {
|
||||
type args struct {
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.PasswordComplexityPolicyWriteModel
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "err from filter",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "USER-IgYlN", "Errors.Internal")
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no events",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{}, nil
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "policy found",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewPasswordComplexityPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("id", "ro").Aggregate,
|
||||
8,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: &command.PasswordComplexityPolicyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "id",
|
||||
ResourceOwner: "ro",
|
||||
Events: []eventstore.Event{},
|
||||
},
|
||||
MinLength: 8,
|
||||
HasLowercase: true,
|
||||
HasUppercase: true,
|
||||
HasNumber: true,
|
||||
HasSymbol: true,
|
||||
State: domain.PolicyStateActive,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := customPasswordComplexityPolicy(context.Background(), tt.args.filter)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("customPasswordComplexityPolicy() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("customPasswordComplexityPolicy() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_defaultPasswordComplexityPolicy(t *testing.T) {
|
||||
type args struct {
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.PasswordComplexityPolicyWriteModel
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "err from filter",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "USER-IgYlN", "Errors.Internal")
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no events",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{}, nil
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "policy found",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
instance.NewPasswordComplexityPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&instance.NewAggregate().Aggregate,
|
||||
8,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: &command.PasswordComplexityPolicyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "IAM",
|
||||
ResourceOwner: "IAM",
|
||||
Events: []eventstore.Event{},
|
||||
},
|
||||
MinLength: 8,
|
||||
HasLowercase: true,
|
||||
HasUppercase: true,
|
||||
HasNumber: true,
|
||||
HasSymbol: true,
|
||||
State: domain.PolicyStateActive,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := defaultPasswordComplexityPolicy(context.Background(), tt.args.filter)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("defaultPasswordComplexityPolicy() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("defaultPasswordComplexityPolicy() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_passwordComplexityPolicy(t *testing.T) {
|
||||
type args struct {
|
||||
filter preparation.FilterToQueryReducer
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *command.PasswordComplexityPolicyWriteModel
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "err from filter custom",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "USER-IgYlN", "Errors.Internal")
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "custom found",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewPasswordComplexityPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&org.NewAggregate("id", "ro").Aggregate,
|
||||
8,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
want: &command.PasswordComplexityPolicyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "id",
|
||||
ResourceOwner: "ro",
|
||||
Events: []eventstore.Event{},
|
||||
},
|
||||
MinLength: 8,
|
||||
HasLowercase: true,
|
||||
HasUppercase: true,
|
||||
HasNumber: true,
|
||||
HasSymbol: true,
|
||||
State: domain.PolicyStateActive,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "err from filter default",
|
||||
args: args{
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
}).
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "USER-6HnsD", "Errors.Internal")
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "default found",
|
||||
args: args{
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
}).
|
||||
Append(func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
instance.NewPasswordComplexityPolicyAddedEvent(
|
||||
context.Background(),
|
||||
&instance.NewAggregate().Aggregate,
|
||||
8,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
},
|
||||
want: &command.PasswordComplexityPolicyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: "IAM",
|
||||
ResourceOwner: "IAM",
|
||||
Events: []eventstore.Event{},
|
||||
},
|
||||
MinLength: 8,
|
||||
HasLowercase: true,
|
||||
HasUppercase: true,
|
||||
HasNumber: true,
|
||||
HasSymbol: true,
|
||||
State: domain.PolicyStateActive,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "no policy found",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := passwordComplexityPolicyWriteModel(context.Background(), tt.args.filter)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("defaultPasswordComplexityPolicy() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("defaultPasswordComplexityPolicy() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/caos/zitadel/internal/command/v2/preparation"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/user"
|
||||
)
|
||||
|
||||
func TestExistsUser(t *testing.T) {
|
||||
type args struct {
|
||||
filter preparation.FilterToQueryReducer
|
||||
id string
|
||||
resourceOwner string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantExists bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no events",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{}, nil
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "human registered",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
user.NewHumanRegisteredEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("id", "ro").Aggregate,
|
||||
"userName",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"nickName",
|
||||
"displayName",
|
||||
language.German,
|
||||
domain.GenderFemale,
|
||||
"[email protected]",
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "human added",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
user.NewHumanAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("id", "ro").Aggregate,
|
||||
"userName",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"nickName",
|
||||
"displayName",
|
||||
language.German,
|
||||
domain.GenderFemale,
|
||||
"[email protected]",
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "machine added",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
user.NewMachineAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("id", "ro").Aggregate,
|
||||
"userName",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "user removed",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
user.NewMachineAddedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("removed", "ro").Aggregate,
|
||||
"userName",
|
||||
"name",
|
||||
"description",
|
||||
true,
|
||||
),
|
||||
user.NewUserRemovedEvent(
|
||||
context.Background(),
|
||||
&user.NewAggregate("removed", "ro").Aggregate,
|
||||
"userName",
|
||||
nil,
|
||||
true,
|
||||
),
|
||||
}, nil
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "error durring filter",
|
||||
args: args{
|
||||
filter: func(_ context.Context, _ *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, errors.ThrowInternal(nil, "USER-Drebn", "Errors.Internal")
|
||||
},
|
||||
id: "id",
|
||||
resourceOwner: "ro",
|
||||
},
|
||||
wantExists: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotExists, err := ExistsUser(context.Background(), tt.args.filter, tt.args.id, tt.args.resourceOwner)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ExistsUser() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotExists != tt.wantExists {
|
||||
t.Errorf("ExistsUser() = %v, want %v", gotExists, tt.wantExists)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user