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 <livio.a@gmail.com>
Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
This commit is contained in:
Silvan
2022-03-28 10:05:09 +02:00
committed by GitHub
parent 9d4f296c62
commit c5b99274d7
175 changed files with 5213 additions and 2212 deletions

View File

@@ -13,7 +13,7 @@ import (
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/id"
"github.com/caos/zitadel/internal/repository/action"
iam_repo "github.com/caos/zitadel/internal/repository/instance"
instance_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"
@@ -82,7 +82,7 @@ func StartCommands(es *eventstore.Eventstore,
domainVerificationAlg: domainVerificationEncryption,
keyAlgorithm: oidcEncryption,
}
iam_repo.RegisterEventMappers(repo.eventstore)
instance_repo.RegisterEventMappers(repo.eventstore)
org.RegisterEventMappers(repo.eventstore)
usr_repo.RegisterEventMappers(repo.eventstore)
usr_grant_repo.RegisterEventMappers(repo.eventstore)

View File

@@ -27,7 +27,7 @@ func (c *Commands) SetDefaultMessageText(ctx context.Context, messageText *domai
return writeModelToObjectDetails(&existingMessageText.WriteModel), nil
}
func (c *Commands) setDefaultMessageText(ctx context.Context, instanceAgg *eventstore.Aggregate, msg *domain.CustomMessageText) ([]eventstore.Command, *InstanceCustomMessageTextReadModel, error) {
func (c *Commands) setDefaultMessageText(ctx context.Context, instanceAgg *eventstore.Aggregate, msg *domain.CustomMessageText) ([]eventstore.Command, *InstanceCustomMessageTextWriteModel, error) {
if !msg.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "INSTANCE-kd9fs", "Errors.CustomMessageText.Invalid")
}
@@ -113,7 +113,7 @@ func (c *Commands) RemoveInstanceMessageTexts(ctx context.Context, messageTextTy
return writeModelToObjectDetails(&customText.WriteModel), nil
}
func (c *Commands) defaultCustomMessageTextWriteModelByID(ctx context.Context, messageType string, lang language.Tag) (*InstanceCustomMessageTextReadModel, error) {
func (c *Commands) defaultCustomMessageTextWriteModelByID(ctx context.Context, messageType string, lang language.Tag) (*InstanceCustomMessageTextWriteModel, error) {
writeModel := NewInstanceCustomMessageTextWriteModel(messageType, lang)
err := c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {

View File

@@ -8,12 +8,12 @@ import (
"github.com/caos/zitadel/internal/repository/instance"
)
type InstanceCustomMessageTextReadModel struct {
type InstanceCustomMessageTextWriteModel struct {
CustomMessageTextReadModel
}
func NewInstanceCustomMessageTextWriteModel(messageTextType string, lang language.Tag) *InstanceCustomMessageTextReadModel {
return &InstanceCustomMessageTextReadModel{
func NewInstanceCustomMessageTextWriteModel(messageTextType string, lang language.Tag) *InstanceCustomMessageTextWriteModel {
return &InstanceCustomMessageTextWriteModel{
CustomMessageTextReadModel{
WriteModel: eventstore.WriteModel{
AggregateID: domain.IAMID,
@@ -25,7 +25,7 @@ func NewInstanceCustomMessageTextWriteModel(messageTextType string, lang languag
}
}
func (wm *InstanceCustomMessageTextReadModel) AppendEvents(events ...eventstore.Event) {
func (wm *InstanceCustomMessageTextWriteModel) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
case *instance.CustomTextSetEvent:
@@ -38,11 +38,11 @@ func (wm *InstanceCustomMessageTextReadModel) AppendEvents(events ...eventstore.
}
}
func (wm *InstanceCustomMessageTextReadModel) Reduce() error {
func (wm *InstanceCustomMessageTextWriteModel) Reduce() error {
return wm.CustomMessageTextReadModel.Reduce()
}
func (wm *InstanceCustomMessageTextReadModel) Query() *eventstore.SearchQueryBuilder {
func (wm *InstanceCustomMessageTextWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().

View File

@@ -37,7 +37,7 @@ func (c *Commands) addDefaultDomainPolicy(ctx context.Context, instanceAgg *even
if addedPolicy.State == domain.PolicyStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "INSTANCE-Lk0dS", "Errors.IAM.DomainPolicy.AlreadyExists")
}
return iam_repo.NewInstnaceDomainPolicyAddedEvent(ctx, instanceAgg, policy.UserLoginMustBeDomain), nil
return iam_repo.NewDomainPolicyAddedEvent(ctx, instanceAgg, policy.UserLoginMustBeDomain), nil
}
func (c *Commands) ChangeDefaultDomainPolicy(ctx context.Context, policy *domain.DomainPolicy) (*domain.DomainPolicy, error) {

View File

@@ -28,9 +28,9 @@ func NewInstanceDomainPolicyWriteModel() *InstanceDomainPolicyWriteModel {
func (wm *InstanceDomainPolicyWriteModel) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
case *instance.InstanceDomainPolicyAddedEvent:
case *instance.DomainPolicyAddedEvent:
wm.PolicyDomainWriteModel.AppendEvents(&e.DomainPolicyAddedEvent)
case *instance.InstanceDomainPolicyChangedEvent:
case *instance.DomainPolicyChangedEvent:
wm.PolicyDomainWriteModel.AppendEvents(&e.DomainPolicyChangedEvent)
}
}
@@ -47,15 +47,15 @@ func (wm *InstanceDomainPolicyWriteModel) Query() *eventstore.SearchQueryBuilder
AggregateTypes(instance.AggregateType).
AggregateIDs(wm.PolicyDomainWriteModel.AggregateID).
EventTypes(
instance.InstanceDomainPolicyAddedEventType,
instance.InstanceDomainPolicyChangedEventType).
instance.DomainPolicyAddedEventType,
instance.DomainPolicyChangedEventType).
Builder()
}
func (wm *InstanceDomainPolicyWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
userLoginMustBeDomain bool) (*instance.InstanceDomainPolicyChangedEvent, bool) {
userLoginMustBeDomain bool) (*instance.DomainPolicyChangedEvent, bool) {
changes := make([]policy.OrgPolicyChanges, 0)
if wm.UserLoginMustBeDomain != userLoginMustBeDomain {
changes = append(changes, policy.ChangeUserLoginMustBeDomain(userLoginMustBeDomain))
@@ -63,7 +63,7 @@ func (wm *InstanceDomainPolicyWriteModel) NewChangedEvent(
if len(changes) == 0 {
return nil, false
}
changedEvent, err := instance.NewInstanceDomainPolicyChangedEvent(ctx, aggregate, changes)
changedEvent, err := instance.NewDomainPolicyChangedEvent(ctx, aggregate, changes)
if err != nil {
return nil, false
}

View File

@@ -33,13 +33,13 @@ func TestCommandSide_AddDefaultDomainPolicy(t *testing.T) {
res res
}{
{
name: "orgiam policy already existing, already exists error",
name: "domain policy already existing, already exists error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
instance.NewInstnaceDomainPolicyAddedEvent(context.Background(),
instance.NewDomainPolicyAddedEvent(context.Background(),
&instance.NewAggregate().Aggregate,
true,
),
@@ -66,7 +66,7 @@ func TestCommandSide_AddDefaultDomainPolicy(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
instance.NewInstnaceDomainPolicyAddedEvent(context.Background(),
instance.NewDomainPolicyAddedEvent(context.Background(),
&instance.NewAggregate().Aggregate,
true,
),
@@ -111,7 +111,7 @@ func TestCommandSide_AddDefaultDomainPolicy(t *testing.T) {
}
}
func TestCommandSide_ChangeDefaultOrgIAMPolicy(t *testing.T) {
func TestCommandSide_ChangeDefaultDomainPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
@@ -130,7 +130,7 @@ func TestCommandSide_ChangeDefaultOrgIAMPolicy(t *testing.T) {
res res
}{
{
name: "orgiampolicy not existing, not found error",
name: "domain policy not existing, not found error",
fields: fields{
eventstore: eventstoreExpect(
t,
@@ -154,7 +154,7 @@ func TestCommandSide_ChangeDefaultOrgIAMPolicy(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
instance.NewInstnaceDomainPolicyAddedEvent(context.Background(),
instance.NewDomainPolicyAddedEvent(context.Background(),
&instance.NewAggregate().Aggregate,
true,
),
@@ -179,7 +179,7 @@ func TestCommandSide_ChangeDefaultOrgIAMPolicy(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
instance.NewInstnaceDomainPolicyAddedEvent(context.Background(),
instance.NewDomainPolicyAddedEvent(context.Background(),
&instance.NewAggregate().Aggregate,
true,
),
@@ -188,7 +188,7 @@ func TestCommandSide_ChangeDefaultOrgIAMPolicy(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newDefaultOrgIAMPolicyChangedEvent(context.Background(), false),
newDefaultDomainPolicyChangedEvent(context.Background(), false),
),
},
),
@@ -230,8 +230,8 @@ func TestCommandSide_ChangeDefaultOrgIAMPolicy(t *testing.T) {
}
}
func newDefaultOrgIAMPolicyChangedEvent(ctx context.Context, userLoginMustBeDomain bool) *instance.InstanceDomainPolicyChangedEvent {
event, _ := instance.NewInstanceDomainPolicyChangedEvent(ctx,
func newDefaultDomainPolicyChangedEvent(ctx context.Context, userLoginMustBeDomain bool) *instance.DomainPolicyChangedEvent {
event, _ := instance.NewDomainPolicyChangedEvent(ctx,
&instance.NewAggregate().Aggregate,
[]policy.OrgPolicyChanges{
policy.ChangeUserLoginMustBeDomain(userLoginMustBeDomain),

View File

@@ -32,19 +32,20 @@ func (c *Commands) checkOrgExists(ctx context.Context, orgID string) error {
return nil
}
func (c *Commands) SetUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.Human, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, claimedUserIDs []string, selfregistered bool) (*domain.ObjectDetails, error) {
orgIAMPolicy, err := c.getDefaultDomainPolicy(ctx)
func (c *Commands) SetUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.Human, initCodeGenerator, phoneCodeGenerator crypto.Generator, claimedUserIDs []string, selfregistered bool) (*domain.ObjectDetails, error) {
domainPolicy, err := c.getDefaultDomainPolicy(ctx)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.IAM.DomainPolicy.NotFound")
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Instance.DomainPolicy.NotFound")
}
pwPolicy, err := c.getDefaultPasswordComplexityPolicy(ctx)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.IAM.PasswordComplexity.NotFound")
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Instance.PasswordComplexity.NotFound")
}
_, orgWriteModel, _, _, events, err := c.setUpOrg(ctx, organisation, admin, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, claimedUserIDs, selfregistered)
_, orgWriteModel, _, _, events, err := c.setUpOrg(ctx, organisation, admin, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, claimedUserIDs, selfregistered)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, events...)
if err != nil {
return nil, err
@@ -201,7 +202,7 @@ func (c *Commands) setUpOrg(
}
func (c *Commands) addOrg(ctx context.Context, organisation *domain.Org, claimedUserIDs []string) (_ *eventstore.Aggregate, _ *OrgWriteModel, _ []eventstore.Command, err error) {
if organisation == nil || !organisation.IsValid() {
if !organisation.IsValid() {
return nil, nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMM-deLSk", "Errors.Org.Invalid")
}
@@ -220,9 +221,8 @@ func (c *Commands) addOrg(ctx context.Context, organisation *domain.Org, claimed
orgDomainEvents, err := c.addOrgDomain(ctx, orgAgg, NewOrgDomainWriteModel(orgAgg.ID, orgDomain.Domain), orgDomain, claimedUserIDs)
if err != nil {
return nil, nil, nil, err
} else {
events = append(events, orgDomainEvents...)
}
events = append(events, orgDomainEvents...)
}
return orgAgg, addedOrg, events, nil
}

View File

@@ -819,7 +819,7 @@ func TestCommandSide_ValidateOrgDomain(t *testing.T) {
),
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org2", "org2").Aggregate,
false))),
expectPush(

View File

@@ -39,7 +39,7 @@ func (c *Commands) addOrgDomainPolicy(ctx context.Context, orgAgg *eventstore.Ag
if addedPolicy.State == domain.PolicyStateActive {
return nil, caos_errs.ThrowAlreadyExists(nil, "ORG-1M8ds", "Errors.Org.DomainPolicy.AlreadyExists")
}
return org.NewOrgDomainPolicyAddedEvent(ctx, orgAgg, policy.UserLoginMustBeDomain), nil
return org.NewDomainPolicyAddedEvent(ctx, orgAgg, policy.UserLoginMustBeDomain), nil
}
func (c *Commands) ChangeOrgDomainPolicy(ctx context.Context, resourceOwner string, policy *domain.DomainPolicy) (*domain.DomainPolicy, error) {
@@ -80,11 +80,11 @@ func (c *Commands) RemoveOrgDomainPolicy(ctx context.Context, orgID string) erro
return err
}
if existingPolicy.State == domain.PolicyStateUnspecified || existingPolicy.State == domain.PolicyStateRemoved {
return caos_errs.ThrowNotFound(nil, "ORG-Dvsh3", "Errors.Org.OrgIAMPolicy.NotFound")
return caos_errs.ThrowNotFound(nil, "ORG-Dvsh3", "Errors.Org.DomainPolicy.NotFound")
}
orgAgg := OrgAggregateFromWriteModel(&existingPolicy.PolicyDomainWriteModel.WriteModel)
_, err = c.eventstore.Push(ctx, org.NewOrgDomainPolicyRemovedEvent(ctx, orgAgg))
_, err = c.eventstore.Push(ctx, org.NewDomainPolicyRemovedEvent(ctx, orgAgg))
return err
}

View File

@@ -27,11 +27,11 @@ func NewOrgDomainPolicyWriteModel(orgID string) *OrgDomainPolicyWriteModel {
func (wm *OrgDomainPolicyWriteModel) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
case *org.OrgDomainPolicyAddedEvent:
case *org.DomainPolicyAddedEvent:
wm.PolicyDomainWriteModel.AppendEvents(&e.DomainPolicyAddedEvent)
case *org.OrgDomainPolicyChangedEvent:
case *org.DomainPolicyChangedEvent:
wm.PolicyDomainWriteModel.AppendEvents(&e.DomainPolicyChangedEvent)
case *org.OrgDomainPolicyRemovedEvent:
case *org.DomainPolicyRemovedEvent:
wm.PolicyDomainWriteModel.AppendEvents(&e.DomainPolicyRemovedEvent)
}
}
@@ -47,16 +47,16 @@ func (wm *OrgDomainPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
AddQuery().
AggregateTypes(org.AggregateType).
AggregateIDs(wm.PolicyDomainWriteModel.AggregateID).
EventTypes(org.OrgDomainPolicyAddedEventType,
org.OrgDomainPolicyChangedEventType,
org.OrgDomainPolicyRemovedEventType).
EventTypes(org.DomainPolicyAddedEventType,
org.DomainPolicyChangedEventType,
org.DomainPolicyRemovedEventType).
Builder()
}
func (wm *OrgDomainPolicyWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
userLoginMustBeDomain bool) (*org.OrgDomainPolicyChangedEvent, bool) {
userLoginMustBeDomain bool) (*org.DomainPolicyChangedEvent, bool) {
changes := make([]policy.OrgPolicyChanges, 0)
if wm.UserLoginMustBeDomain != userLoginMustBeDomain {
changes = append(changes, policy.ChangeUserLoginMustBeDomain(userLoginMustBeDomain))
@@ -64,7 +64,7 @@ func (wm *OrgDomainPolicyWriteModel) NewChangedEvent(
if len(changes) == 0 {
return nil, false
}
changedEvent, err := org.NewOrgDomainPolicyChangedEvent(ctx, aggregate, changes)
changedEvent, err := org.NewDomainPolicyChangedEvent(ctx, aggregate, changes)
if err != nil {
return nil, false
}

View File

@@ -58,7 +58,7 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
@@ -86,7 +86,7 @@ func TestCommandSide_AddDomainPolicy(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
@@ -194,7 +194,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
@@ -220,7 +220,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
@@ -229,7 +229,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
newOrgIAMPolicyChangedEvent(context.Background(), "org1", false),
newDomainPolicyChangedEvent(context.Background(), "org1", false),
),
},
),
@@ -272,7 +272,7 @@ func TestCommandSide_ChangeDomainPolicy(t *testing.T) {
}
}
func TestCommandSide_RemoveOrgIAMPolicy(t *testing.T) {
func TestCommandSide_RemoveDomainPolicy(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
@@ -327,7 +327,7 @@ func TestCommandSide_RemoveOrgIAMPolicy(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
@@ -336,7 +336,7 @@ func TestCommandSide_RemoveOrgIAMPolicy(t *testing.T) {
expectPush(
[]*repository.Event{
eventFromEventPusher(
org.NewOrgDomainPolicyRemovedEvent(context.Background(),
org.NewDomainPolicyRemovedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate),
),
},
@@ -370,8 +370,8 @@ func TestCommandSide_RemoveOrgIAMPolicy(t *testing.T) {
}
}
func newOrgIAMPolicyChangedEvent(ctx context.Context, orgID string, userLoginMustBeDomain bool) *org.OrgDomainPolicyChangedEvent {
event, _ := org.NewOrgDomainPolicyChangedEvent(ctx,
func newDomainPolicyChangedEvent(ctx context.Context, orgID string, userLoginMustBeDomain bool) *org.DomainPolicyChangedEvent {
event, _ := org.NewDomainPolicyChangedEvent(ctx,
&org.NewAggregate(orgID, orgID).Aggregate,
[]policy.OrgPolicyChanges{
policy.ChangeUserLoginMustBeDomain(userLoginMustBeDomain),

View File

@@ -1,11 +1,21 @@
package command
import (
"regexp"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/policy"
)
var (
hasStringLowerCase = regexp.MustCompile(`[a-z]`).MatchString
hasStringUpperCase = regexp.MustCompile(`[A-Z]`).MatchString
hasNumber = regexp.MustCompile(`[0-9]`).MatchString
hasSymbol = regexp.MustCompile(`[^A-Za-z0-9]`).MatchString
)
type PasswordComplexityPolicyWriteModel struct {
eventstore.WriteModel
@@ -49,3 +59,26 @@ func (wm *PasswordComplexityPolicyWriteModel) Reduce() error {
}
return wm.WriteModel.Reduce()
}
func (wm *PasswordComplexityPolicyWriteModel) Validate(password string) error {
if wm.MinLength != 0 && uint64(len(password)) < wm.MinLength {
return errors.ThrowInvalidArgument(nil, "COMMA-HuJf6", "Errors.User.PasswordComplexityPolicy.MinLength")
}
if wm.HasLowercase && !hasStringLowerCase(password) {
return errors.ThrowInvalidArgument(nil, "COMMA-co3Xw", "Errors.User.PasswordComplexityPolicy.HasLower")
}
if wm.HasUppercase && !hasStringUpperCase(password) {
return errors.ThrowInvalidArgument(nil, "COMMA-VoaRj", "Errors.User.PasswordComplexityPolicy.HasUpper")
}
if wm.HasNumber && !hasNumber(password) {
return errors.ThrowInvalidArgument(nil, "COMMA-ZBv4H", "Errors.User.PasswordComplexityPolicy.HasNumber")
}
if wm.HasSymbol && !hasSymbol(password) {
return errors.ThrowInvalidArgument(nil, "COMMA-ZDLwA", "Errors.User.PasswordComplexityPolicy.HasSymbol")
}
return nil
}

View File

@@ -1,329 +0,0 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/v1/models"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
iam_repo "github.com/caos/zitadel/internal/repository/instance"
)
const (
OIDCResponseTypeCode = "CODE"
OIDCResponseTypeIDToken = "ID_TOKEN"
OIDCResponseTypeToken = "ID_TOKEN TOKEN"
OIDCGrantTypeAuthorizationCode = "AUTHORIZATION_CODE"
OIDCGrantTypeImplicit = "IMPLICIT"
OIDCGrantTypeRefreshToken = "REFRESH_TOKEN"
OIDCApplicationTypeNative = "NATIVE"
OIDCApplicationTypeUserAgent = "USER_AGENT"
OIDCApplicationTypeWeb = "WEB"
AuthMethodTypeNone = "NONE"
AuthMethodTypeBasic = "BASIC"
AuthMethodTypePost = "POST"
AuthMethodTypePrivateKeyJWT = "PRIVATE_KEY_JWT"
)
type Step1 struct {
GlobalOrg string
IAMProject string
DefaultLoginPolicy LoginPolicy
Orgs []Org
}
func (s *Step1) Step() domain.Step {
return domain.Step1
}
func (s *Step1) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep1(ctx, s)
}
type LoginPolicy struct {
AllowRegister bool
AllowUsernamePassword bool
AllowExternalIdp bool
}
type User struct {
FirstName string
LastName string
UserName string
Email string
Password string
}
type Org struct {
Name string
Domain string
OrgIamPolicy bool
Owner User
Projects []Project
}
type Project struct {
Name string
Users []User
Members []string
OIDCApps []OIDCApp
APIs []API
}
type OIDCApp struct {
Name string
RedirectUris []string
ResponseTypes []string
GrantTypes []string
ApplicationType string
AuthMethodType string
PostLogoutRedirectUris []string
DevMode bool
}
type API struct {
Name string
AuthMethodType string
}
func (c *Commands) SetupStep1(ctx context.Context, step1 *Step1) error {
var events []eventstore.Command
iamWriteModel := NewInstanceWriteModel()
iamAgg := InstanceAggregateFromWriteModel(&iamWriteModel.WriteModel)
//create default login policy
loginPolicyEvent, err := c.addDefaultLoginPolicy(ctx, iamAgg, NewInstanceLoginPolicyWriteModel(),
&domain.LoginPolicy{
AllowUsernamePassword: step1.DefaultLoginPolicy.AllowUsernamePassword,
AllowRegister: step1.DefaultLoginPolicy.AllowRegister,
AllowExternalIDP: step1.DefaultLoginPolicy.AllowExternalIdp,
})
if err != nil {
return err
}
events = append(events, loginPolicyEvent)
logging.Log("SETUP-sd2hj").Info("default login policy set up")
//create orgs
for _, organisation := range step1.Orgs {
orgIAMPolicy := &domain.DomainPolicy{UserLoginMustBeDomain: true}
if organisation.OrgIamPolicy {
orgIAMPolicy.UserLoginMustBeDomain = false
}
pwPolicy := &domain.PasswordComplexityPolicy{
MinLength: 1,
}
orgAgg, _, humanWriteModel, _, setUpOrgEvents, err := c.setUpOrg(ctx,
&domain.Org{
Name: organisation.Name,
Domains: []*domain.OrgDomain{{Domain: organisation.Domain}},
},
&domain.Human{
Username: organisation.Owner.UserName,
Profile: &domain.Profile{
FirstName: organisation.Owner.FirstName,
LastName: organisation.Owner.LastName,
},
Password: &domain.Password{
SecretString: organisation.Owner.Password,
},
Email: &domain.Email{
EmailAddress: organisation.Owner.Email,
IsEmailVerified: true,
},
}, orgIAMPolicy, pwPolicy,
nil, //TODO: Code Generator missing! Should be setuped in step1 create iam
nil, //TODO: Code Generator missing! Should be setuped in step1 create iam
nil, false)
if err != nil {
return err
}
events = append(events, setUpOrgEvents...)
logging.LogWithFields("SETUP-Gdsfg", "id", orgAgg.ID, "name", organisation.Name).Info("org set up")
if organisation.OrgIamPolicy {
orgIAMPolicyEvent, err := c.addOrgDomainPolicy(ctx, orgAgg, NewOrgDomainPolicyWriteModel(orgAgg.ID), orgIAMPolicy)
if err != nil {
return err
}
events = append(events, orgIAMPolicyEvent)
}
if organisation.Name == step1.GlobalOrg {
globalOrgEvent, err := c.setGlobalOrg(ctx, iamAgg, iamWriteModel, orgAgg.ID)
if err != nil {
return err
}
events = append(events, globalOrgEvent)
logging.Log("SETUP-BDn52").Info("global org set")
}
//projects
for _, proj := range organisation.Projects {
project := &domain.Project{Name: proj.Name}
projectEvents, projectWriteModel, err := c.addProject(ctx, project, orgAgg.ID, humanWriteModel.AggregateID)
if err != nil {
return err
}
events = append(events, projectEvents...)
if project.Name == step1.IAMProject {
iamProjectEvent, err := c.setIAMProject(ctx, iamAgg, iamWriteModel, projectWriteModel.AggregateID)
if err != nil {
return err
}
events = append(events, iamProjectEvent)
logging.Log("SETUP-Bdfs1").Info("IAM project set")
iamEvent, err := c.addInstanceMember(ctx, iamAgg, NewInstanceMemberWriteModel(humanWriteModel.AggregateID), domain.NewMember(iamAgg.ID, humanWriteModel.AggregateID, domain.RoleIAMOwner))
if err != nil {
return err
}
events = append(events, iamEvent)
logging.Log("SETUP-BSf2h").Info("IAM owner set")
}
//create applications
for _, app := range proj.OIDCApps {
//TODO: Add Secret Generator
applicationEvents, err := setUpOIDCApplication(ctx, c, projectWriteModel, project, app, orgAgg.ID, nil)
if err != nil {
return err
}
events = append(events, applicationEvents...)
}
for _, app := range proj.APIs {
//TODO: Add Secret Generator
applicationEvents, err := setUpAPI(ctx, c, projectWriteModel, project, app, orgAgg.ID, nil)
if err != nil {
return err
}
events = append(events, applicationEvents...)
}
}
}
events = append(events, iam_repo.NewSetupStepDoneEvent(ctx, iamAgg, domain.Step1))
_, err = c.eventstore.Push(ctx, events...)
if err != nil {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-Gr2hh", "Setup Step1 failed")
}
return nil
}
func setUpOIDCApplication(ctx context.Context, r *Commands, projectWriteModel *ProjectWriteModel, project *domain.Project, oidcApp OIDCApp, resourceOwner string, appSecretGenerator crypto.Generator) ([]eventstore.Command, error) {
app := &domain.OIDCApp{
ObjectRoot: models.ObjectRoot{
AggregateID: projectWriteModel.AggregateID,
},
AppName: oidcApp.Name,
RedirectUris: oidcApp.RedirectUris,
ResponseTypes: getOIDCResponseTypes(oidcApp.ResponseTypes),
GrantTypes: getOIDCGrantTypes(oidcApp.GrantTypes),
ApplicationType: getOIDCApplicationType(oidcApp.ApplicationType),
AuthMethodType: getOIDCAuthMethod(oidcApp.AuthMethodType),
DevMode: oidcApp.DevMode,
}
projectAgg := ProjectAggregateFromWriteModel(&projectWriteModel.WriteModel)
events, _, err := r.addOIDCApplication(ctx, projectAgg, project, app, resourceOwner, appSecretGenerator)
if err != nil {
return nil, err
}
logging.LogWithFields("SETUP-Edgw4", "name", app.AppName, "clientID", app.ClientID).Info("application set up")
return events, nil
}
func setUpAPI(ctx context.Context, r *Commands, projectWriteModel *ProjectWriteModel, project *domain.Project, apiApp API, resourceOwner string, appSecretGenerator crypto.Generator) ([]eventstore.Command, error) {
app := &domain.APIApp{
ObjectRoot: models.ObjectRoot{
AggregateID: projectWriteModel.AggregateID,
},
AppName: apiApp.Name,
AuthMethodType: getAPIAuthMethod(apiApp.AuthMethodType),
}
projectAgg := ProjectAggregateFromWriteModel(&projectWriteModel.WriteModel)
events, _, err := r.addAPIApplication(ctx, projectAgg, project, app, resourceOwner, appSecretGenerator)
if err != nil {
return nil, err
}
logging.LogWithFields("SETUP-Dbgsf", "name", app.AppName, "clientID", app.ClientID).Info("application set up")
return events, nil
}
func getOIDCResponseTypes(responseTypes []string) []domain.OIDCResponseType {
types := make([]domain.OIDCResponseType, len(responseTypes))
for i, t := range responseTypes {
types[i] = getOIDCResponseType(t)
}
return types
}
func getOIDCResponseType(responseType string) domain.OIDCResponseType {
switch responseType {
case OIDCResponseTypeCode:
return domain.OIDCResponseTypeCode
case OIDCResponseTypeIDToken:
return domain.OIDCResponseTypeIDToken
case OIDCResponseTypeToken:
return domain.OIDCResponseTypeIDTokenToken
}
return domain.OIDCResponseTypeCode
}
func getOIDCGrantTypes(grantTypes []string) []domain.OIDCGrantType {
types := make([]domain.OIDCGrantType, len(grantTypes))
for i, t := range grantTypes {
types[i] = getOIDCGrantType(t)
}
return types
}
func getOIDCGrantType(grantTypes string) domain.OIDCGrantType {
switch grantTypes {
case OIDCGrantTypeAuthorizationCode:
return domain.OIDCGrantTypeAuthorizationCode
case OIDCGrantTypeImplicit:
return domain.OIDCGrantTypeImplicit
case OIDCGrantTypeRefreshToken:
return domain.OIDCGrantTypeRefreshToken
}
return domain.OIDCGrantTypeAuthorizationCode
}
func getOIDCApplicationType(appType string) domain.OIDCApplicationType {
switch appType {
case OIDCApplicationTypeNative:
return domain.OIDCApplicationTypeNative
case OIDCApplicationTypeUserAgent:
return domain.OIDCApplicationTypeUserAgent
case OIDCApplicationTypeWeb:
return domain.OIDCApplicationTypeWeb
}
return domain.OIDCApplicationTypeWeb
}
func getOIDCAuthMethod(authMethod string) domain.OIDCAuthMethodType {
switch authMethod {
case AuthMethodTypeNone:
return domain.OIDCAuthMethodTypeNone
case AuthMethodTypeBasic:
return domain.OIDCAuthMethodTypeBasic
case AuthMethodTypePost:
return domain.OIDCAuthMethodTypePost
case AuthMethodTypePrivateKeyJWT:
return domain.OIDCAuthMethodTypePrivateKeyJWT
}
return domain.OIDCAuthMethodTypeBasic
}
func getAPIAuthMethod(authMethod string) domain.APIAuthMethodType {
switch authMethod {
case AuthMethodTypeBasic:
return domain.APIAuthMethodTypeBasic
case AuthMethodTypePrivateKeyJWT:
return domain.APIAuthMethodTypePrivateKeyJWT
}
return domain.APIAuthMethodTypePrivateKeyJWT
}

View File

@@ -1,38 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step10 struct {
DefaultMailTemplate domain.MailTemplate
}
func (s *Step10) Step() domain.Step {
return domain.Step10
}
func (s *Step10) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep10(ctx, s)
}
func (c *Commands) SetupStep10(ctx context.Context, step *Step10) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel)
mailTemplateEvent, err := c.addDefaultMailTemplate(ctx, iamAgg, NewInstanceMailTemplateWriteModel(), &step.DefaultMailTemplate)
if err != nil {
return nil, err
}
events := []eventstore.Command{
mailTemplateEvent,
}
logging.Log("SETUP-3N9fs").Info("default mail template/text set up")
return events, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,40 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
iam_repo "github.com/caos/zitadel/internal/repository/instance"
)
type Step11 struct {
MigrateV1EventstoreToV2 bool
}
func (s *Step11) Step() domain.Step {
return domain.Step11
}
func (s *Step11) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep11(ctx, s)
}
func (c *Commands) SetupStep11(ctx context.Context, step *Step11) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel)
var uniqueContraintMigrations []*domain.UniqueConstraintMigration
if step.MigrateV1EventstoreToV2 {
uniqueConstraints := NewUniqueConstraintReadModel(ctx, c)
err := c.eventstore.FilterToQueryReducer(ctx, uniqueConstraints)
if err != nil {
return nil, err
}
uniqueContraintMigrations = uniqueConstraints.UniqueConstraints
}
logging.Log("SETUP-M9fsd").Info("migrate v1 eventstore to v2")
return []eventstore.Command{iam_repo.NewMigrateUniqueConstraintEvent(ctx, iamAgg, uniqueContraintMigrations)}, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,56 +0,0 @@
package command
import (
"context"
"time"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step12 struct {
TierName string
TierDescription string
AuditLogRetention time.Duration
LoginPolicyFactors bool
LoginPolicyIDP bool
LoginPolicyPasswordless bool
LoginPolicyRegistration bool
LoginPolicyUsernameLogin bool
PasswordComplexityPolicy bool
LabelPolicy bool
CustomDomain bool
}
func (s *Step12) Step() domain.Step {
return domain.Step12
}
func (s *Step12) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep12(ctx, s)
}
func (c *Commands) SetupStep12(ctx context.Context, step *Step12) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
featuresWriteModel := NewInstanceFeaturesWriteModel()
featuresEvent, err := c.setDefaultFeatures(ctx, featuresWriteModel, &domain.Features{
TierName: step.TierName,
TierDescription: step.TierDescription,
State: domain.FeaturesStateActive,
AuditLogRetention: step.AuditLogRetention,
LoginPolicyFactors: step.LoginPolicyFactors,
LoginPolicyIDP: step.LoginPolicyIDP,
LoginPolicyPasswordless: step.LoginPolicyPasswordless,
LoginPolicyRegistration: step.LoginPolicyRegistration,
LoginPolicyUsernameLogin: step.LoginPolicyUsernameLogin,
PasswordComplexityPolicy: step.PasswordComplexityPolicy,
LabelPolicyPrivateLabel: step.LabelPolicy,
CustomDomain: step.CustomDomain,
})
if err != nil {
return nil, err
}
return []eventstore.Command{featuresEvent}, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,33 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step13 struct {
DefaultMailTemplate domain.MailTemplate
}
func (s *Step13) Step() domain.Step {
return domain.Step13
}
func (s *Step13) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep13(ctx, s)
}
func (c *Commands) SetupStep13(ctx context.Context, step *Step13) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
_, mailTemplateEvent, err := c.changeDefaultMailTemplate(ctx, &step.DefaultMailTemplate)
if err != nil {
return nil, err
}
logging.Log("SETUP-4insR").Info("default mail template/text set up")
return []eventstore.Command{mailTemplateEvent}, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,48 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
iam_repo "github.com/caos/zitadel/internal/repository/instance"
org_repo "github.com/caos/zitadel/internal/repository/org"
)
type Step14 struct {
ActivateExistingLabelPolicies bool
}
func (s *Step14) Step() domain.Step {
return domain.Step14
}
func (s *Step14) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep14(ctx, s)
}
func (c *Commands) SetupStep14(ctx context.Context, step *Step14) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel)
var events []eventstore.Command
if step.ActivateExistingLabelPolicies {
existingPolicies := NewExistingLabelPoliciesReadModel(ctx)
err := c.eventstore.FilterToQueryReducer(ctx, existingPolicies)
if err != nil {
return nil, err
}
for _, aggID := range existingPolicies.aggregateIDs {
if iamAgg.ID == aggID {
events = append(events, iam_repo.NewLabelPolicyActivatedEvent(ctx, iamAgg))
continue
}
events = append(events, org_repo.NewLabelPolicyActivatedEvent(ctx, &org_repo.NewAggregate(aggID, aggID).Aggregate))
}
}
logging.Log("SETUP-M9fsd").Info("activate login policies")
return events, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,33 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step15 struct {
DefaultMailTemplate domain.MailTemplate
}
func (s *Step15) Step() domain.Step {
return domain.Step15
}
func (s *Step15) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep15(ctx, s)
}
func (c *Commands) SetupStep15(ctx context.Context, step *Step15) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
_, mailTemplateEvent, err := c.changeDefaultMailTemplate(ctx, &step.DefaultMailTemplate)
if err != nil {
return nil, err
}
logging.Log("SETUP-2nfsd").Info("default mail template/text set up")
return []eventstore.Command{mailTemplateEvent}, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,40 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step16 struct {
DefaultMessageTexts []domain.CustomMessageText
}
func (s *Step16) Step() domain.Step {
return domain.Step16
}
func (s *Step16) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep16(ctx, s)
}
func (c *Commands) SetupStep16(ctx context.Context, step *Step16) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel)
events := make([]eventstore.Command, 0)
for _, text := range step.DefaultMessageTexts {
mailEvents, _, err := c.setDefaultMessageText(ctx, iamAgg, &text)
if err != nil {
return nil, err
}
events = append(events, mailEvents...)
}
logging.Log("SETUP-4k0LL").Info("default message text set up")
return events, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,36 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step17 struct {
PrivacyPolicy domain.PrivacyPolicy
}
func (s *Step17) Step() domain.Step {
return domain.Step17
}
func (s *Step17) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep17(ctx, s)
}
func (c *Commands) SetupStep17(ctx context.Context, step *Step17) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel)
addedPolicy := NewInstancePrivacyPolicyWriteModel()
events, err := c.addDefaultPrivacyPolicy(ctx, iamAgg, addedPolicy, &step.PrivacyPolicy)
if err != nil {
return nil, err
}
logging.Log("SETUP-N9sq2").Info("default privacy policy set up")
return []eventstore.Command{events}, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,36 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step18 struct {
LockoutPolicy domain.LockoutPolicy
}
func (s *Step18) Step() domain.Step {
return domain.Step18
}
func (s *Step18) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep18(ctx, s)
}
func (c *Commands) SetupStep18(ctx context.Context, step *Step18) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel)
addedPolicy := NewInstanceLockoutPolicyWriteModel()
events, err := c.addDefaultLockoutPolicy(ctx, iamAgg, addedPolicy, &step.LockoutPolicy)
if err != nil {
return nil, err
}
logging.Log("SETUP-3m99ds").Info("default lockout policy set up")
return []eventstore.Command{events}, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,212 +0,0 @@
package command
import (
"context"
"fmt"
"strings"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/user"
)
type Step19 struct{}
func (s *Step19) Step() domain.Step {
return domain.Step19
}
func (s *Step19) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep19(ctx, s)
}
func (c *Commands) SetupStep19(ctx context.Context, step *Step19) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
events := make([]eventstore.Command, 0)
orgs := newOrgsWithUsernameNotDomain()
if err := c.eventstore.FilterToQueryReducer(ctx, orgs); err != nil {
return nil, err
}
for orgID, usernameCheck := range orgs.orgs {
if !usernameCheck {
continue
}
users := newDomainClaimedUsernames(orgID)
if err := c.eventstore.FilterToQueryReducer(ctx, users); err != nil {
return nil, err
}
for userID, username := range users.users {
split := strings.Split(username, "@")
if len(split) != 2 {
continue
}
domainVerified := NewOrgDomainVerifiedWriteModel(split[1])
if err := c.eventstore.FilterToQueryReducer(ctx, domainVerified); err != nil {
return nil, err
}
if domainVerified.Verified && domainVerified.ResourceOwner != orgID {
id, err := c.idGenerator.Next()
if err != nil {
return nil, err
}
events = append(events, user.NewDomainClaimedEvent(
ctx,
&user.NewAggregate(userID, orgID).Aggregate,
fmt.Sprintf("%s@temporary.%s", id, c.iamDomain),
username,
false))
}
}
}
if length := len(events); length > 0 {
logging.Log("SETUP-dFG2t").WithField("count", length).Info("domain claimed events created")
}
return events, nil
}
return c.setup(ctx, step, fn)
}
func newOrgsWithUsernameNotDomain() *orgsWithUsernameNotDomain {
return &orgsWithUsernameNotDomain{
orgEvents: make(map[string][]eventstore.Event),
orgs: make(map[string]bool),
}
}
type orgsWithUsernameNotDomain struct {
eventstore.WriteModel
orgEvents map[string][]eventstore.Event
orgs map[string]bool
}
func (wm *orgsWithUsernameNotDomain) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
case *org.OrgAddedEvent:
wm.orgEvents[e.Aggregate().ID] = append(wm.orgEvents[e.Aggregate().ID], e)
case *org.OrgRemovedEvent:
delete(wm.orgEvents, e.Aggregate().ID)
case *org.OrgDomainPolicyAddedEvent:
wm.orgEvents[e.Aggregate().ID] = append(wm.orgEvents[e.Aggregate().ID], e)
case *org.OrgDomainPolicyChangedEvent:
if e.UserLoginMustBeDomain == nil {
continue
}
wm.orgEvents[e.Aggregate().ID] = append(wm.orgEvents[e.Aggregate().ID], e)
case *org.OrgDomainPolicyRemovedEvent:
delete(wm.orgEvents, e.Aggregate().ID)
}
}
}
func (wm *orgsWithUsernameNotDomain) Reduce() error {
for _, events := range wm.orgEvents {
for _, event := range events {
switch e := event.(type) {
case *org.OrgDomainPolicyAddedEvent:
if !e.UserLoginMustBeDomain {
wm.orgs[e.Aggregate().ID] = true
}
case *org.OrgDomainPolicyChangedEvent:
if !*e.UserLoginMustBeDomain {
wm.orgs[e.Aggregate().ID] = true
}
delete(wm.orgs, e.Aggregate().ID)
}
}
}
return nil
}
func (wm *orgsWithUsernameNotDomain) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery().
AggregateTypes(org.AggregateType).
EventTypes(
org.OrgAddedEventType,
org.OrgRemovedEventType,
org.OrgDomainPolicyAddedEventType,
org.OrgDomainPolicyChangedEventType,
org.OrgDomainPolicyRemovedEventType).
Builder()
}
func newDomainClaimedUsernames(orgID string) *domainClaimedUsernames {
return &domainClaimedUsernames{
WriteModel: eventstore.WriteModel{
ResourceOwner: orgID,
},
userEvents: make(map[string][]eventstore.Event),
users: make(map[string]string),
}
}
type domainClaimedUsernames struct {
eventstore.WriteModel
userEvents map[string][]eventstore.Event
users map[string]string
}
func (wm *domainClaimedUsernames) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
case *user.HumanAddedEvent:
if !strings.Contains(e.UserName, "@") {
continue
}
wm.userEvents[e.Aggregate().ID] = append(wm.userEvents[e.Aggregate().ID], e)
case *user.HumanRegisteredEvent:
if !strings.Contains(e.UserName, "@") {
continue
}
wm.userEvents[e.Aggregate().ID] = append(wm.userEvents[e.Aggregate().ID], e)
case *user.UsernameChangedEvent:
if !strings.Contains(e.UserName, "@") {
delete(wm.userEvents, e.Aggregate().ID)
continue
}
wm.userEvents[e.Aggregate().ID] = append(wm.userEvents[e.Aggregate().ID], e)
case *user.DomainClaimedEvent:
delete(wm.userEvents, e.Aggregate().ID)
case *user.UserRemovedEvent:
delete(wm.userEvents, e.Aggregate().ID)
}
}
}
func (wm *domainClaimedUsernames) Reduce() error {
for _, events := range wm.userEvents {
for _, event := range events {
switch e := event.(type) {
case *user.HumanAddedEvent:
wm.users[e.Aggregate().ID] = e.UserName
case *user.HumanRegisteredEvent:
wm.users[e.Aggregate().ID] = e.UserName
case *user.UsernameChangedEvent:
wm.users[e.Aggregate().ID] = e.UserName
}
}
}
return nil
}
func (wm *domainClaimedUsernames) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(user.AggregateType).
EventTypes(
user.UserV1AddedType,
user.UserV1RegisteredType,
user.HumanAddedType,
user.HumanRegisteredType,
user.UserUserNameChangedType,
user.UserDomainClaimedType,
user.UserRemovedType).
Builder()
}

View File

@@ -1,42 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
)
type Step2 struct {
DefaultPasswordComplexityPolicy domain.PasswordComplexityPolicy
}
func (s *Step2) Step() domain.Step {
return domain.Step2
}
func (s *Step2) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep2(ctx, s)
}
func (c *Commands) SetupStep2(ctx context.Context, step *Step2) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel)
event, err := c.addDefaultPasswordComplexityPolicy(ctx, iamAgg, NewInstancePasswordComplexityPolicyWriteModel(), &domain.PasswordComplexityPolicy{
MinLength: step.DefaultPasswordComplexityPolicy.MinLength,
HasLowercase: step.DefaultPasswordComplexityPolicy.HasLowercase,
HasUppercase: step.DefaultPasswordComplexityPolicy.HasUppercase,
HasNumber: step.DefaultPasswordComplexityPolicy.HasNumber,
HasSymbol: step.DefaultPasswordComplexityPolicy.HasSymbol,
})
if err != nil {
return nil, err
}
logging.Log("SETUP-ADgd2").Info("default password complexity policy set up")
return []eventstore.Command{event}, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,26 +0,0 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
)
type Step20 struct{}
func (s *Step20) Step() domain.Step {
return domain.Step20
}
func (s *Step20) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep20(ctx, s)
}
func (c *Commands) SetupStep20(ctx context.Context, step *Step20) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
err := c.eventstore.Step20(ctx, iam.Events[len(iam.Events)-1].Sequence())
return nil, err
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,105 +0,0 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/org"
"github.com/caos/zitadel/internal/repository/user"
)
type Step21 struct{}
func (s *Step21) Step() domain.Step {
return domain.Step21
}
func (s *Step21) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep21(ctx, s)
}
func (c *Commands) SetupStep21(ctx context.Context, step *Step21) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
events := make([]eventstore.Command, 0)
globalMembers := newGlobalOrgMemberWriteModel(iam.GlobalOrgID, domain.RoleOrgProjectCreator)
orgAgg := OrgAggregateFromWriteModel(&globalMembers.WriteModel)
if err := c.eventstore.FilterToQueryReducer(ctx, globalMembers); err != nil {
return nil, err
}
for userID, roles := range globalMembers.members {
for i, role := range roles {
if role == domain.RoleOrgProjectCreator {
roles[i] = domain.RoleSelfManagementGlobal
}
}
events = append(events, org.NewMemberChangedEvent(ctx, orgAgg, userID, roles...))
}
return events, nil
}
return c.setup(ctx, step, fn)
}
type globalOrgMembersWriteModel struct {
eventstore.WriteModel
role string
members map[string][]string
}
func newGlobalOrgMemberWriteModel(orgID, role string) *globalOrgMembersWriteModel {
return &globalOrgMembersWriteModel{
WriteModel: eventstore.WriteModel{
ResourceOwner: orgID,
AggregateID: orgID,
},
role: role,
members: make(map[string][]string),
}
}
func (wm *globalOrgMembersWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *org.MemberAddedEvent:
for _, role := range e.Roles {
if wm.role == role {
wm.members[e.UserID] = e.Roles
break
}
}
case *org.MemberChangedEvent:
delete(wm.members, e.UserID)
for _, role := range e.Roles {
if wm.role == role {
wm.members[e.UserID] = e.Roles
break
}
}
case *org.MemberRemovedEvent:
delete(wm.members, e.UserID)
case *org.MemberCascadeRemovedEvent:
delete(wm.members, e.UserID)
case *user.UserRemovedEvent:
delete(wm.members, e.Aggregate().ID)
}
}
return wm.WriteModel.Reduce()
}
func (wm *globalOrgMembersWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery().
AggregateTypes(org.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
org.MemberAddedEventType,
org.MemberChangedEventType,
org.MemberRemovedEventType,
org.MemberCascadeRemovedEventType,
).
Or().
AggregateTypes(user.AggregateType).
EventTypes(user.UserRemovedType).
Builder()
}

View File

@@ -1,39 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
)
type Step3 struct {
DefaultPasswordAgePolicy domain.PasswordAgePolicy
}
func (s *Step3) Step() domain.Step {
return domain.Step3
}
func (s *Step3) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep3(ctx, s)
}
func (c *Commands) SetupStep3(ctx context.Context, step *Step3) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel)
event, err := c.addDefaultPasswordAgePolicy(ctx, iamAgg, NewInstancePasswordAgePolicyWriteModel(), &domain.PasswordAgePolicy{
MaxAgeDays: step.DefaultPasswordAgePolicy.MaxAgeDays,
ExpireWarnDays: step.DefaultPasswordAgePolicy.ExpireWarnDays,
})
if err != nil {
return nil, err
}
logging.Log("SETUP-DBqgq").Info("default password age policy set up")
return []eventstore.Command{event}, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,31 +0,0 @@
package command
import (
"context"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
)
type Step4 struct {
DefaultPasswordLockoutPolicy domain.LockoutPolicy
}
func (s *Step4) Step() domain.Step {
return domain.Step4
}
func (s *Step4) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep4(ctx, s)
}
//This step should not be executed when a new instance is setup, because its not used anymore
//SetupStep4 is no op in favour of step 18.
//Password lockout policy is replaced by lockout policy
func (c *Commands) SetupStep4(ctx context.Context, step *Step4) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
return nil, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,38 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
)
type Step5 struct {
DefaultOrgIAMPolicy domain.DomainPolicy
}
func (s *Step5) Step() domain.Step {
return domain.Step5
}
func (s *Step5) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep5(ctx, s)
}
func (c *Commands) SetupStep5(ctx context.Context, step *Step5) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel)
event, err := c.addDefaultDomainPolicy(ctx, iamAgg, NewInstanceDomainPolicyWriteModel(), &domain.DomainPolicy{
UserLoginMustBeDomain: step.DefaultOrgIAMPolicy.UserLoginMustBeDomain,
})
if err != nil {
return nil, err
}
logging.Log("SETUP-ADgd2").Info("default org iam policy set up")
return []eventstore.Command{event}, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,36 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
)
type Step6 struct {
DefaultLabelPolicy domain.LabelPolicy
}
func (s *Step6) Step() domain.Step {
return domain.Step6
}
func (s *Step6) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep6(ctx, s)
}
func (c *Commands) SetupStep6(ctx context.Context, step *Step6) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
iamAgg := InstanceAggregateFromWriteModel(&iam.WriteModel)
event, err := c.addDefaultLabelPolicy(ctx, iamAgg, NewInstanceLabelPolicyWriteModel(), &step.DefaultLabelPolicy)
if err != nil {
return nil, err
}
logging.Log("SETUP-ADgd2").Info("default label policy set up")
return []eventstore.Command{event}, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,39 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
)
type Step7 struct {
OTP bool
}
func (s *Step7) Step() domain.Step {
return domain.Step7
}
func (s *Step7) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep7(ctx, s)
}
func (c *Commands) SetupStep7(ctx context.Context, step *Step7) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
secondFactorModel := NewInstanceSecondFactorWriteModel(domain.SecondFactorTypeOTP)
iamAgg := InstanceAggregateFromWriteModel(&secondFactorModel.SecondFactorWriteModel.WriteModel)
if !step.OTP {
return []eventstore.Command{}, nil
}
event, err := c.addSecondFactorToDefaultLoginPolicy(ctx, iamAgg, secondFactorModel, domain.SecondFactorTypeOTP)
if err != nil {
return nil, err
}
logging.Log("SETUP-Dggsg").Info("added OTP to 2FA login policy")
return []eventstore.Command{event}, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,39 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
)
type Step8 struct {
U2F bool
}
func (s *Step8) Step() domain.Step {
return domain.Step8
}
func (s *Step8) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep8(ctx, s)
}
func (c *Commands) SetupStep8(ctx context.Context, step *Step8) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
secondFactorModel := NewInstanceSecondFactorWriteModel(domain.SecondFactorTypeU2F)
iamAgg := InstanceAggregateFromWriteModel(&secondFactorModel.SecondFactorWriteModel.WriteModel)
if !step.U2F {
return []eventstore.Command{}, nil
}
event, err := c.addSecondFactorToDefaultLoginPolicy(ctx, iamAgg, secondFactorModel, domain.SecondFactorTypeU2F)
if err != nil {
return nil, err
}
logging.Log("SETUP-BDhne").Info("added U2F to 2FA login policy")
return []eventstore.Command{event}, nil
}
return c.setup(ctx, step, fn)
}

View File

@@ -1,53 +0,0 @@
package command
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/domain"
)
type Step9 struct {
Passwordless bool
}
func (s *Step9) Step() domain.Step {
return domain.Step9
}
func (s *Step9) execute(ctx context.Context, commandSide *Commands) error {
return commandSide.SetupStep9(ctx, s)
}
func (c *Commands) SetupStep9(ctx context.Context, step *Step9) error {
fn := func(iam *InstanceWriteModel) ([]eventstore.Command, error) {
multiFactorModel := NewInstanceMultiFactorWriteModel(domain.MultiFactorTypeU2FWithPIN)
iamAgg := InstanceAggregateFromWriteModel(&multiFactorModel.MultiFactorWriteModel.WriteModel)
if !step.Passwordless {
return []eventstore.Command{}, nil
}
passwordlessEvent, err := setPasswordlessAllowedInPolicy(ctx, c, iamAgg)
if err != nil {
return nil, err
}
logging.Log("SETUP-AEG2t").Info("allowed passwordless in login policy")
multifactorEvent, err := c.addMultiFactorToDefaultLoginPolicy(ctx, iamAgg, multiFactorModel, domain.MultiFactorTypeU2FWithPIN)
if err != nil {
return nil, err
}
logging.Log("SETUP-ADfng").Info("added passwordless to MFA login policy")
return []eventstore.Command{passwordlessEvent, multifactorEvent}, nil
}
return c.setup(ctx, step, fn)
}
func setPasswordlessAllowedInPolicy(ctx context.Context, c *Commands, iamAgg *eventstore.Aggregate) (eventstore.Command, error) {
policy, err := c.getDefaultLoginPolicy(ctx)
if err != nil {
return nil, err
}
policy.PasswordlessType = domain.PasswordlessTypeAllowed
return c.changeDefaultLoginPolicy(ctx, iamAgg, NewInstanceLoginPolicyWriteModel(), policy)
}

View File

@@ -36,18 +36,18 @@ func (c *Commands) ChangeUsername(ctx context.Context, orgID, userID, userName s
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6m9gs", "Errors.User.UsernameNotChanged")
}
orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-38fnu", "Errors.Org.DomainPolicy.NotExisting")
}
if err := CheckDomainPolicyForUserName(userName, orgIAMPolicy); err != nil {
if err := CheckDomainPolicyForUserName(userName, domainPolicy); err != nil {
return nil, err
}
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx,
user.NewUsernameChangedEvent(ctx, userAgg, existingUser.UserName, userName, orgIAMPolicy.UserLoginMustBeDomain))
user.NewUsernameChangedEvent(ctx, userAgg, existingUser.UserName, userName, domainPolicy.UserLoginMustBeDomain))
if err != nil {
return nil, err
}
@@ -186,13 +186,13 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string,
return nil, caos_errs.ThrowNotFound(nil, "COMMAND-m9od", "Errors.User.NotFound")
}
orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, existingUser.ResourceOwner)
domainPolicy, err := c.getOrgDomainPolicy(ctx, existingUser.ResourceOwner)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.OrgIAMPolicy.NotExisting")
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotExisting")
}
var events []eventstore.Command
userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel)
events = append(events, user.NewUserRemovedEvent(ctx, userAgg, existingUser.UserName, existingUser.IDPLinks, orgIAMPolicy.UserLoginMustBeDomain))
events = append(events, user.NewUserRemovedEvent(ctx, userAgg, existingUser.UserName, existingUser.IDPLinks, domainPolicy.UserLoginMustBeDomain))
for _, grantID := range cascadingGrantIDs {
removeEvent, _, err := c.removeUserGrant(ctx, grantID, "", true)
@@ -320,7 +320,7 @@ func (c *Commands) userDomainClaimed(ctx context.Context, userID string) (events
changedUserGrant := NewUserWriteModel(userID, existingUser.ResourceOwner)
userAgg := UserAggregateFromWriteModel(&changedUserGrant.WriteModel)
orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, existingUser.ResourceOwner)
domainPolicy, err := c.getOrgDomainPolicy(ctx, existingUser.ResourceOwner)
if err != nil {
return nil, nil, err
}
@@ -335,7 +335,7 @@ func (c *Commands) userDomainClaimed(ctx context.Context, userID string) (events
userAgg,
fmt.Sprintf("%s@temporary.%s", id, c.iamDomain),
existingUser.UserName,
orgIAMPolicy.UserLoginMustBeDomain),
domainPolicy.UserLoginMustBeDomain),
}, changedUserGrant, nil
}

View File

@@ -28,7 +28,7 @@ func (c *Commands) AddHuman(ctx context.Context, orgID string, human *domain.Hum
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-XYFk9", "Errors.ResourceOwnerMissing")
}
orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Org.DomainPolicy.NotFound")
}
@@ -36,7 +36,7 @@ func (c *Commands) AddHuman(ctx context.Context, orgID string, human *domain.Hum
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-M5Fsd", "Errors.Org.PasswordComplexityPolicy.NotFound")
}
events, addedHuman, err := c.addHuman(ctx, orgID, human, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
events, addedHuman, err := c.addHuman(ctx, orgID, human, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
if err != nil {
return nil, err
}
@@ -57,7 +57,7 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
if orgID == "" {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5N8fs", "Errors.ResourceOwnerMissing")
}
orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
if err != nil {
return nil, nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-2N9fs", "Errors.Org.DomainPolicy.NotFound")
}
@@ -65,7 +65,7 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
if err != nil {
return nil, nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-4N8gs", "Errors.Org.PasswordComplexityPolicy.NotFound")
}
events, addedHuman, addedCode, code, err := c.importHuman(ctx, orgID, human, passwordless, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator)
events, addedHuman, addedCode, code, err := c.importHuman(ctx, orgID, human, passwordless, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator)
if err != nil {
return nil, nil, err
}
@@ -89,21 +89,21 @@ func (c *Commands) ImportHuman(ctx context.Context, orgID string, human *domain.
return writeModelToHuman(addedHuman), passwordlessCode, nil
}
func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human, orgIAMPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
if orgID == "" || !human.IsValid() {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-67Ms8", "Errors.User.Invalid")
}
if human.Password != nil && human.SecretString != "" {
human.ChangeRequired = true
}
return c.createHuman(ctx, orgID, human, nil, false, false, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
return c.createHuman(ctx, orgID, human, nil, false, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
}
func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, orgIAMPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) {
func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) {
if orgID == "" || !human.IsValid() {
return nil, nil, nil, "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.User.Invalid")
}
events, humanWriteModel, err = c.createHuman(ctx, orgID, human, nil, false, passwordless, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
events, humanWriteModel, err = c.createHuman(ctx, orgID, human, nil, false, passwordless, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
if err != nil {
return nil, nil, nil, "", err
}
@@ -122,7 +122,7 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai
if orgID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-GEdf2", "Errors.ResourceOwnerMissing")
}
orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-33M9f", "Errors.Org.DomainPolicy.NotFound")
}
@@ -137,7 +137,7 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai
if !loginPolicy.AllowRegister {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-SAbr3", "Errors.Org.LoginPolicy.RegistrationNotAllowed")
}
userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, link, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
userEvents, registeredHuman, err := c.registerHuman(ctx, orgID, human, link, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
if err != nil {
return nil, err
}
@@ -171,7 +171,7 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai
return writeModelToHuman(registeredHuman), nil
}
func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, orgIAMPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
if human != nil && human.Username == "" {
human.Username = human.EmailAddress
}
@@ -181,14 +181,14 @@ func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domai
if human.Password != nil && human.SecretString != "" {
human.ChangeRequired = false
}
return c.createHuman(ctx, orgID, human, link, true, false, orgIAMPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
return c.createHuman(ctx, orgID, human, link, true, false, domainPolicy, pwPolicy, initCodeGenerator, phoneCodeGenerator)
}
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, selfregister, passwordless bool, orgIAMPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
if err := human.CheckDomainPolicy(orgIAMPolicy); err != nil {
func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.Human, link *domain.UserIDPLink, selfregister, passwordless bool, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator crypto.Generator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
if err := human.CheckDomainPolicy(domainPolicy); err != nil {
return nil, nil, err
}
if !orgIAMPolicy.UserLoginMustBeDomain {
if !domainPolicy.UserLoginMustBeDomain {
usernameSplit := strings.Split(human.Username, "@")
if len(usernameSplit) != 2 {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Dfd21", "Errors.User.Invalid")
@@ -219,9 +219,9 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
var events []eventstore.Command
if selfregister {
events = append(events, createRegisterHumanEvent(ctx, userAgg, human, orgIAMPolicy.UserLoginMustBeDomain))
events = append(events, createRegisterHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
} else {
events = append(events, createAddHumanEvent(ctx, userAgg, human, orgIAMPolicy.UserLoginMustBeDomain))
events = append(events, createAddHumanEvent(ctx, userAgg, human, domainPolicy.UserLoginMustBeDomain))
}
if link != nil {

View File

@@ -67,7 +67,8 @@ func (wm *HumanInitCodeWriteModel) Query() *eventstore.SearchQueryBuilder {
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(user.UserV1AddedType,
EventTypes(
user.UserV1AddedType,
user.HumanAddedType,
user.UserV1RegisteredType,
user.HumanRegisteredType,

View File

@@ -109,7 +109,8 @@ func (wm *HumanWriteModel) Query() *eventstore.SearchQueryBuilder {
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(user.HumanAddedType,
EventTypes(
user.HumanAddedType,
user.HumanRegisteredType,
user.HumanInitialCodeAddedType,
user.HumanInitializedCheckSucceededType,

View File

@@ -28,7 +28,7 @@ func (c *Commands) AddHumanOTP(ctx context.Context, userID, resourceowner string
orgPolicy, err := c.getOrgDomainPolicy(ctx, org.AggregateID)
if err != nil {
logging.Log("COMMAND-y5zv9").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Debug("unable to get org policy for loginname")
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-8ugTs", "Errors.Org.OrgIAMPolicy.NotFound")
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-8ugTs", "Errors.Org.DomainPolicy.NotFound")
}
otpWriteModel, err := c.otpWriteModelByID(ctx, userID, resourceowner)
if err != nil {

View File

@@ -176,7 +176,7 @@ func TestCommandSide_AddHumanOTP(t *testing.T) {
),
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),

View File

@@ -102,7 +102,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -137,7 +137,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -178,7 +178,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -272,7 +272,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -359,7 +359,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -440,7 +440,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -539,7 +539,7 @@ func TestCommandSide_AddHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -732,7 +732,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -767,7 +767,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -808,7 +808,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -896,7 +896,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -978,7 +978,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -1082,7 +1082,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -1190,7 +1190,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -1294,7 +1294,7 @@ func TestCommandSide_ImportHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -1495,7 +1495,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
@@ -1533,7 +1533,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
@@ -1579,7 +1579,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
@@ -1641,7 +1641,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
@@ -1703,7 +1703,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
false,
),
@@ -1782,7 +1782,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
false,
),
@@ -1919,7 +1919,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
@@ -2024,7 +2024,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("org1", "org1").Aggregate,
true,
),
@@ -2123,7 +2123,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),
@@ -2244,7 +2244,7 @@ func TestCommandSide_RegisterHuman(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&org.NewAggregate("org1", "org1").Aggregate,
true,
),

View File

@@ -317,8 +317,7 @@ type HumanU2FLoginReadModel struct {
Challenge string
AllowedCredentialIDs [][]byte
UserVerification domain.UserVerificationRequirement
User
State domain.UserState
State domain.UserState
}
func NewHumanU2FLoginReadModel(userID, authReqID, resourceOwner string) *HumanU2FLoginReadModel {

View File

@@ -13,11 +13,11 @@ func (c *Commands) AddMachine(ctx context.Context, orgID string, machine *domain
if !machine.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-bm9Ds", "Errors.User.Invalid")
}
orgIAMPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
domainPolicy, err := c.getOrgDomainPolicy(ctx, orgID)
if err != nil {
return nil, caos_errs.ThrowPreconditionFailed(err, "COMMAND-3M9fs", "Errors.Org.DomainPolicy.NotFound")
}
if !orgIAMPolicy.UserLoginMustBeDomain {
if !domainPolicy.UserLoginMustBeDomain {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-6M0ds", "Errors.User.Invalid")
}
userID, err := c.idGenerator.Next()
@@ -33,7 +33,7 @@ func (c *Commands) AddMachine(ctx context.Context, orgID string, machine *domain
machine.Username,
machine.Name,
machine.Description,
orgIAMPolicy.UserLoginMustBeDomain,
domainPolicy.UserLoginMustBeDomain,
))
if err != nil {
return nil, err

View File

@@ -83,7 +83,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
false,
),
@@ -110,7 +110,7 @@ func TestCommandSide_AddMachine(t *testing.T) {
t,
expectFilter(
eventFromEventPusher(
org.NewOrgDomainPolicyAddedEvent(context.Background(),
org.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),

View File

@@ -128,7 +128,7 @@ func UserAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregat
func CheckDomainPolicyForUserName(userName string, policy *domain.DomainPolicy) error {
if policy == nil {
return caos_errors.ThrowPreconditionFailed(nil, "COMMAND-3Mb9s", "Errors.Users.OrgIamPolicyNil")
return caos_errors.ThrowPreconditionFailed(nil, "COMMAND-3Mb9s", "Errors.Users.DomainPolicyNil")
}
if policy.UserLoginMustBeDomain && strings.Contains(userName, "@") {
return caos_errors.ThrowPreconditionFailed(nil, "COMMAND-4M9vs", "Errors.User.EmailAsUsernameNotAllowed")

View File

@@ -202,7 +202,7 @@ func TestCommandSide_UsernameChange(t *testing.T) {
expectFilter(),
expectFilter(
eventFromEventPusher(
instance.NewInstnaceDomainPolicyAddedEvent(context.Background(),
instance.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -244,7 +244,7 @@ func TestCommandSide_UsernameChange(t *testing.T) {
expectFilter(),
expectFilter(
eventFromEventPusher(
instance.NewInstnaceDomainPolicyAddedEvent(context.Background(),
instance.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -1026,7 +1026,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
expectFilter(),
expectFilter(
eventFromEventPusher(
instance.NewInstnaceDomainPolicyAddedEvent(context.Background(),
instance.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -1090,7 +1090,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
expectFilter(),
expectFilter(
eventFromEventPusher(
instance.NewInstnaceDomainPolicyAddedEvent(context.Background(),
instance.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),
@@ -1147,7 +1147,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
expectFilter(),
expectFilter(
eventFromEventPusher(
instance.NewInstnaceDomainPolicyAddedEvent(context.Background(),
instance.NewDomainPolicyAddedEvent(context.Background(),
&user.NewAggregate("user1", "org1").Aggregate,
true,
),

View File

@@ -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),
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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)
})
}
}

View File

@@ -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
}

View File

@@ -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)
}
})
}
}

View File

@@ -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)
})
}
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
})
}
}

View File

@@ -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)
}
})
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
})
}
}

View File

@@ -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
}
}

View File

@@ -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: "support@zitadel.ch",
},
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.Invalid.Argument"),
},
},
{
name: "invalid last name",
args: args{
a: agg,
human: &AddHuman{
Email: "support@zitadel.ch",
FirstName: "hurst",
},
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-DiAq8", "Errors.Invalid.Argument"),
},
},
{
name: "invalid password",
args: args{
a: agg,
human: &AddHuman{
Email: "support@zitadel.ch",
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: "support@zitadel.ch",
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,
"support@zitadel.ch",
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)
})
}
}

View File

@@ -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
}

View File

@@ -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)
}
})
}
}

View File

@@ -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,
"support@zitadel.ch",
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,
"support@zitadel.ch",
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)
}
})
}
}