feat: multiple domains (#188)

* check uniqueness on create and register user

* change user email, reserve release unique email

* usergrant unique aggregate

* usergrant uniqueness

* validate UserGrant

* fix tests

* domain is set on username in all orgs

* domain in admin

* org domain sql

* zitadel domain org name

* org domains

* org iam policy

* default org iam policy

* SETUP

* load login names

* login by login name

* login name

* fix: merge master

* fix: merge master

* Update internal/user/repository/eventsourcing/user.go

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

* fix: fix unique domains

* fix: rename env variable

Co-authored-by: adlerhurst <silvan.reusser@gmail.com>
Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi
2020-06-16 11:40:18 +02:00
committed by GitHub
parent 64b14b4e19
commit 7a6ca24625
109 changed files with 12578 additions and 6025 deletions

View File

@@ -0,0 +1,18 @@
package model
import es_models "github.com/caos/zitadel/internal/eventstore/models"
type OrgDomain struct {
es_models.ObjectRoot
Domain string
Primary bool
Verified bool
}
func NewOrgDomain(orgID, domain string) *OrgDomain {
return &OrgDomain{ObjectRoot: es_models.ObjectRoot{AggregateID: orgID}, Domain: domain}
}
func (domain *OrgDomain) IsValid() bool {
return domain.AggregateID != "" && domain.Domain != ""
}

View File

@@ -0,0 +1,52 @@
package model
import (
"github.com/caos/zitadel/internal/model"
"time"
)
type OrgDomainView struct {
OrgID string
CreationDate time.Time
ChangeDate time.Time
Domain string
Primary bool
Verified bool
}
type OrgDomainSearchRequest struct {
Offset uint64
Limit uint64
SortingColumn OrgDomainSearchKey
Asc bool
Queries []*OrgDomainSearchQuery
}
type OrgDomainSearchKey int32
const (
ORGDOMAINSEARCHKEY_UNSPECIFIED OrgDomainSearchKey = iota
ORGDOMAINSEARCHKEY_DOMAIN
ORGDOMAINSEARCHKEY_ORG_ID
ORGDOMAINSEARCHKEY_VERIFIED
ORGDOMAINSEARCHKEY_PRIMARY
)
type OrgDomainSearchQuery struct {
Key OrgDomainSearchKey
Method model.SearchMethod
Value interface{}
}
type OrgDomainSearchResponse struct {
Offset uint64
Limit uint64
TotalResult uint64
Result []*OrgDomainView
}
func (r *OrgDomainSearchRequest) EnsureLimit(limit uint64) {
if r.Limit == 0 || r.Limit > limit {
r.Limit = limit
}
}

View File

@@ -2,17 +2,19 @@ package model
import (
es_models "github.com/caos/zitadel/internal/eventstore/models"
"strings"
"github.com/golang/protobuf/ptypes/timestamp"
)
type Org struct {
es_models.ObjectRoot
State OrgState
Name string
Domain string
State OrgState
Name string
Domains []*OrgDomain
Members []*OrgMember
Members []*OrgMember
OrgIamPolicy *OrgIamPolicy
}
type OrgChanges struct {
Changes []*OrgChange
@@ -43,7 +45,16 @@ func (o *Org) IsActive() bool {
}
func (o *Org) IsValid() bool {
return o.Name != "" && o.Domain != ""
return o.Name != ""
}
func (o *Org) ContainsDomain(domain *OrgDomain) bool {
for _, d := range o.Domains {
if d.Domain == domain.Domain {
return true
}
}
return false
}
func (o *Org) ContainsMember(userID string) bool {
@@ -54,3 +65,11 @@ func (o *Org) ContainsMember(userID string) bool {
}
return false
}
func (o *Org) nameForDomain(iamDomain string) string {
return strings.ToLower(strings.ReplaceAll(o.Name, " ", "-") + "." + iamDomain)
}
func (o *Org) AddIAMDomain(iamDomain string) {
o.Domains = append(o.Domains, &OrgDomain{Domain: o.nameForDomain(iamDomain), Verified: true, Primary: true})
}

View File

@@ -0,0 +1,21 @@
package model
import (
"github.com/caos/zitadel/internal/eventstore/models"
)
type OrgIamPolicy struct {
models.ObjectRoot
Description string
State PolicyState
UserLoginMustBeDomain bool
Default bool
}
type PolicyState int32
const (
POLICYSTATE_ACTIVE PolicyState = iota
POLICYSTATE_REMOVED
)

View File

@@ -42,7 +42,7 @@ const (
type OrgMemberSearchQuery struct {
Key OrgMemberSearchKey
Method model.SearchMethod
Value string
Value interface{}
}
type OrgMemberSearchResponse struct {

View File

@@ -15,8 +15,7 @@ type OrgView struct {
ResourceOwner string
Sequence uint64
Name string
Domain string
Name string
}
type OrgSearchRequest struct {
@@ -41,7 +40,7 @@ const (
type OrgSearchQuery struct {
Key OrgSearchKey
Method model.SearchMethod
Value string
Value interface{}
}
type OrgSearchResult struct {
@@ -66,8 +65,7 @@ func OrgViewToOrg(o *OrgView) *Org {
ResourceOwner: o.ResourceOwner,
Sequence: o.Sequence,
},
Domain: o.Domain,
Name: o.Name,
State: o.State,
Name: o.Name,
State: o.State,
}
}

View File

@@ -3,11 +3,11 @@ package eventsourcing
import (
"context"
"encoding/json"
"github.com/caos/zitadel/internal/config/systemdefaults"
"log"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
es_models "github.com/caos/zitadel/internal/eventstore/models"
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
@@ -19,17 +19,24 @@ import (
type OrgEventstore struct {
eventstore.Eventstore
idGenerator id.Generator
IAMDomain string
idGenerator id.Generator
defaultOrgIamPolicy *org_model.OrgIamPolicy
}
type OrgConfig struct {
eventstore.Eventstore
IAMDomain string
}
func StartOrg(conf OrgConfig) *OrgEventstore {
func StartOrg(conf OrgConfig, defaults systemdefaults.SystemDefaults) *OrgEventstore {
policy := defaults.DefaultPolicies.OrgIam
policy.Default = true
return &OrgEventstore{
Eventstore: conf.Eventstore,
idGenerator: id.SonyFlakeGenerator,
Eventstore: conf.Eventstore,
idGenerator: id.SonyFlakeGenerator,
IAMDomain: conf.IAMDomain,
defaultOrgIamPolicy: &policy,
}
}
@@ -37,6 +44,8 @@ func (es *OrgEventstore) PrepareCreateOrg(ctx context.Context, orgModel *org_mod
if orgModel == nil || !orgModel.IsValid() {
return nil, nil, errors.ThrowInvalidArgument(nil, "EVENT-OeLSk", "org not valid")
}
orgModel.AddIAMDomain(es.IAMDomain)
id, err := es.idGenerator.Next()
if err != nil {
return nil, nil, errors.ThrowInternal(err, "EVENT-OwciI", "id gen failed")
@@ -138,6 +147,53 @@ func (es *OrgEventstore) ReactivateOrg(ctx context.Context, orgID string) (*org_
return model.OrgToModel(org), nil
}
func (es *OrgEventstore) AddOrgDomain(ctx context.Context, domain *org_model.OrgDomain) (*org_model.OrgDomain, error) {
if !domain.IsValid() {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-8sFJW", "domain is invalid")
}
existing, err := es.OrgByID(ctx, org_model.NewOrg(domain.AggregateID))
if err != nil {
return nil, err
}
repoOrg := model.OrgFromModel(existing)
repoDomain := model.OrgDomainFromModel(domain)
aggregate := OrgDomainAddedAggregate(es.Eventstore.AggregateCreator(), repoOrg, repoDomain)
err = es_sdk.Push(ctx, es.PushAggregates, repoOrg.AppendEvents, aggregate)
if err != nil {
return nil, err
}
if _, d := model.GetDomain(repoOrg.Domains, domain.Domain); d != nil {
return model.OrgDomainToModel(d), nil
}
return nil, errors.ThrowInternal(nil, "EVENT-ISOP0", "Could not find org in list")
}
func (es *OrgEventstore) RemoveOrgDomain(ctx context.Context, domain *org_model.OrgDomain) error {
if domain.Domain == "" {
return errors.ThrowPreconditionFailed(nil, "EVENT-SJsK3", "Domain is required")
}
existing, err := es.OrgByID(ctx, org_model.NewOrg(domain.AggregateID))
if err != nil {
return err
}
if !existing.ContainsDomain(domain) {
return errors.ThrowPreconditionFailed(nil, "EVENT-Sjdi3", "Domain doesn't exist on project")
}
repoOrg := model.OrgFromModel(existing)
repoDomain := model.OrgDomainFromModel(domain)
orgAggregates, err := OrgDomainRemovedAggregate(ctx, es.Eventstore.AggregateCreator(), repoOrg, repoDomain)
if err != nil {
return err
}
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoOrg.AppendEvents, orgAggregates...)
if err != nil {
return err
}
return nil
}
func (es *OrgEventstore) OrgChanges(ctx context.Context, id string, lastSequence uint64, limit uint64) (*org_model.OrgChanges, error) {
query := ChangesQuery(id, lastSequence)
@@ -147,7 +203,7 @@ func (es *OrgEventstore) OrgChanges(ctx context.Context, id string, lastSequence
return nil, errors.ThrowInternal(err, "EVENT-328b1", "unable to get current user")
}
if len(events) == 0 {
return nil, caos_errs.ThrowNotFound(nil, "EVENT-FpQqK", "no objects found")
return nil, errors.ThrowNotFound(nil, "EVENT-FpQqK", "no objects found")
}
result := make([]*org_model.OrgChange, 0)
@@ -277,3 +333,74 @@ func (es *OrgEventstore) RemoveOrgMember(ctx context.Context, member *org_model.
orgAggregate := orgMemberRemovedAggregate(es.Eventstore.AggregateCreator(), repoMember)
return es_sdk.Push(ctx, es.PushAggregates, repoMember.AppendEvents, orgAggregate)
}
func (es *OrgEventstore) GetOrgIamPolicy(ctx context.Context, orgID string) (*org_model.OrgIamPolicy, error) {
existing, err := es.OrgByID(ctx, org_model.NewOrg(orgID))
if err != nil && !errors.IsNotFound(err) {
return nil, err
}
if existing != nil && existing.OrgIamPolicy != nil {
return existing.OrgIamPolicy, nil
}
return es.defaultOrgIamPolicy, nil
}
func (es *OrgEventstore) AddOrgIamPolicy(ctx context.Context, policy *org_model.OrgIamPolicy) (*org_model.OrgIamPolicy, error) {
existing, err := es.OrgByID(ctx, org_model.NewOrg(policy.AggregateID))
if err != nil {
return nil, err
}
if existing.OrgIamPolicy != nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-7Usj3", "Policy already exists")
}
repoOrg := model.OrgFromModel(existing)
repoPolicy := model.OrgIamPolicyFromModel(policy)
orgAggregate := OrgIamPolicyAddedAggregate(es.Eventstore.AggregateCreator(), repoOrg, repoPolicy)
if err != nil {
return nil, err
}
err = es_sdk.Push(ctx, es.PushAggregates, repoOrg.AppendEvents, orgAggregate)
if err != nil {
return nil, err
}
return model.OrgIamPolicyToModel(repoOrg.OrgIamPolicy), nil
}
func (es *OrgEventstore) ChangeOrgIamPolicy(ctx context.Context, policy *org_model.OrgIamPolicy) (*org_model.OrgIamPolicy, error) {
existing, err := es.OrgByID(ctx, org_model.NewOrg(policy.AggregateID))
if err != nil {
return nil, err
}
if existing.OrgIamPolicy == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-8juSd", "Policy doesnt exist")
}
repoOrg := model.OrgFromModel(existing)
repoPolicy := model.OrgIamPolicyFromModel(policy)
orgAggregate := OrgIamPolicyChangedAggregate(es.Eventstore.AggregateCreator(), repoOrg, repoPolicy)
if err != nil {
return nil, err
}
err = es_sdk.Push(ctx, es.PushAggregates, repoOrg.AppendEvents, orgAggregate)
if err != nil {
return nil, err
}
return model.OrgIamPolicyToModel(repoOrg.OrgIamPolicy), nil
}
func (es *OrgEventstore) RemoveOrgIamPolicy(ctx context.Context, orgID string) error {
existing, err := es.OrgByID(ctx, org_model.NewOrg(orgID))
if err != nil {
return err
}
if existing.OrgIamPolicy == nil {
return errors.ThrowPreconditionFailed(nil, "EVENT-z6Dse", "Policy doesnt exist")
}
repoOrg := model.OrgFromModel(existing)
orgAggregate := OrgIamPolicyRemovedAggregate(es.Eventstore.AggregateCreator(), repoOrg)
if err != nil {
return err
}
return es_sdk.Push(ctx, es.PushAggregates, repoOrg.AppendEvents, orgAggregate)
}

View File

@@ -18,8 +18,7 @@ func GetMockedEventstoreComplexity(ctrl *gomock.Controller, mockEs *mock.MockEve
func GetMockChangesOrgOK(ctrl *gomock.Controller) *OrgEventstore {
org := model.Org{
Name: "MusterOrg",
Domain: "myDomain",
Name: "MusterOrg",
}
data, err := json.Marshal(org)
if err != nil {

View File

@@ -1058,7 +1058,7 @@ func TestChangesOrg(t *testing.T) {
},
res: res{
changes: &org_model.OrgChanges{Changes: []*org_model.OrgChange{&org_model.OrgChange{EventType: "", Sequence: 1, Modifier: ""}}, LastSequence: 1},
org: &model.Org{Name: "MusterOrg", Domain: "myDomain"},
org: &model.Org{Name: "MusterOrg"},
},
},
{

View File

@@ -0,0 +1,120 @@
package model
import (
"encoding/json"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/org/model"
)
type OrgDomain struct {
es_models.ObjectRoot `json:"-"`
Domain string `json:"domain"`
Verified bool `json:"-"`
Primary bool `json:"-"`
}
func GetDomain(domains []*OrgDomain, domain string) (int, *OrgDomain) {
for i, d := range domains {
if d.Domain == domain {
return i, d
}
}
return -1, nil
}
func (o *Org) appendAddDomainEvent(event *es_models.Event) error {
domain := new(OrgDomain)
err := domain.SetData(event)
if err != nil {
return err
}
domain.ObjectRoot.CreationDate = event.CreationDate
o.Domains = append(o.Domains, domain)
return nil
}
func (o *Org) appendRemoveDomainEvent(event *es_models.Event) error {
domain := new(OrgDomain)
err := domain.SetData(event)
if err != nil {
return err
}
if i, r := GetDomain(o.Domains, domain.Domain); r != nil {
o.Domains[i] = o.Domains[len(o.Domains)-1]
o.Domains[len(o.Domains)-1] = nil
o.Domains = o.Domains[:len(o.Domains)-1]
}
return nil
}
func (o *Org) appendVerifyDomainEvent(event *es_models.Event) error {
domain := new(OrgDomain)
err := domain.SetData(event)
if err != nil {
return err
}
if i, d := GetDomain(o.Domains, domain.Domain); d != nil {
d.Verified = true
o.Domains[i] = d
}
return nil
}
func (o *Org) appendPrimaryDomainEvent(event *es_models.Event) error {
domain := new(OrgDomain)
err := domain.SetData(event)
if err != nil {
return err
}
for _, d := range o.Domains {
d.Primary = false
if d.Domain == domain.Domain {
d.Primary = true
}
}
return nil
}
func (m *OrgDomain) SetData(event *es_models.Event) error {
err := json.Unmarshal(event.Data, m)
if err != nil {
return errors.ThrowInternal(err, "EVENT-Hz7Mb", "unable to unmarshal data")
}
return nil
}
func OrgDomainsFromModel(domains []*model.OrgDomain) []*OrgDomain {
convertedDomainss := make([]*OrgDomain, len(domains))
for i, m := range domains {
convertedDomainss[i] = OrgDomainFromModel(m)
}
return convertedDomainss
}
func OrgDomainFromModel(domain *model.OrgDomain) *OrgDomain {
return &OrgDomain{
ObjectRoot: domain.ObjectRoot,
Domain: domain.Domain,
Verified: domain.Verified,
Primary: domain.Primary,
}
}
func OrgDomainsToModel(domains []*OrgDomain) []*model.OrgDomain {
convertedDomains := make([]*model.OrgDomain, len(domains))
for i, m := range domains {
convertedDomains[i] = OrgDomainToModel(m)
}
return convertedDomains
}
func OrgDomainToModel(domain *OrgDomain) *model.OrgDomain {
return &model.OrgDomain{
ObjectRoot: domain.ObjectRoot,
Domain: domain.Domain,
Verified: domain.Verified,
Primary: domain.Primary,
}
}

View File

@@ -14,33 +14,42 @@ const (
type Org struct {
es_models.ObjectRoot `json:"-"`
Name string `json:"name,omitempty"`
Domain string `json:"domain,omitempty"`
State int32 `json:"-"`
Name string `json:"name,omitempty"`
State int32 `json:"-"`
Members []*OrgMember `json:"-"`
Domains []*OrgDomain `json:"-"`
Members []*OrgMember `json:"-"`
OrgIamPolicy *OrgIamPolicy `json:"-"`
}
func OrgFromModel(org *org_model.Org) *Org {
members := OrgMembersFromModel(org.Members)
return &Org{
domains := OrgDomainsFromModel(org.Domains)
converted := &Org{
ObjectRoot: org.ObjectRoot,
Domain: org.Domain,
Name: org.Name,
State: int32(org.State),
Domains: domains,
Members: members,
}
if org.OrgIamPolicy != nil {
converted.OrgIamPolicy = OrgIamPolicyFromModel(org.OrgIamPolicy)
}
return converted
}
func OrgToModel(org *Org) *org_model.Org {
return &org_model.Org{
converted := &org_model.Org{
ObjectRoot: org.ObjectRoot,
Domain: org.Domain,
Name: org.Name,
State: org_model.OrgState(org.State),
Domains: OrgDomainsToModel(org.Domains),
Members: OrgMembersToModel(org.Members),
}
if org.OrgIamPolicy != nil {
converted.OrgIamPolicy = OrgIamPolicyToModel(org.OrgIamPolicy)
}
return converted
}
func OrgFromEvents(org *Org, events ...*es_models.Event) (*Org, error) {
@@ -101,6 +110,20 @@ func (o *Org) AppendEvent(event *es_models.Event) error {
return err
}
o.removeMember(member.UserID)
case OrgDomainAdded:
o.appendAddDomainEvent(event)
case OrgDomainVerified:
o.appendVerifyDomainEvent(event)
case OrgDomainPrimarySet:
o.appendPrimaryDomainEvent(event)
case OrgDomainRemoved:
o.appendRemoveDomainEvent(event)
case OrgIamPolicyAdded:
o.appendAddOrgIamPolicyEvent(event)
case OrgIamPolicyChanged:
o.appendChangeOrgIamPolicyEvent(event)
case OrgIamPolicyRemoved:
o.appendRemoveOrgIamPolicyEvent()
}
o.ObjectRoot.AppendEvent(event)
@@ -151,9 +174,6 @@ func (o *Org) Changes(changed *Org) map[string]interface{} {
if changed.Name != "" && changed.Name != o.Name {
changes["name"] = changed.Name
}
if changed.Domain != "" && changed.Domain != o.Domain {
changes["domain"] = changed.Domain
}
return changes
}

View File

@@ -0,0 +1,72 @@
package model
import (
"encoding/json"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
org_model "github.com/caos/zitadel/internal/org/model"
)
type OrgIamPolicy struct {
models.ObjectRoot
Description string `json:"description,omitempty"`
State int32 `json:"-"`
UserLoginMustBeDomain bool `json:"userLoginMustBeDomain"`
}
func OrgIamPolicyToModel(policy *OrgIamPolicy) *org_model.OrgIamPolicy {
return &org_model.OrgIamPolicy{
ObjectRoot: policy.ObjectRoot,
State: org_model.PolicyState(policy.State),
UserLoginMustBeDomain: policy.UserLoginMustBeDomain,
}
}
func OrgIamPolicyFromModel(policy *org_model.OrgIamPolicy) *OrgIamPolicy {
return &OrgIamPolicy{
ObjectRoot: policy.ObjectRoot,
State: int32(policy.State),
UserLoginMustBeDomain: policy.UserLoginMustBeDomain,
}
}
func (o *Org) appendAddOrgIamPolicyEvent(event *es_models.Event) error {
o.OrgIamPolicy = new(OrgIamPolicy)
err := o.OrgIamPolicy.SetData(event)
if err != nil {
return err
}
o.OrgIamPolicy.ObjectRoot.CreationDate = event.CreationDate
return nil
}
func (o *Org) appendChangeOrgIamPolicyEvent(event *es_models.Event) error {
return o.OrgIamPolicy.SetData(event)
}
func (o *Org) appendRemoveOrgIamPolicyEvent() {
o.OrgIamPolicy = nil
}
func (p *OrgIamPolicy) Changes(changed *OrgIamPolicy) map[string]interface{} {
changes := make(map[string]interface{}, 2)
if changed.Description != p.Description {
changes["description"] = changed.Description
}
if changed.UserLoginMustBeDomain != p.UserLoginMustBeDomain {
changes["userLoginMustBeDomain"] = changed.UserLoginMustBeDomain
}
return changes
}
func (p *OrgIamPolicy) SetData(event *es_models.Event) error {
err := json.Unmarshal(event.Data, p)
if err != nil {
return errors.ThrowInternal(err, "EVENT-7JS9d", "unable to unmarshal data")
}
return nil
}

View File

@@ -74,10 +74,10 @@ func TestAppendEvent(t *testing.T) {
{
name: "append change event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: OrgChanged, Data: []byte(`{"domain": "OrgDomain"}`)},
org: &Org{Name: "OrgName", Domain: "asdf"},
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: OrgChanged, Data: []byte(`{"name": "OrgName}`)},
org: &Org{Name: "OrgNameChanged"},
},
result: &Org{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, State: int32(model.ORGSTATE_ACTIVE), Name: "OrgName", Domain: "OrgDomain"},
result: &Org{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, State: int32(model.ORGSTATE_ACTIVE), Name: "OrgNameChanged"},
},
{
name: "append deactivate event",
@@ -93,6 +93,13 @@ func TestAppendEvent(t *testing.T) {
},
result: &Org{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, State: int32(model.ORGSTATE_ACTIVE)},
},
{
name: "append added domain event",
args: args{
event: &es_models.Event{AggregateID: "ID", Sequence: 1, Type: OrgDomainAdded},
},
result: &Org{ObjectRoot: es_models.ObjectRoot{AggregateID: "ID"}, State: int32(model.ORGSTATE_ACTIVE)},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -138,16 +145,6 @@ func TestChanges(t *testing.T) {
changesLen: 1,
},
},
{
name: "org domain changes",
args: args{
existing: &Org{Name: "Name", Domain: "old domain"},
new: &Org{Name: "Name", Domain: "new domain"},
},
res: res{
changesLen: 1,
},
},
{
name: "no changes",
args: args{

View File

@@ -7,11 +7,15 @@ const (
OrgDomainAggregate models.AggregateType = "org.domain"
OrgNameAggregate models.AggregateType = "org.name"
OrgAdded models.EventType = "org.added"
OrgChanged models.EventType = "org.changed"
OrgDeactivated models.EventType = "org.deactivated"
OrgReactivated models.EventType = "org.reactivated"
OrgRemoved models.EventType = "org.removed"
OrgAdded models.EventType = "org.added"
OrgChanged models.EventType = "org.changed"
OrgDeactivated models.EventType = "org.deactivated"
OrgReactivated models.EventType = "org.reactivated"
OrgRemoved models.EventType = "org.removed"
OrgDomainAdded models.EventType = "org.domain.added"
OrgDomainVerified models.EventType = "org.domain.verified"
OrgDomainRemoved models.EventType = "org.domain.removed"
OrgDomainPrimarySet models.EventType = "org.domain.primary.set"
OrgNameReserved models.EventType = "org.name.reserved"
OrgNameReleased models.EventType = "org.name.released"
@@ -22,4 +26,8 @@ const (
OrgMemberAdded models.EventType = "org.member.added"
OrgMemberChanged models.EventType = "org.member.changed"
OrgMemberRemoved models.EventType = "org.member.removed"
OrgIamPolicyAdded models.EventType = "org.iam.policy.added"
OrgIamPolicyChanged models.EventType = "org.iam.policy.changed"
OrgIamPolicyRemoved models.EventType = "org.iam.policy.removed"
)

View File

@@ -47,16 +47,6 @@ func orgCreatedAggregates(ctx context.Context, aggCreator *es_models.AggregateCr
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-kdie7", "org should not be nil")
}
domainAgrregate, err := uniqueDomainAggregate(ctx, aggCreator, org.AggregateID, org.Domain)
if err != nil {
return nil, err
}
nameAggregate, err := uniqueNameAggregate(ctx, aggCreator, org.AggregateID, org.Name)
if err != nil {
return nil, err
}
agg, err := aggCreator.NewAggregate(ctx, org.AggregateID, model.OrgAggregate, model.OrgVersion, org.Sequence, es_models.OverwriteResourceOwner(org.AggregateID))
if err != nil {
return nil, err
@@ -65,12 +55,44 @@ func orgCreatedAggregates(ctx context.Context, aggCreator *es_models.AggregateCr
if err != nil {
return nil, err
}
aggregates := make([]*es_models.Aggregate, 0)
aggregates, err = addDomainAggregateAndEvents(ctx, aggCreator, agg, aggregates, org)
if err != nil {
return nil, err
}
nameAggregate, err := reservedUniqueNameAggregate(ctx, aggCreator, org.AggregateID, org.Name)
if err != nil {
return nil, err
}
aggregates = append(aggregates, nameAggregate)
return append(aggregates, agg), nil
}
return []*es_models.Aggregate{
agg,
domainAgrregate,
nameAggregate,
}, nil
func addDomainAggregateAndEvents(ctx context.Context, aggCreator *es_models.AggregateCreator, orgAggregate *es_models.Aggregate, aggregates []*es_models.Aggregate, org *model.Org) ([]*es_models.Aggregate, error) {
for _, domain := range org.Domains {
orgAggregate, err := orgAggregate.AppendEvent(model.OrgDomainAdded, domain)
if err != nil {
return nil, err
}
if domain.Verified {
domainAggregate, err := reservedUniqueDomainAggregate(ctx, aggCreator, org.AggregateID, domain.Domain)
if err != nil {
return nil, err
}
aggregates = append(aggregates, domainAggregate)
orgAggregate, err = orgAggregate.AppendEvent(model.OrgDomainVerified, domain)
if err != nil {
return nil, err
}
}
if domain.Primary {
orgAggregate, err = orgAggregate.AppendEvent(model.OrgDomainPrimarySet, domain)
if err != nil {
return nil, err
}
}
}
return aggregates, nil
}
func OrgUpdateAggregates(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *model.Org, updated *model.Org) ([]*es_models.Aggregate, error) {
@@ -88,19 +110,16 @@ func OrgUpdateAggregates(ctx context.Context, aggCreator *es_models.AggregateCre
aggregates := make([]*es_models.Aggregate, 0, 3)
if name, ok := changes["name"]; ok {
nameAggregate, err := uniqueNameAggregate(ctx, aggCreator, "", name.(string))
nameAggregate, err := reservedUniqueNameAggregate(ctx, aggCreator, "", name.(string))
if err != nil {
return nil, err
}
aggregates = append(aggregates, nameAggregate)
}
if name, ok := changes["domain"]; ok {
domainAggregate, err := uniqueDomainAggregate(ctx, aggCreator, "", name.(string))
nameReleasedAggregate, err := releasedUniqueNameAggregate(ctx, aggCreator, "", existing.Name)
if err != nil {
return nil, err
}
aggregates = append(aggregates, domainAggregate)
aggregates = append(aggregates, nameReleasedAggregate)
}
orgAggregate, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
@@ -151,7 +170,7 @@ func orgReactivateAggregate(aggCreator *es_models.AggregateCreator, org *model.O
}
}
func uniqueDomainAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, domain string) (*es_models.Aggregate, error) {
func reservedUniqueDomainAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, domain string) (*es_models.Aggregate, error) {
aggregate, err := aggCreator.NewAggregate(ctx, domain, model.OrgDomainAggregate, model.OrgVersion, 0)
if resourceOwner != "" {
aggregate, err = aggCreator.NewAggregate(ctx, domain, model.OrgDomainAggregate, model.OrgVersion, 0, es_models.OverwriteResourceOwner(resourceOwner))
@@ -164,10 +183,26 @@ func uniqueDomainAggregate(ctx context.Context, aggCreator *es_models.AggregateC
return nil, err
}
return aggregate.SetPrecondition(OrgDomainUniqueQuery(domain), isReservedValidation(aggregate, model.OrgDomainReserved)), nil
return aggregate.SetPrecondition(OrgDomainUniqueQuery(domain), isEventValidation(aggregate, model.OrgDomainReserved)), nil
}
func uniqueNameAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, name string) (aggregate *es_models.Aggregate, err error) {
func releasedUniqueDomainAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, domain string) (*es_models.Aggregate, error) {
aggregate, err := aggCreator.NewAggregate(ctx, domain, model.OrgDomainAggregate, model.OrgVersion, 0)
if resourceOwner != "" {
aggregate, err = aggCreator.NewAggregate(ctx, domain, model.OrgDomainAggregate, model.OrgVersion, 0, es_models.OverwriteResourceOwner(resourceOwner))
}
if err != nil {
return nil, err
}
aggregate, err = aggregate.AppendEvent(model.OrgDomainReleased, nil)
if err != nil {
return nil, err
}
return aggregate.SetPrecondition(OrgDomainUniqueQuery(domain), isEventValidation(aggregate, model.OrgDomainReleased)), nil
}
func reservedUniqueNameAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, name string) (aggregate *es_models.Aggregate, err error) {
aggregate, err = aggCreator.NewAggregate(ctx, name, model.OrgNameAggregate, model.OrgVersion, 0)
if resourceOwner != "" {
aggregate, err = aggCreator.NewAggregate(ctx, name, model.OrgNameAggregate, model.OrgVersion, 0, es_models.OverwriteResourceOwner(resourceOwner))
@@ -180,17 +215,103 @@ func uniqueNameAggregate(ctx context.Context, aggCreator *es_models.AggregateCre
return nil, err
}
return aggregate.SetPrecondition(OrgNameUniqueQuery(name), isReservedValidation(aggregate, model.OrgNameReserved)), nil
return aggregate.SetPrecondition(OrgNameUniqueQuery(name), isEventValidation(aggregate, model.OrgNameReserved)), nil
}
func isReservedValidation(aggregate *es_models.Aggregate, resevedEventType es_models.EventType) func(...*es_models.Event) error {
func releasedUniqueNameAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, name string) (aggregate *es_models.Aggregate, err error) {
aggregate, err = aggCreator.NewAggregate(ctx, name, model.OrgNameAggregate, model.OrgVersion, 0)
if resourceOwner != "" {
aggregate, err = aggCreator.NewAggregate(ctx, name, model.OrgNameAggregate, model.OrgVersion, 0, es_models.OverwriteResourceOwner(resourceOwner))
}
if err != nil {
return nil, err
}
aggregate, err = aggregate.AppendEvent(model.OrgNameReleased, nil)
if err != nil {
return nil, err
}
return aggregate.SetPrecondition(OrgNameUniqueQuery(name), isEventValidation(aggregate, model.OrgNameReleased)), nil
}
func OrgDomainAddedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Org, domain *model.OrgDomain) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if domain == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-OSid3", "domain should not be nil")
}
agg, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.OrgDomainAdded, domain)
}
}
func OrgDomainVerifiedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Org, domain *model.OrgDomain) func(ctx context.Context) ([]*es_models.Aggregate, error) {
return func(ctx context.Context) ([]*es_models.Aggregate, error) {
if domain == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-DHs7s", "domain should not be nil")
}
agg, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
aggregates := make([]*es_models.Aggregate, 0, 2)
agg, err = agg.AppendEvent(model.OrgDomainVerified, domain)
if err != nil {
return nil, err
}
domainAgregate, err := reservedUniqueDomainAggregate(ctx, aggCreator, existing.AggregateID, domain.Domain)
if err != nil {
return nil, err
}
aggregates = append(aggregates, domainAgregate)
return append(aggregates, agg), nil
}
}
func OrgDomainSetPrimaryAggregate(aggCreator *es_models.AggregateCreator, existing *model.Org, domain *model.OrgDomain) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if domain == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-PSw3j", "domain should not be nil")
}
agg, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.OrgDomainPrimarySet, domain)
}
}
func OrgDomainRemovedAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existing *model.Org, domain *model.OrgDomain) ([]*es_models.Aggregate, error) {
if domain == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-si8dW", "domain should not be nil")
}
aggregates := make([]*es_models.Aggregate, 0, 2)
agg, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
agg, err = agg.AppendEvent(model.OrgDomainRemoved, domain)
if err != nil {
return nil, err
}
aggregates = append(aggregates, agg)
domainAgregate, err := releasedUniqueDomainAggregate(ctx, aggCreator, existing.AggregateID, domain.Domain)
if err != nil {
return nil, err
}
return append(aggregates, domainAgregate), nil
}
func isEventValidation(aggregate *es_models.Aggregate, eventType es_models.EventType) func(...*es_models.Event) error {
return func(events ...*es_models.Event) error {
if len(events) == 0 {
aggregate.PreviousSequence = 0
return nil
}
if events[0].Type == resevedEventType {
return errors.ThrowPreconditionFailed(nil, "EVENT-eJQqe", "org already reseved")
if events[0].Type == eventType {
return errors.ThrowPreconditionFailedf(nil, "EVENT-eJQqe", "user is already %v", eventType)
}
aggregate.PreviousSequence = events[0].Sequence
return nil

View File

@@ -0,0 +1,48 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
func OrgIamPolicyAddedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Org, policy *model.OrgIamPolicy) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if policy == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-i9sJS", "policy should not be nil")
}
agg, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.OrgIamPolicyAdded, policy)
}
}
func OrgIamPolicyChangedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Org, policy *model.OrgIamPolicy) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if policy == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-9Ksie", "policy should not be nil")
}
agg, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
changes := existing.OrgIamPolicy.Changes(policy)
if len(changes) == 0 {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Js6Vs", "no changes")
}
return agg.AppendEvent(model.OrgIamPolicyChanged, changes)
}
}
func OrgIamPolicyRemovedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Org) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
agg, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.OrgIamPolicyRemoved, nil)
}
}

View File

@@ -0,0 +1,237 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/api/auth"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
"testing"
)
func TestOrgIamPolicyAddedAggregates(t *testing.T) {
type res struct {
eventsCount int
eventType es_models.EventType
isErr func(error) bool
}
type args struct {
ctx context.Context
aggCreator *es_models.AggregateCreator
org *model.Org
policy *model.OrgIamPolicy
}
tests := []struct {
name string
args args
res res
}{
{
name: "no policy error",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
{
name: "policy successful",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
org: &model.Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
},
policy: &model.OrgIamPolicy{
Description: "description",
UserLoginMustBeDomain: true,
},
},
res: res{
eventsCount: 1,
eventType: model.OrgIamPolicyAdded,
isErr: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg := OrgIamPolicyAddedAggregate(tt.args.aggCreator, tt.args.org, tt.args.policy)
got, err := agg(tt.args.ctx)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got %T: %v", err, err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && got.Events[0].Type != tt.res.eventType {
t.Errorf("OrgIamPolicyAddedAggregate() event type = %v, wanted count %v", got.Events[0].Type, tt.res.eventType)
}
if tt.res.isErr == nil && len(got.Events) != tt.res.eventsCount {
t.Errorf("OrgIamPolicyAddedAggregate() event count = %d, wanted count %d", len(got.Events), tt.res.eventsCount)
}
})
}
}
func TestOrgIamPolicyChangedAggregates(t *testing.T) {
type res struct {
eventsCount int
eventType es_models.EventType
isErr func(error) bool
}
type args struct {
ctx context.Context
aggCreator *es_models.AggregateCreator
org *model.Org
policy *model.OrgIamPolicy
}
tests := []struct {
name string
args args
res res
}{
{
name: "no policy error",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
{
name: "policy successful",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
org: &model.Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
OrgIamPolicy: &model.OrgIamPolicy{
Description: "description",
UserLoginMustBeDomain: true,
},
},
policy: &model.OrgIamPolicy{
Description: "description",
UserLoginMustBeDomain: false,
},
},
res: res{
eventsCount: 1,
eventType: model.OrgIamPolicyChanged,
isErr: nil,
},
},
{
name: "policy no changes",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
org: &model.Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
OrgIamPolicy: &model.OrgIamPolicy{
Description: "description",
UserLoginMustBeDomain: true,
},
},
policy: &model.OrgIamPolicy{
Description: "description",
UserLoginMustBeDomain: true,
},
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg := OrgIamPolicyChangedAggregate(tt.args.aggCreator, tt.args.org, tt.args.policy)
got, err := agg(tt.args.ctx)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got %T: %v", err, err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && got.Events[0].Type != tt.res.eventType {
t.Errorf("OrgIamPolicyChangedAggregate() event type = %v, wanted count %v", got.Events[0].Type, tt.res.eventType)
}
if tt.res.isErr == nil && len(got.Events) != tt.res.eventsCount {
t.Errorf("OrgIamPolicyChangedAggregate() event count = %d, wanted count %d", len(got.Events), tt.res.eventsCount)
}
})
}
}
func TestOrgIamPolicyRemovedAggregates(t *testing.T) {
type res struct {
eventsCount int
eventType es_models.EventType
isErr func(error) bool
}
type args struct {
ctx context.Context
aggCreator *es_models.AggregateCreator
org *model.Org
}
tests := []struct {
name string
args args
res res
}{
{
name: "policy successful",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
org: &model.Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
OrgIamPolicy: &model.OrgIamPolicy{
Description: "description",
UserLoginMustBeDomain: true,
},
},
},
res: res{
eventsCount: 1,
eventType: model.OrgIamPolicyRemoved,
isErr: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg := OrgIamPolicyRemovedAggregate(tt.args.aggCreator, tt.args.org)
got, err := agg(tt.args.ctx)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got %T: %v", err, err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && got.Events[0].Type != tt.res.eventType {
t.Errorf("OrgIamPolicyChangedAggregate() event type = %v, wanted count %v", got.Events[0].Type, tt.res.eventType)
}
if tt.res.isErr == nil && len(got.Events) != tt.res.eventsCount {
t.Errorf("OrgIamPolicyChangedAggregate() event count = %d, wanted count %d", len(got.Events), tt.res.eventsCount)
}
})
}
}

View File

@@ -79,7 +79,7 @@ func Test_isReservedValidation(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
validate := isReservedValidation(tt.args.aggregate, tt.args.eventType)
validate := isEventValidation(tt.args.aggregate, tt.args.eventType)
err := validate(tt.args.Events...)
@@ -142,7 +142,7 @@ func Test_uniqueNameAggregate(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := uniqueNameAggregate(tt.args.ctx, tt.args.aggCreator, "", tt.args.orgName)
got, err := reservedUniqueNameAggregate(tt.args.ctx, tt.args.aggCreator, "", tt.args.orgName)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got: %v", err)
}
@@ -198,7 +198,7 @@ func Test_uniqueDomainAggregate(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := uniqueDomainAggregate(tt.args.ctx, tt.args.aggCreator, "", tt.args.orgDomain)
got, err := reservedUniqueDomainAggregate(tt.args.ctx, tt.args.aggCreator, "", tt.args.orgDomain)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got: %v", err)
}
@@ -425,47 +425,18 @@ func TestOrgUpdateAggregates(t *testing.T) {
AggregateID: "sdaf",
Sequence: 5,
},
Domain: "caos.ch",
Name: "coas",
Name: "coas",
},
updated: &model.Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
Domain: "caos.ch",
Name: "caos",
Name: "caos",
},
},
res: res{
aggregateCount: 2,
isErr: nil,
},
},
{
name: "domain changed",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
existing: &model.Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
Domain: "caos.swiss",
Name: "caos",
},
updated: &model.Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
Domain: "caos.ch",
Name: "caos",
},
},
res: res{
aggregateCount: 2,
aggregateCount: 3,
isErr: nil,
},
},
@@ -523,17 +494,16 @@ func TestOrgCreatedAggregates(t *testing.T) {
AggregateID: "sdaf",
Sequence: 5,
},
Domain: "caos.ch",
Name: "caos",
Name: "caos",
},
},
res: res{
aggregateCount: 3,
aggregateCount: 2,
isErr: nil,
},
},
{
name: "no domain error",
name: "org with domain successful",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
@@ -543,11 +513,14 @@ func TestOrgCreatedAggregates(t *testing.T) {
Sequence: 5,
},
Name: "caos",
Domains: []*model.OrgDomain{&model.OrgDomain{
Domain: "caos.ch",
}},
},
},
res: res{
aggregateCount: 2,
isErr: errors.IsPreconditionFailed,
isErr: nil,
},
},
{
@@ -560,7 +533,6 @@ func TestOrgCreatedAggregates(t *testing.T) {
AggregateID: "sdaf",
Sequence: 5,
},
Domain: "caos.ch",
},
},
res: res{
@@ -584,3 +556,270 @@ func TestOrgCreatedAggregates(t *testing.T) {
})
}
}
func TestOrgDomainAddedAggregates(t *testing.T) {
type res struct {
eventCount int
eventType es_models.EventType
isErr func(error) bool
}
type args struct {
ctx context.Context
aggCreator *es_models.AggregateCreator
org *model.Org
domain *model.OrgDomain
}
tests := []struct {
name string
args args
res res
}{
{
name: "no domain error",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
{
name: "domain successful",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
org: &model.Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
},
domain: &model.OrgDomain{
Domain: "caos.ch",
},
},
res: res{
eventCount: 1,
eventType: model.OrgDomainAdded,
isErr: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg := OrgDomainAddedAggregate(tt.args.aggCreator, tt.args.org, tt.args.domain)
got, err := agg(tt.args.ctx)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got %T: %v", err, err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && got.Events[0].Type != tt.res.eventType {
t.Errorf("OrgDomainAddedAggregate() event type = %v, wanted count %v", got.Events[0].Type, tt.res.eventType)
}
if tt.res.isErr == nil && len(got.Events) != tt.res.eventCount {
t.Errorf("OrgDomainAddedAggregate() event count = %v, wanted count %v", len(got.Events), tt.res.eventCount)
}
})
}
}
func TestOrgDomainVerifiedAggregates(t *testing.T) {
type res struct {
aggregateCount int
isErr func(error) bool
}
type args struct {
ctx context.Context
aggCreator *es_models.AggregateCreator
org *model.Org
domain *model.OrgDomain
}
tests := []struct {
name string
args args
res res
}{
{
name: "no domain error",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
{
name: "domain successful",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
org: &model.Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
},
domain: &model.OrgDomain{
Domain: "caos.ch",
},
},
res: res{
aggregateCount: 2,
isErr: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg := OrgDomainVerifiedAggregate(tt.args.aggCreator, tt.args.org, tt.args.domain)
got, err := agg(tt.args.ctx)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got %T: %v", err, err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && len(got) != tt.res.aggregateCount {
t.Errorf("OrgDomainVerifiedAggregate() aggregate count = %d, wanted count %d", len(got), tt.res.aggregateCount)
}
})
}
}
func TestOrgDomainSetPrimaryAggregates(t *testing.T) {
type res struct {
eventsCount int
eventType es_models.EventType
isErr func(error) bool
}
type args struct {
ctx context.Context
aggCreator *es_models.AggregateCreator
org *model.Org
domain *model.OrgDomain
}
tests := []struct {
name string
args args
res res
}{
{
name: "no domain error",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
},
res: res{
isErr: errors.IsPreconditionFailed,
},
},
{
name: "domain successful",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
org: &model.Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
},
domain: &model.OrgDomain{
Domain: "caos.ch",
},
},
res: res{
eventsCount: 1,
eventType: model.OrgDomainPrimarySet,
isErr: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg := OrgDomainSetPrimaryAggregate(tt.args.aggCreator, tt.args.org, tt.args.domain)
got, err := agg(tt.args.ctx)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got %T: %v", err, err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && got.Events[0].Type != tt.res.eventType {
t.Errorf("OrgDomainSetPrimaryAggregate() event type = %v, wanted count %v", got.Events[0].Type, tt.res.eventType)
}
if tt.res.isErr == nil && len(got.Events) != tt.res.eventsCount {
t.Errorf("OrgDomainSetPrimaryAggregate() event count = %d, wanted count %d", len(got.Events), tt.res.eventsCount)
}
})
}
}
func TestOrgDomainRemovedAggregates(t *testing.T) {
type res struct {
aggregateCount int
isErr func(error) bool
}
type args struct {
ctx context.Context
aggCreator *es_models.AggregateCreator
org *model.Org
domain *model.OrgDomain
}
tests := []struct {
name string
args args
res res
}{
{
name: "no domain error",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
},
res: res{
aggregateCount: 0,
isErr: errors.IsPreconditionFailed,
},
},
{
name: "domain successful",
args: args{
ctx: auth.NewMockContext("org", "user"),
aggCreator: es_models.NewAggregateCreator("test"),
org: &model.Org{
ObjectRoot: es_models.ObjectRoot{
AggregateID: "sdaf",
Sequence: 5,
},
},
domain: &model.OrgDomain{
Domain: "caos.ch",
},
},
res: res{
aggregateCount: 2,
isErr: nil,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := OrgDomainRemovedAggregate(tt.args.ctx, tt.args.aggCreator, tt.args.org, tt.args.domain)
if tt.res.isErr == nil && err != nil {
t.Errorf("no error expected got %T: %v", err, err)
}
if tt.res.isErr != nil && !tt.res.isErr(err) {
t.Errorf("wrong error got %T: %v", err, err)
}
if tt.res.isErr == nil && len(got) != tt.res.aggregateCount {
t.Errorf("OrgDomainRemovedAggregate() aggregate count = %d, wanted count %d", len(got), tt.res.aggregateCount)
}
})
}
}

View File

@@ -1,4 +1,4 @@
package view
package model
import (
"encoding/json"
@@ -33,7 +33,6 @@ type OrgView struct {
func OrgFromModel(org *org_model.OrgView) *OrgView {
return &OrgView{
Domain: org.Domain,
ChangeDate: org.ChangeDate,
CreationDate: org.CreationDate,
ID: org.ID,
@@ -46,7 +45,6 @@ func OrgFromModel(org *org_model.OrgView) *OrgView {
func OrgToModel(org *OrgView) *org_model.OrgView {
return &org_model.OrgView{
Domain: org.Domain,
ChangeDate: org.ChangeDate,
CreationDate: org.CreationDate,
ID: org.ID,

View File

@@ -0,0 +1,87 @@
package model
import (
"encoding/json"
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/org/model"
es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
"time"
)
const (
OrgDomainKeyOrgID = "org_id"
OrgDomainKeyDomain = "domain"
OrgDomainKeyVerified = "verified"
OrgDomainKeyPrimary = "primary_domain"
)
type OrgDomainView struct {
Domain string `json:"domain" gorm:"column:domain;primary_key"`
OrgID string `json:"-" gorm:"column:org_id;primary_key"`
Verified bool `json:"-" gorm:"column:verified"`
Primary bool `json:"-" gorm:"column:primary_domain"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
}
func OrgDomainViewFromModel(domain *model.OrgDomainView) *OrgDomainView {
return &OrgDomainView{
OrgID: domain.OrgID,
Domain: domain.Domain,
Primary: domain.Primary,
Verified: domain.Verified,
CreationDate: domain.CreationDate,
ChangeDate: domain.ChangeDate,
}
}
func OrgDomainToModel(domain *OrgDomainView) *model.OrgDomainView {
return &model.OrgDomainView{
OrgID: domain.OrgID,
Domain: domain.Domain,
Primary: domain.Primary,
Verified: domain.Verified,
CreationDate: domain.CreationDate,
ChangeDate: domain.ChangeDate,
}
}
func OrgDomainsToModel(domain []*OrgDomainView) []*model.OrgDomainView {
result := make([]*model.OrgDomainView, len(domain))
for i, r := range domain {
result[i] = OrgDomainToModel(r)
}
return result
}
func (d *OrgDomainView) AppendEvent(event *models.Event) (err error) {
d.Sequence = event.Sequence
d.ChangeDate = event.CreationDate
switch event.Type {
case es_model.OrgDomainAdded:
d.setRootData(event)
d.CreationDate = event.CreationDate
err = d.SetData(event)
case es_model.OrgDomainVerified:
d.Verified = true
case es_model.OrgDomainPrimarySet:
d.Primary = true
}
return err
}
func (r *OrgDomainView) setRootData(event *models.Event) {
r.OrgID = event.AggregateID
}
func (r *OrgDomainView) SetData(event *models.Event) error {
if err := json.Unmarshal(event.Data, r); err != nil {
logging.Log("EVEN-sj4Sf").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(err, "MODEL-lub6s", "Could not unmarshal data")
}
return nil
}

View File

@@ -0,0 +1,65 @@
package model
import (
global_model "github.com/caos/zitadel/internal/model"
org_model "github.com/caos/zitadel/internal/org/model"
"github.com/caos/zitadel/internal/view"
)
type OrgDomainSearchRequest org_model.OrgDomainSearchRequest
type OrgDomainSearchQuery org_model.OrgDomainSearchQuery
type OrgDomainSearchKey org_model.OrgDomainSearchKey
func (req OrgDomainSearchRequest) GetLimit() uint64 {
return req.Limit
}
func (req OrgDomainSearchRequest) GetOffset() uint64 {
return req.Offset
}
func (req OrgDomainSearchRequest) GetSortingColumn() view.ColumnKey {
if req.SortingColumn == org_model.ORGDOMAINSEARCHKEY_UNSPECIFIED {
return nil
}
return OrgDomainSearchKey(req.SortingColumn)
}
func (req OrgDomainSearchRequest) GetAsc() bool {
return req.Asc
}
func (req OrgDomainSearchRequest) GetQueries() []view.SearchQuery {
result := make([]view.SearchQuery, len(req.Queries))
for i, q := range req.Queries {
result[i] = OrgDomainSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
}
return result
}
func (req OrgDomainSearchQuery) GetKey() view.ColumnKey {
return OrgDomainSearchKey(req.Key)
}
func (req OrgDomainSearchQuery) GetMethod() global_model.SearchMethod {
return req.Method
}
func (req OrgDomainSearchQuery) GetValue() interface{} {
return req.Value
}
func (key OrgDomainSearchKey) ToColumnName() string {
switch org_model.OrgDomainSearchKey(key) {
case org_model.ORGDOMAINSEARCHKEY_DOMAIN:
return OrgDomainKeyDomain
case org_model.ORGDOMAINSEARCHKEY_ORG_ID:
return OrgDomainKeyOrgID
case org_model.ORGDOMAINSEARCHKEY_VERIFIED:
return OrgDomainKeyVerified
case org_model.ORGDOMAINSEARCHKEY_PRIMARY:
return OrgDomainKeyPrimary
default:
return ""
}
}

View File

@@ -1,4 +1,4 @@
package view
package model
import (
"encoding/json"

View File

@@ -1,4 +1,4 @@
package view
package model
import (
global_model "github.com/caos/zitadel/internal/model"

View File

@@ -1,4 +1,4 @@
package view
package model
import (
global_model "github.com/caos/zitadel/internal/model"

View File

@@ -0,0 +1,64 @@
package view
import (
global_model "github.com/caos/zitadel/internal/model"
org_model "github.com/caos/zitadel/internal/org/model"
"github.com/caos/zitadel/internal/org/repository/view/model"
"github.com/caos/zitadel/internal/view"
"github.com/jinzhu/gorm"
)
func OrgDomainByOrgIDAndDomain(db *gorm.DB, table, orgID, domain string) (*model.OrgDomainView, error) {
domainView := new(model.OrgDomainView)
orgIDQuery := &model.OrgDomainSearchQuery{Key: org_model.ORGDOMAINSEARCHKEY_ORG_ID, Value: orgID, Method: global_model.SEARCHMETHOD_EQUALS}
domainQuery := &model.OrgDomainSearchQuery{Key: org_model.ORGDOMAINSEARCHKEY_DOMAIN, Value: domain, Method: global_model.SEARCHMETHOD_EQUALS}
query := view.PrepareGetByQuery(table, orgIDQuery, domainQuery)
err := query(db, domainView)
return domainView, err
}
func VerifiedOrgDomain(db *gorm.DB, table, domain string) (*model.OrgDomainView, error) {
domainView := new(model.OrgDomainView)
domainQuery := &model.OrgDomainSearchQuery{Key: org_model.ORGDOMAINSEARCHKEY_DOMAIN, Value: domain, Method: global_model.SEARCHMETHOD_EQUALS}
verifiedQuery := &model.OrgDomainSearchQuery{Key: org_model.ORGDOMAINSEARCHKEY_VERIFIED, Value: true, Method: global_model.SEARCHMETHOD_EQUALS}
query := view.PrepareGetByQuery(table, domainQuery, verifiedQuery)
err := query(db, domainView)
return domainView, err
}
func SearchOrgDomains(db *gorm.DB, table string, req *org_model.OrgDomainSearchRequest) ([]*model.OrgDomainView, int, error) {
members := make([]*model.OrgDomainView, 0)
query := view.PrepareSearchQuery(table, model.OrgDomainSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &members)
if err != nil {
return nil, 0, err
}
return members, count, nil
}
func OrgDomainsByOrgID(db *gorm.DB, table string, orgID string) ([]*model.OrgDomainView, error) {
domains := make([]*model.OrgDomainView, 0)
queries := []*org_model.OrgDomainSearchQuery{
{
Key: org_model.ORGDOMAINSEARCHKEY_ORG_ID,
Value: orgID,
Method: global_model.SEARCHMETHOD_EQUALS,
},
}
query := view.PrepareSearchQuery(table, model.OrgDomainSearchRequest{Queries: queries})
_, err := query(db, &domains)
if err != nil {
return nil, err
}
return domains, nil
}
func PutOrgDomain(db *gorm.DB, table string, role *model.OrgDomainView) error {
save := view.PrepareSave(table)
return save(db, role)
}
func DeleteOrgDomain(db *gorm.DB, table, domain string) error {
delete := view.PrepareDeleteByKey(table, model.OrgSearchKey(org_model.ORGDOMAINSEARCHKEY_DOMAIN), domain)
return delete(db)
}

View File

@@ -3,31 +3,32 @@ package view
import (
global_model "github.com/caos/zitadel/internal/model"
org_model "github.com/caos/zitadel/internal/org/model"
"github.com/caos/zitadel/internal/org/repository/view/model"
"github.com/caos/zitadel/internal/view"
"github.com/jinzhu/gorm"
)
func OrgMemberByIDs(db *gorm.DB, table, orgID, userID string) (*OrgMemberView, error) {
member := new(OrgMemberView)
func OrgMemberByIDs(db *gorm.DB, table, orgID, userID string) (*model.OrgMemberView, error) {
member := new(model.OrgMemberView)
orgIDQuery := &OrgMemberSearchQuery{Key: org_model.ORGMEMBERSEARCHKEY_ORG_ID, Value: orgID, Method: global_model.SEARCHMETHOD_EQUALS}
userIDQuery := &OrgMemberSearchQuery{Key: org_model.ORGMEMBERSEARCHKEY_USER_ID, Value: userID, Method: global_model.SEARCHMETHOD_EQUALS}
orgIDQuery := &model.OrgMemberSearchQuery{Key: org_model.ORGMEMBERSEARCHKEY_ORG_ID, Value: orgID, Method: global_model.SEARCHMETHOD_EQUALS}
userIDQuery := &model.OrgMemberSearchQuery{Key: org_model.ORGMEMBERSEARCHKEY_USER_ID, Value: userID, Method: global_model.SEARCHMETHOD_EQUALS}
query := view.PrepareGetByQuery(table, orgIDQuery, userIDQuery)
err := query(db, member)
return member, err
}
func SearchOrgMembers(db *gorm.DB, table string, req *org_model.OrgMemberSearchRequest) ([]*OrgMemberView, int, error) {
members := make([]*OrgMemberView, 0)
query := view.PrepareSearchQuery(table, OrgMemberSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
func SearchOrgMembers(db *gorm.DB, table string, req *org_model.OrgMemberSearchRequest) ([]*model.OrgMemberView, int, error) {
members := make([]*model.OrgMemberView, 0)
query := view.PrepareSearchQuery(table, model.OrgMemberSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &members)
if err != nil {
return nil, 0, err
}
return members, count, nil
}
func OrgMembersByUserID(db *gorm.DB, table string, userID string) ([]*OrgMemberView, error) {
members := make([]*OrgMemberView, 0)
func OrgMembersByUserID(db *gorm.DB, table string, userID string) ([]*model.OrgMemberView, error) {
members := make([]*model.OrgMemberView, 0)
queries := []*org_model.OrgMemberSearchQuery{
{
Key: org_model.ORGMEMBERSEARCHKEY_USER_ID,
@@ -35,7 +36,7 @@ func OrgMembersByUserID(db *gorm.DB, table string, userID string) ([]*OrgMemberV
Method: global_model.SEARCHMETHOD_EQUALS,
},
}
query := view.PrepareSearchQuery(table, OrgMemberSearchRequest{Queries: queries})
query := view.PrepareSearchQuery(table, model.OrgMemberSearchRequest{Queries: queries})
_, err := query(db, &members)
if err != nil {
return nil, err
@@ -43,7 +44,7 @@ func OrgMembersByUserID(db *gorm.DB, table string, userID string) ([]*OrgMemberV
return members, nil
}
func PutOrgMember(db *gorm.DB, table string, role *OrgMemberView) error {
func PutOrgMember(db *gorm.DB, table string, role *model.OrgMemberView) error {
save := view.PrepareSave(table)
return save(db, role)
}

View File

@@ -2,20 +2,21 @@ package view
import (
org_model "github.com/caos/zitadel/internal/org/model"
"github.com/caos/zitadel/internal/org/repository/view/model"
"github.com/caos/zitadel/internal/view"
"github.com/jinzhu/gorm"
)
func OrgByID(db *gorm.DB, table, orgID string) (*OrgView, error) {
org := new(OrgView)
query := view.PrepareGetByKey(table, OrgSearchKey(org_model.ORGSEARCHKEY_ORG_ID), orgID)
func OrgByID(db *gorm.DB, table, orgID string) (*model.OrgView, error) {
org := new(model.OrgView)
query := view.PrepareGetByKey(table, model.OrgSearchKey(org_model.ORGSEARCHKEY_ORG_ID), orgID)
err := query(db, org)
return org, err
}
func SearchOrgs(db *gorm.DB, table string, req *org_model.OrgSearchRequest) ([]*OrgView, int, error) {
orgs := make([]*OrgView, 0)
query := view.PrepareSearchQuery(table, OrgSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
func SearchOrgs(db *gorm.DB, table string, req *org_model.OrgSearchRequest) ([]*model.OrgView, int, error) {
orgs := make([]*model.OrgView, 0)
query := view.PrepareSearchQuery(table, model.OrgSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
count, err := query(db, &orgs)
if err != nil {
return nil, 0, err
@@ -23,19 +24,12 @@ func SearchOrgs(db *gorm.DB, table string, req *org_model.OrgSearchRequest) ([]*
return orgs, count, nil
}
func GetGlobalOrgByDomain(db *gorm.DB, table, domain string) (*OrgView, error) {
org := new(OrgView)
query := view.PrepareGetByKey(table, OrgSearchKey(org_model.ORGSEARCHKEY_ORG_DOMAIN), domain)
err := query(db, org)
return org, err
}
func PutOrg(db *gorm.DB, table string, org *OrgView) error {
func PutOrg(db *gorm.DB, table string, org *model.OrgView) error {
save := view.PrepareSave(table)
return save(db, org)
}
func DeleteOrg(db *gorm.DB, table, orgID string) error {
delete := view.PrepareDeleteByKey(table, OrgSearchKey(org_model.ORGSEARCHKEY_ORG_ID), orgID)
delete := view.PrepareDeleteByKey(table, model.OrgSearchKey(org_model.ORGSEARCHKEY_ORG_ID), orgID)
return delete(db)
}