mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-23 07:52:15 +00:00
fix: use preparation filter for org existence check during setup (#11932)
<!-- Please inform yourself about the contribution guidelines on submitting a PR here: https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr. Take note of how PR/commit titles should be written and replace the template texts in the sections below. Don't remove any of the sections. It is important that the commit history clearly shows what is changed and why. Important: By submitting a contribution you agree to the terms from our Licensing Policy as described here: https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions. --> # Which Problems Are Solved - Creating an organization via `SetUpOrg` failed when a custom domain was provided in the same request. - The org-domain setup path checked org existence only against persisted state, so it could not see the newly created organization before the batch was pushed. # How the Problems Are Solved - Added a preparation-aware org existence check that resolves the org through the validation filter instead of the direct persisted-state lookup. - Reused the transaction-aware preparation filter so later validations can see earlier in-flight commands from the same `PrepareCommands` batch. fixes #11677 --------- Co-authored-by: abhishek kumar gupta <[email protected]> Co-authored-by: Wim Van Laer <[email protected]>
This commit is contained in:
co-authored by
abhishek kumar gupta
Wim Van Laer
parent
21b28b56ac
commit
8e41d288ff
@@ -327,6 +327,26 @@ func (c *Commands) getOrg(ctx context.Context, orgID string) (*domain.Org, error
|
||||
return orgWriteModelToOrg(writeModel), nil
|
||||
}
|
||||
|
||||
func checkOrgExistsInPreparation(ctx context.Context, filter preparation.FilterToQueryReducer, orgID string) error {
|
||||
orgWriteModel := NewOrgWriteModel(orgID)
|
||||
|
||||
events, err := filter(ctx, orgWriteModel.Query())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
orgWriteModel.AppendEvents(events...)
|
||||
if err = orgWriteModel.Reduce(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isOrgStateExists(orgWriteModel.State) {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "COMMAND-QXPGs", "Errors.Org.NotFound")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Commands) checkOrgExists(ctx context.Context, orgID string) error {
|
||||
orgWriteModel, err := c.getOrgWriteModelByID(ctx, orgID)
|
||||
if err != nil {
|
||||
|
||||
@@ -35,7 +35,8 @@ func (c *Commands) prepareAddOrgDomain(a *org.Aggregate, addDomain string, userI
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := c.checkOrgExists(ctx, a.ID); err != nil {
|
||||
|
||||
if err := checkOrgExistsInPreparation(ctx, filter, a.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@ func TestAddDomain(t *testing.T) {
|
||||
}
|
||||
|
||||
agg := org.NewAggregate("test")
|
||||
noEvents := func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -58,19 +61,47 @@ func TestAddDomain(t *testing.T) {
|
||||
a: agg,
|
||||
domain: "domain",
|
||||
claimedUserIDs: []string{"userID1"},
|
||||
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainPolicyAddedEvent(ctx, &agg.Aggregate, true, true, true),
|
||||
}, nil
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewOrgAddedEvent(ctx, &agg.Aggregate, "org"),
|
||||
}, nil
|
||||
}).
|
||||
Append(noEvents).
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainPolicyAddedEvent(ctx, &agg.Aggregate, true, true, true),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
eventstore: expectEventstore(),
|
||||
loginPaths: expectLoginPathsNoCall,
|
||||
},
|
||||
want: Want{
|
||||
Commands: []eventstore.Command{
|
||||
org.NewDomainAddedEvent(context.Background(), &agg.Aggregate, "domain"),
|
||||
},
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"org"),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "correct when org exists only in the current preparation",
|
||||
args: args{
|
||||
a: agg,
|
||||
domain: "domain",
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewOrgAddedEvent(ctx, &agg.Aggregate, "org"),
|
||||
}, nil
|
||||
}).
|
||||
Append(noEvents).
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainPolicyAddedEvent(ctx, &agg.Aggregate, true, true, true),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
eventstore: expectEventstore(),
|
||||
loginPaths: expectLoginPathsNoCall,
|
||||
},
|
||||
want: Want{
|
||||
@@ -86,41 +117,41 @@ func TestAddDomain(t *testing.T) {
|
||||
domain: "domain",
|
||||
claimedUserIDs: []string{"userID1"},
|
||||
idGenerator: id_mock.ExpectID(t, "newID"),
|
||||
filter: func() func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
i := 0 //TODO: we should fix this in the future to use some kind of mock struct and expect filter calls
|
||||
return func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
if i == 2 {
|
||||
i++
|
||||
return []eventstore.Event{user.NewHumanAddedEvent(
|
||||
ctx,
|
||||
&user.NewAggregate("userID1", "org2").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
)}, nil
|
||||
}
|
||||
if i == 3 {
|
||||
i++
|
||||
return []eventstore.Event{org.NewDomainPolicyAddedEvent(ctx, &agg.Aggregate, false, false, false)}, nil
|
||||
}
|
||||
i++
|
||||
return []eventstore.Event{org.NewDomainPolicyAddedEvent(ctx, &agg.Aggregate, true, false, false)}, nil
|
||||
}
|
||||
}(),
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"org"),
|
||||
),
|
||||
),
|
||||
),
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewOrgAddedEvent(ctx, &agg.Aggregate, "org"),
|
||||
}, nil
|
||||
}).
|
||||
Append(noEvents).
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainPolicyAddedEvent(ctx, &agg.Aggregate, false, false, false),
|
||||
}, nil
|
||||
}).
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{user.NewHumanAddedEvent(
|
||||
ctx,
|
||||
&user.NewAggregate("userID1", "org2").Aggregate,
|
||||
"username",
|
||||
"firstname",
|
||||
"lastname",
|
||||
"nickname",
|
||||
"displayname",
|
||||
language.Und,
|
||||
domain.GenderUnspecified,
|
||||
"email",
|
||||
false,
|
||||
)}, nil
|
||||
}).
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainPolicyAddedEvent(ctx, &org.NewAggregate("org2").Aggregate, false, false, false),
|
||||
}, nil
|
||||
}).
|
||||
Append(noEvents).
|
||||
Filter(),
|
||||
eventstore: expectEventstore(),
|
||||
loginPaths: expectLoginPathsDefaultDomainClaimedURLTemplate(""),
|
||||
},
|
||||
want: Want{
|
||||
@@ -137,21 +168,21 @@ func TestAddDomain(t *testing.T) {
|
||||
a: agg,
|
||||
domain: "domain",
|
||||
claimedUserIDs: []string{"userID1"},
|
||||
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain"),
|
||||
org.NewDomainVerificationAddedEvent(ctx, &agg.Aggregate, "domain", domain.OrgDomainValidationTypeHTTP, nil),
|
||||
org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "domain"),
|
||||
}, nil
|
||||
},
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"org"),
|
||||
),
|
||||
),
|
||||
),
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewOrgAddedEvent(ctx, &agg.Aggregate, "org"),
|
||||
}, nil
|
||||
}).
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainAddedEvent(ctx, &agg.Aggregate, "domain"),
|
||||
org.NewDomainVerificationAddedEvent(ctx, &agg.Aggregate, "domain", domain.OrgDomainValidationTypeHTTP, nil),
|
||||
org.NewDomainVerifiedEvent(ctx, &agg.Aggregate, "domain"),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
eventstore: expectEventstore(),
|
||||
loginPaths: expectLoginPathsNoCall,
|
||||
},
|
||||
want: Want{
|
||||
@@ -179,19 +210,20 @@ func TestAddDomain(t *testing.T) {
|
||||
a: agg,
|
||||
domain: "domain",
|
||||
claimedUserIDs: []string{"userID1"},
|
||||
filter: func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainPolicyAddedEvent(ctx, &agg.Aggregate, true, true, true),
|
||||
}, nil
|
||||
},
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewOrgAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"org"),
|
||||
),
|
||||
),
|
||||
),
|
||||
filter: NewMultiFilter().
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewOrgAddedEvent(ctx, &agg.Aggregate, "org"),
|
||||
}, nil
|
||||
}).
|
||||
Append(noEvents).
|
||||
Append(func(ctx context.Context, queryFactory *eventstore.SearchQueryBuilder) ([]eventstore.Event, error) {
|
||||
return []eventstore.Event{
|
||||
org.NewDomainPolicyAddedEvent(ctx, &agg.Aggregate, true, true, true),
|
||||
}, nil
|
||||
}).
|
||||
Filter(),
|
||||
eventstore: expectEventstore(),
|
||||
loginPaths: expectLoginPathsNoCall,
|
||||
permissionCheck: newMockOrganizationPermissionCheckAllowed(),
|
||||
},
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
id_mock "github.com/zitadel/zitadel/internal/id/mock"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/project"
|
||||
"github.com/zitadel/zitadel/internal/repository/user"
|
||||
@@ -1845,6 +1846,64 @@ func TestCommandSide_SetUpOrg(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no admin added, custom domain",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(), // org already exists check
|
||||
expectFilter(), // org exists in current preparation
|
||||
expectFilter(), // custom domain does not exist yet
|
||||
expectFilter(), // no org domain policy yet
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewDomainPolicyAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("instance1").Aggregate,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
eventFromEventPusher(org.NewOrgAddedEvent(context.Background(),
|
||||
&org.NewAggregate("orgID").Aggregate,
|
||||
"Org",
|
||||
)),
|
||||
eventFromEventPusher(org.NewDomainAddedEvent(context.Background(),
|
||||
&org.NewAggregate("orgID").Aggregate, "org.iam-domain",
|
||||
)),
|
||||
eventFromEventPusher(org.NewDomainVerifiedEvent(context.Background(),
|
||||
&org.NewAggregate("orgID").Aggregate,
|
||||
"org.iam-domain",
|
||||
)),
|
||||
eventFromEventPusher(org.NewDomainPrimarySetEvent(context.Background(),
|
||||
&org.NewAggregate("orgID").Aggregate,
|
||||
"org.iam-domain",
|
||||
)),
|
||||
eventFromEventPusher(org.NewDomainAddedEvent(context.Background(),
|
||||
&org.NewAggregate("orgID").Aggregate,
|
||||
"custom.example.com",
|
||||
)),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "orgID"),
|
||||
},
|
||||
args: args{
|
||||
ctx: http_util.WithRequestedHost(authz.WithInstanceID(context.Background(), "instance1"), "iam-domain"),
|
||||
setupOrg: &OrgSetup{
|
||||
Name: "Org",
|
||||
CustomDomain: "custom.example.com",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
createdOrg: &CreatedOrg{
|
||||
ObjectDetails: &domain.ObjectDetails{
|
||||
ResourceOwner: "orgID",
|
||||
},
|
||||
OrgAdmins: []OrgAdmin{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "existing human added",
|
||||
fields: fields{
|
||||
|
||||
Reference in New Issue
Block a user