diff --git a/cmd/zitadel/setup.yaml b/cmd/zitadel/setup.yaml index b2134f22e4..1e08acb25a 100644 --- a/cmd/zitadel/setup.yaml +++ b/cmd/zitadel/setup.yaml @@ -30,49 +30,45 @@ SetUp: AllowUsernamePassword: true AllowRegister: true AllowExternalIdp: true -# Orgs: -# - Name: 'Global' -# Domain: 'global.caos.ch' -# Default: true -# OrgIamPolicy: true -# Users: -# - FirstName: 'Global Org' -# LastName: 'Administrator' -# UserName: 'zitadel-global-org-admin@caos.ch' -# Email: 'zitadel-global-org-admin@caos.ch' -# Password: 'Password1!' -# Owners: -# - 'zitadel-global-org-admin@caos.ch' -# - Name: 'CAOS AG' -# Domain: 'caos.ch' -# Users: -# - FirstName: 'Zitadel' -# LastName: 'Administrator' -# UserName: 'zitadel-admin' -# Email: 'zitadel-admin@caos.ch' -# Password: 'Password1!' -# Owners: -# - 'zitadel-admin@caos.ch' -# Projects: -# - Name: 'Zitadel' -# OIDCApps: -# - Name: 'Management-API' -# - Name: 'Auth-API' -# - Name: 'Admin-API' -# - Name: 'Zitadel Console' -# RedirectUris: -# - '$ZITADEL_CONSOLE/auth/callback' -# PostLogoutRedirectUris: -# - '$ZITADEL_CONSOLE/signedout' -# ResponseTypes: -# - $ZITADEL_CONSOLE_RESPONSE_TYPE -# GrantTypes: -# - $ZITADEL_CONSOLE_GRANT_TYPE -# ApplicationType: 'USER_AGENT' -# AuthMethodType: 'NONE' -# DevMode: $ZITADEL_CONSOLE_DEV_MODE -# Owners: -# - 'zitadel-admin@caos.ch' + Orgs: + - Name: 'Global' + Domain: 'global.caos.ch' + Default: true + OrgIamPolicy: true + Owner: + FirstName: 'Global Org' + LastName: 'Administrator' + UserName: 'zitadel-global-org-admin@caos.ch' + Email: 'zitadel-global-org-admin@caos.ch' + Password: 'Password1!' + - Name: 'CAOS AG' + Domain: 'caos.ch' + Owner: + FirstName: 'Zitadel' + LastName: 'Administrator' + UserName: 'zitadel-admin' + Email: 'zitadel-admin@caos.ch' + Password: 'Password1!' + Projects: + - Name: 'Zitadel' + OIDCApps: + - Name: 'Management-API' + - Name: 'Auth-API' + - Name: 'Admin-API' + - Name: 'Zitadel Console' + RedirectUris: + - '$ZITADEL_CONSOLE/auth/callback' + PostLogoutRedirectUris: + - '$ZITADEL_CONSOLE/signedout' + ResponseTypes: + - $ZITADEL_CONSOLE_RESPONSE_TYPE + GrantTypes: + - $ZITADEL_CONSOLE_GRANT_TYPE + ApplicationType: 'USER_AGENT' + AuthMethodType: 'NONE' + DevMode: $ZITADEL_CONSOLE_DEV_MODE + Owners: + - 'zitadel-admin@caos.ch' Step2: DefaultPasswordComplexityPolicy: MinLength: 8 diff --git a/internal/admin/repository/org.go b/internal/admin/repository/org.go index e41038dd1b..315f94594b 100644 --- a/internal/admin/repository/org.go +++ b/internal/admin/repository/org.go @@ -2,21 +2,17 @@ package repository import ( "context" + iam_model "github.com/caos/zitadel/internal/iam/model" - admin_model "github.com/caos/zitadel/internal/admin/model" org_model "github.com/caos/zitadel/internal/org/model" ) type OrgRepository interface { - SetUpOrg(context.Context, *admin_model.SetupOrg) (*admin_model.SetupOrg, error) IsOrgUnique(ctx context.Context, name, domain string) (bool, error) OrgByID(ctx context.Context, id string) (*org_model.Org, error) SearchOrgs(ctx context.Context, query *org_model.OrgSearchRequest) (*org_model.OrgSearchResult, error) GetOrgIAMPolicyByID(ctx context.Context, id string) (*iam_model.OrgIAMPolicyView, error) - GetDefaultOrgIAMPolicy(ctx context.Context) (*iam_model.OrgIAMPolicyView, error) - CreateOrgIAMPolicy(ctx context.Context, policy *iam_model.OrgIAMPolicy) (*iam_model.OrgIAMPolicy, error) - ChangeOrgIAMPolicy(ctx context.Context, policy *iam_model.OrgIAMPolicy) (*iam_model.OrgIAMPolicy, error) RemoveOrgIAMPolicy(ctx context.Context, id string) error } diff --git a/internal/api/grpc/admin/iam_member_converter.go b/internal/api/grpc/admin/iam_member_converter.go index abaa1f4baf..7e3918e858 100644 --- a/internal/api/grpc/admin/iam_member_converter.go +++ b/internal/api/grpc/admin/iam_member_converter.go @@ -10,21 +10,21 @@ import ( "github.com/caos/zitadel/pkg/grpc/admin" ) -func addIamMemberToDomain(member *admin.AddIamMemberRequest) *domain.IAMMember { - return &domain.IAMMember{ +func addIamMemberToDomain(member *admin.AddIamMemberRequest) *domain.Member { + return &domain.Member{ UserID: member.UserId, Roles: member.Roles, } } -func changeIamMemberToDomain(member *admin.ChangeIamMemberRequest) *domain.IAMMember { - return &domain.IAMMember{ +func changeIamMemberToDomain(member *admin.ChangeIamMemberRequest) *domain.Member { + return &domain.Member{ UserID: member.UserId, Roles: member.Roles, } } -func iamMemberFromDomain(member *domain.IAMMember) *admin.IamMember { +func iamMemberFromDomain(member *domain.Member) *admin.IamMember { creationDate, err := ptypes.TimestampProto(member.CreationDate) logging.Log("GRPC-Lsp76").OnError(err).Debug("date parse failed") diff --git a/internal/api/grpc/admin/org.go b/internal/api/grpc/admin/org.go index d813fa0a51..8d0f840fb2 100644 --- a/internal/api/grpc/admin/org.go +++ b/internal/api/grpc/admin/org.go @@ -30,12 +30,12 @@ func (s *Server) IsOrgUnique(ctx context.Context, request *admin.UniqueOrgReques return &admin.UniqueOrgResponse{IsUnique: isUnique}, err } -func (s *Server) SetUpOrg(ctx context.Context, orgSetUp *admin.OrgSetUpRequest) (_ *admin.OrgSetUpResponse, err error) { - setUp, err := s.org.SetUpOrg(ctx, setUpRequestToModel(orgSetUp)) +func (s *Server) SetUpOrg(ctx context.Context, orgSetUp *admin.OrgSetUpRequest) (_ *empty.Empty, err error) { + err = s.command.SetUpOrg(ctx, orgCreateRequestToDomain(orgSetUp.Org), userCreateRequestToDomain(orgSetUp.User)) if err != nil { return nil, err } - return setUpOrgResponseFromModel(setUp), err + return &empty.Empty{}, nil } func (s *Server) GetDefaultOrgIamPolicy(ctx context.Context, _ *empty.Empty) (_ *admin.OrgIamPolicyView, err error) { diff --git a/internal/api/grpc/admin/org_converter.go b/internal/api/grpc/admin/org_converter.go index 5084273fe4..803431355b 100644 --- a/internal/api/grpc/admin/org_converter.go +++ b/internal/api/grpc/admin/org_converter.go @@ -2,9 +2,10 @@ package admin import ( "github.com/caos/logging" + "github.com/golang/protobuf/ptypes" + iam_model "github.com/caos/zitadel/internal/iam/model" "github.com/caos/zitadel/internal/v2/domain" - "github.com/golang/protobuf/ptypes" admin_model "github.com/caos/zitadel/internal/admin/model" "github.com/caos/zitadel/internal/eventstore/models" @@ -14,20 +15,13 @@ import ( "github.com/caos/zitadel/pkg/grpc/admin" ) -func setUpRequestToModel(setUp *admin.OrgSetUpRequest) *admin_model.SetupOrg { - return &admin_model.SetupOrg{ - Org: orgCreateRequestToModel(setUp.Org), - User: userCreateRequestToModel(setUp.User), - } -} - -func orgCreateRequestToModel(org *admin.CreateOrgRequest) *org_model.Org { - o := &org_model.Org{ - Domains: []*org_model.OrgDomain{}, +func orgCreateRequestToDomain(org *admin.CreateOrgRequest) *domain.Org { + o := &domain.Org{ + Domains: []*domain.OrgDomain{}, Name: org.Name, } if org.Domain != "" { - o.Domains = append(o.Domains, &org_model.OrgDomain{Domain: org.Domain}) + o.Domains = append(o.Domains, &domain.OrgDomain{Domain: org.Domain}) } return o diff --git a/internal/api/grpc/admin/user_converter.go b/internal/api/grpc/admin/user_converter.go index c8ce2accc1..f023da3ea4 100644 --- a/internal/api/grpc/admin/user_converter.go +++ b/internal/api/grpc/admin/user_converter.go @@ -3,11 +3,83 @@ package admin import ( "github.com/caos/logging" usr_model "github.com/caos/zitadel/internal/user/model" + "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/pkg/grpc/admin" "github.com/golang/protobuf/ptypes" "golang.org/x/text/language" ) +func userCreateRequestToDomain(user *admin.CreateUserRequest) *domain.User { + var human *domain.Human + var machine *domain.Machine + + if h := user.GetHuman(); h != nil { + human = humanCreateToDomain(h) + } + if m := user.GetMachine(); m != nil { + machine = machineCreateToDomain(m) + } + + return &domain.User{ + UserName: user.UserName, + Human: human, + Machine: machine, + } +} + +func humanCreateToDomain(u *admin.CreateHumanRequest) *domain.Human { + preferredLanguage, err := language.Parse(u.PreferredLanguage) + logging.Log("GRPC-1ouQc").OnError(err).Debug("language malformed") + + human := &domain.Human{ + Profile: &domain.Profile{ + FirstName: u.FirstName, + LastName: u.LastName, + NickName: u.NickName, + PreferredLanguage: preferredLanguage, + Gender: genderToDomain(u.Gender), + }, + Email: &domain.Email{ + EmailAddress: u.Email, + IsEmailVerified: u.IsEmailVerified, + }, + Address: &domain.Address{ + Country: u.Country, + Locality: u.Locality, + PostalCode: u.PostalCode, + Region: u.Region, + StreetAddress: u.StreetAddress, + }, + } + if u.Password != "" { + human.Password = &domain.Password{SecretString: u.Password} + } + if u.Phone != "" { + human.Phone = &domain.Phone{PhoneNumber: u.Phone, IsPhoneVerified: u.IsPhoneVerified} + } + return human +} + +func genderToDomain(gender admin.Gender) domain.Gender { + switch gender { + case admin.Gender_GENDER_FEMALE: + return domain.GenderFemale + case admin.Gender_GENDER_MALE: + return domain.GenderMale + case admin.Gender_GENDER_DIVERSE: + return domain.GenderDiverse + default: + return domain.GenderUnspecified + } +} + +func machineCreateToDomain(machine *admin.CreateMachineRequest) *domain.Machine { + return &domain.Machine{ + Name: machine.Name, + Description: machine.Description, + } +} + func userCreateRequestToModel(user *admin.CreateUserRequest) *usr_model.User { var human *usr_model.Human var machine *usr_model.Machine diff --git a/internal/eventstore/v2/aggregate.go b/internal/eventstore/v2/aggregate.go index 71d8b2bfb1..f32fe76a69 100644 --- a/internal/eventstore/v2/aggregate.go +++ b/internal/eventstore/v2/aggregate.go @@ -1,6 +1,6 @@ package eventstore -type aggregater interface { +type Aggregater interface { //ID returns the aggreagte id ID() string //KeyType returns the aggregate type @@ -52,7 +52,7 @@ func AggregateFromWriteModel( } } -//Aggregate is the basic implementation of aggregater +//Aggregate is the basic implementation of Aggregater type Aggregate struct { id string `json:"-"` typ AggregateType `json:"-"` @@ -69,32 +69,32 @@ func (a *Aggregate) PushEvents(events ...EventPusher) *Aggregate { return a } -//ID implements aggregater +//ID implements Aggregater func (a *Aggregate) ID() string { return a.id } -//KeyType implements aggregater +//KeyType implements Aggregater func (a *Aggregate) Type() AggregateType { return a.typ } -//Events implements aggregater +//Events implements Aggregater func (a *Aggregate) Events() []EventPusher { return a.events } -//ResourceOwner implements aggregater +//ResourceOwner implements Aggregater func (a *Aggregate) ResourceOwner() string { return a.resourceOwner } -//Version implements aggregater +//Version implements Aggregater func (a *Aggregate) Version() Version { return a.version } -//PreviousSequence implements aggregater +//PreviousSequence implements Aggregater func (a *Aggregate) PreviousSequence() uint64 { return a.previousSequence } diff --git a/internal/eventstore/v2/eventstore.go b/internal/eventstore/v2/eventstore.go index bb4f3e862f..adff3cc133 100644 --- a/internal/eventstore/v2/eventstore.go +++ b/internal/eventstore/v2/eventstore.go @@ -37,7 +37,7 @@ func (es *Eventstore) Health(ctx context.Context) error { } //PushAggregate pushes the aggregate and reduces the new events on the aggregate -func (es *Eventstore) PushAggregate(ctx context.Context, writeModel queryReducer, aggregate aggregater) error { +func (es *Eventstore) PushAggregate(ctx context.Context, writeModel queryReducer, aggregate Aggregater) error { events, err := es.PushAggregates(ctx, aggregate) if err != nil { return err @@ -49,7 +49,7 @@ func (es *Eventstore) PushAggregate(ctx context.Context, writeModel queryReducer //PushAggregates maps the events of all aggregates to an eventstore event // based on the pushMapper -func (es *Eventstore) PushAggregates(ctx context.Context, aggregates ...aggregater) ([]EventReader, error) { +func (es *Eventstore) PushAggregates(ctx context.Context, aggregates ...Aggregater) ([]EventReader, error) { events, err := es.aggregatesToEvents(aggregates) if err != nil { return nil, err @@ -63,7 +63,7 @@ func (es *Eventstore) PushAggregates(ctx context.Context, aggregates ...aggregat return es.mapEvents(events) } -func (es *Eventstore) aggregatesToEvents(aggregates []aggregater) ([]*repository.Event, error) { +func (es *Eventstore) aggregatesToEvents(aggregates []Aggregater) ([]*repository.Event, error) { events := make([]*repository.Event, 0, len(aggregates)) for _, aggregate := range aggregates { var previousEvent *repository.Event diff --git a/internal/eventstore/v2/eventstore_test.go b/internal/eventstore/v2/eventstore_test.go index 71d5ec4e76..16a75ddec2 100644 --- a/internal/eventstore/v2/eventstore_test.go +++ b/internal/eventstore/v2/eventstore_test.go @@ -356,7 +356,7 @@ func Test_eventData(t *testing.T) { func TestEventstore_aggregatesToEvents(t *testing.T) { type args struct { - aggregates []aggregater + aggregates []Aggregater } type res struct { wantErr bool @@ -370,7 +370,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) { { name: "one aggregate one event", args: args{ - aggregates: []aggregater{ + aggregates: []Aggregater{ &testAggregate{ id: "1", events: []EventPusher{ @@ -403,7 +403,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) { { name: "one aggregate multiple events", args: args{ - aggregates: []aggregater{ + aggregates: []Aggregater{ &testAggregate{ id: "1", events: []EventPusher{ @@ -452,7 +452,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) { { name: "invalid data", args: args{ - aggregates: []aggregater{ + aggregates: []Aggregater{ &testAggregate{ id: "1", events: []EventPusher{ @@ -473,7 +473,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) { { name: "multiple aggregates", args: args{ - aggregates: []aggregater{ + aggregates: []Aggregater{ &testAggregate{ id: "1", events: []EventPusher{ @@ -614,7 +614,7 @@ func (repo *testRepo) LatestSequence(ctx context.Context, queryFactory *reposito func TestEventstore_Push(t *testing.T) { type args struct { - aggregates []aggregater + aggregates []Aggregater } type fields struct { repo *testRepo @@ -632,7 +632,7 @@ func TestEventstore_Push(t *testing.T) { { name: "one aggregate one event", args: args{ - aggregates: []aggregater{ + aggregates: []Aggregater{ &testAggregate{ id: "1", events: []EventPusher{ @@ -672,7 +672,7 @@ func TestEventstore_Push(t *testing.T) { { name: "one aggregate multiple events", args: args{ - aggregates: []aggregater{ + aggregates: []Aggregater{ &testAggregate{ id: "1", events: []EventPusher{ @@ -731,7 +731,7 @@ func TestEventstore_Push(t *testing.T) { { name: "multiple aggregates", args: args{ - aggregates: []aggregater{ + aggregates: []Aggregater{ &testAggregate{ id: "1", events: []EventPusher{ @@ -815,7 +815,7 @@ func TestEventstore_Push(t *testing.T) { { name: "push fails", args: args{ - aggregates: []aggregater{ + aggregates: []Aggregater{ &testAggregate{ id: "1", events: []EventPusher{ @@ -842,7 +842,7 @@ func TestEventstore_Push(t *testing.T) { { name: "aggreagtes to events mapping fails", args: args{ - aggregates: []aggregater{ + aggregates: []Aggregater{ &testAggregate{ id: "1", events: []EventPusher{ diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index 43b4272fb5..8bf48d0304 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -109,6 +109,8 @@ Errors: IdpNotExisting: IDP Konfiguration existiert nicht OIDCConfigInvalid: OIDC IDP Konfiguration ist ungültig IdpIsNotOIDC: IDP Konfiguration ist nicht vom Typ OIDC + Domain: + AlreadyExists: Domäne existiert bereits IDP: InvalidSearchQuery: Ungültiger Suchparameter LoginPolicy: diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index 17b2a26dc8..93512dcfed 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -109,6 +109,8 @@ Errors: IdpNotExisting: IDP configuration does not exist OIDCConfigInvalid: OIDC IDP configuration is invalid IdpIsNotOIDC: IDP configuration is not of type oidc + Domain: + AlreadyExists: Domain already exists IDP: InvalidSearchQuery: Ungültiger Suchparameter LoginPolicy: diff --git a/internal/v2/command/command.go b/internal/v2/command/command.go index d011502c5a..58bff3ba28 100644 --- a/internal/v2/command/command.go +++ b/internal/v2/command/command.go @@ -15,6 +15,7 @@ type CommandSide struct { eventstore *eventstore.Eventstore idGenerator id.Generator iamID string + iamDomain string idpConfigSecretCrypto crypto.Crypto @@ -37,6 +38,7 @@ func StartCommandSide(config *Config) (repo *CommandSide, err error) { eventstore: config.Eventstore, idGenerator: id.SonyFlakeGenerator, iamID: config.SystemDefaults.IamID, + iamDomain: config.SystemDefaults.Domain, } iam_repo.RegisterEventMappers(repo.eventstore) diff --git a/internal/v2/command/iam.go b/internal/v2/command/iam.go index 57f402b7f7..9488ad0c50 100644 --- a/internal/v2/command/iam.go +++ b/internal/v2/command/iam.go @@ -6,6 +6,7 @@ import ( "github.com/caos/zitadel/internal/v2/domain" ) +//TODO: private func (r *CommandSide) GetIAM(ctx context.Context, aggregateID string) (*domain.IAM, error) { iamWriteModel := NewIAMWriteModel(aggregateID) err := r.eventstore.FilterToQueryReducer(ctx, iamWriteModel) diff --git a/internal/v2/command/iam_converter.go b/internal/v2/command/iam_converter.go index 155e2a1657..7f00f4eab9 100644 --- a/internal/v2/command/iam_converter.go +++ b/internal/v2/command/iam_converter.go @@ -25,9 +25,9 @@ func writeModelToIAM(wm *IAMWriteModel) *domain.IAM { } } -func writeModelToMember(writeModel *IAMMemberWriteModel) *domain.IAMMember { - return &domain.IAMMember{ - ObjectRoot: writeModelToObjectRoot(writeModel.MemberWriteModel.WriteModel), +func memberWriteModelToMember(writeModel *MemberWriteModel) *domain.Member { + return &domain.Member{ + ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel), Roles: writeModel.Roles, UserID: writeModel.UserID, } diff --git a/internal/v2/command/iam_member.go b/internal/v2/command/iam_member.go index 71dee33da3..cc24835706 100644 --- a/internal/v2/command/iam_member.go +++ b/internal/v2/command/iam_member.go @@ -11,7 +11,7 @@ import ( iam_repo "github.com/caos/zitadel/internal/v2/repository/iam" ) -func (r *CommandSide) AddIAMMember(ctx context.Context, member *domain.IAMMember) (*domain.IAMMember, error) { +func (r *CommandSide) AddIAMMember(ctx context.Context, member *domain.Member) (*domain.Member, error) { //TODO: check if roles valid if !member.IsValid() { @@ -35,11 +35,11 @@ func (r *CommandSide) AddIAMMember(ctx context.Context, member *domain.IAMMember return nil, err } - return writeModelToMember(addedMember), nil + return memberWriteModelToMember(&addedMember.MemberWriteModel), nil } //ChangeIAMMember updates an existing member -func (r *CommandSide) ChangeIAMMember(ctx context.Context, member *domain.IAMMember) (*domain.IAMMember, error) { +func (r *CommandSide) ChangeIAMMember(ctx context.Context, member *domain.Member) (*domain.Member, error) { //TODO: check if roles valid if !member.IsValid() { @@ -67,7 +67,7 @@ func (r *CommandSide) ChangeIAMMember(ctx context.Context, member *domain.IAMMem return nil, err } - return writeModelToMember(existingMember), nil + return memberWriteModelToMember(&existingMember.MemberWriteModel), nil } func (r *CommandSide) RemoveIAMMember(ctx context.Context, userID string) error { diff --git a/internal/v2/command/iam_policy_org_iam.go b/internal/v2/command/iam_policy_org_iam.go index 0410a3ec82..56fa1528b9 100644 --- a/internal/v2/command/iam_policy_org_iam.go +++ b/internal/v2/command/iam_policy_org_iam.go @@ -8,17 +8,6 @@ import ( iam_repo "github.com/caos/zitadel/internal/v2/repository/iam" ) -func (r *CommandSide) GetDefaultOrgIAMPolicy(ctx context.Context) (*domain.OrgIAMPolicy, error) { - policyWriteModel := NewIAMOrgIAMPolicyWriteModel(r.iamID) - err := r.eventstore.FilterToQueryReducer(ctx, policyWriteModel) - if err != nil { - return nil, err - } - policy := writeModelToOrgIAMPolicy(policyWriteModel) - policy.Default = true - return policy, nil -} - func (r *CommandSide) AddDefaultOrgIAMPolicy(ctx context.Context, policy *domain.OrgIAMPolicy) (*domain.OrgIAMPolicy, error) { policy.AggregateID = r.iamID addedPolicy := NewIAMOrgIAMPolicyWriteModel(policy.AggregateID) @@ -75,6 +64,16 @@ func (r *CommandSide) ChangeDefaultOrgIAMPolicy(ctx context.Context, policy *dom return writeModelToOrgIAMPolicy(existingPolicy), nil } +func (r *CommandSide) getDefaultOrgIAMPolicy(ctx context.Context) (*domain.OrgIAMPolicy, error) { + policyWriteModel, err := r.defaultOrgIAMPolicyWriteModelByID(ctx, r.iamID) + if err != nil { + return nil, err + } + policy := writeModelToOrgIAMPolicy(policyWriteModel) + policy.Default = true + return policy, nil +} + func (r *CommandSide) defaultOrgIAMPolicyWriteModelByID(ctx context.Context, iamID string) (policy *IAMOrgIAMPolicyWriteModel, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/v2/command/org.go b/internal/v2/command/org.go new file mode 100644 index 0000000000..8334be1c2f --- /dev/null +++ b/internal/v2/command/org.go @@ -0,0 +1,72 @@ +package command + +import ( + "context" + + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/org" + "github.com/caos/zitadel/internal/v2/repository/user" +) + +func (r *CommandSide) SetUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.User) error { + orgAgg, userAgg, orgMemberAgg, err := r.setUpOrg(ctx, organisation, admin) + if err != nil { + return err + } + + _, err = r.eventstore.PushAggregates(ctx, orgAgg, userAgg, orgMemberAgg) + return err +} + +func (r *CommandSide) setUpOrg(ctx context.Context, organisation *domain.Org, admin *domain.User) (*org.Aggregate, *user.Aggregate, *org.Aggregate, error) { + orgAgg, _, err := r.addOrg(ctx, organisation) + if err != nil { + return nil, nil, nil, err + } + + userAgg, _, err := r.addHuman(ctx, orgAgg.ID(), admin.UserName, admin.Human) + if err != nil { + return nil, nil, nil, err + } + + addedMember := NewOrgMemberWriteModel(orgAgg.ID(), userAgg.ID()) + orgMemberAgg := OrgAggregateFromWriteModel(&addedMember.WriteModel) + err = r.addOrgMember(ctx, orgMemberAgg, addedMember, domain.NewMember(orgMemberAgg.ID(), userAgg.ID(), domain.OrgOwnerRole)) //TODO: correct? + if err != nil { + return nil, nil, nil, err + } + return orgAgg, userAgg, orgMemberAgg, nil +} + +func (r *CommandSide) addOrg(ctx context.Context, organisation *domain.Org) (_ *org.Aggregate, _ *OrgWriteModel, err error) { + if organisation == nil || !organisation.IsValid() { + return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMM-deLSk", "Errors.Org.Invalid") + } + + organisation.AggregateID, err = r.idGenerator.Next() + if err != nil { + return nil, nil, caos_errs.ThrowInternal(err, "COMMA-OwciI", "Errors.Internal") + } + organisation.AddIAMDomain(r.iamDomain) + addedOrg := NewOrgWriteModel(organisation.AggregateID) + + orgAgg := OrgAggregateFromWriteModel(&addedOrg.WriteModel) + //TODO: uniqueness org name + orgAgg.PushEvents(org.NewOrgAddedEvent(ctx, organisation.Name)) + for _, orgDomain := range organisation.Domains { + if err := r.addOrgDomain(ctx, orgAgg, NewOrgDomainWriteModel(orgAgg.ID(), orgDomain.Domain), orgDomain); err != nil { + return nil, nil, err + } + } + return orgAgg, addedOrg, nil +} + +func (r *CommandSide) getOrgWriteModelByID(ctx context.Context, orgID string) (*OrgWriteModel, error) { + orgWriteModel := NewOrgWriteModel(orgID) + err := r.eventstore.FilterToQueryReducer(ctx, orgWriteModel) + if err != nil { + return nil, err + } + return orgWriteModel, nil +} diff --git a/internal/v2/command/org_converter.go b/internal/v2/command/org_converter.go index 9d3e0f120e..2b10e0d925 100644 --- a/internal/v2/command/org_converter.go +++ b/internal/v2/command/org_converter.go @@ -4,6 +4,14 @@ import ( "github.com/caos/zitadel/internal/v2/domain" ) +func orgWriteModelToOrg(wm *OrgWriteModel) *domain.Org { + return &domain.Org{ + ObjectRoot: writeModelToObjectRoot(wm.WriteModel), + Name: wm.Name, + State: wm.State, + } +} + func orgWriteModelToOrgIAMPolicy(wm *ORGOrgIAMPolicyWriteModel) *domain.OrgIAMPolicy { return &domain.OrgIAMPolicy{ ObjectRoot: writeModelToObjectRoot(wm.PolicyOrgIAMWriteModel.WriteModel), @@ -21,3 +29,14 @@ func orgWriteModelToPasswordComplexityPolicy(wm *OrgPasswordComplexityPolicyWrit HasSymbol: wm.HasSymbol, } } + +func orgDomainWriteModelToOrgDomain(wm *OrgDomainWriteModel) *domain.OrgDomain { + return &domain.OrgDomain{ + ObjectRoot: writeModelToObjectRoot(wm.WriteModel), + Domain: wm.Domain, + Primary: wm.Primary, + Verified: wm.Verified, + ValidationType: wm.ValidationType, + ValidationCode: wm.ValidationCode, + } +} diff --git a/internal/v2/command/org_domain.go b/internal/v2/command/org_domain.go new file mode 100644 index 0000000000..2b773d10ad --- /dev/null +++ b/internal/v2/command/org_domain.go @@ -0,0 +1,43 @@ +package command + +import ( + "context" + + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/org" +) + +func (r *CommandSide) AddOrgDomain(ctx context.Context, orgDomain *domain.OrgDomain) (*domain.OrgDomain, error) { + domainWriteModel := NewOrgDomainWriteModel(orgDomain.AggregateID, orgDomain.Domain) + orgAgg := OrgAggregateFromWriteModel(&domainWriteModel.WriteModel) + err := r.addOrgDomain(ctx, orgAgg, domainWriteModel, orgDomain) + if err != nil { + return nil, err + } + err = r.eventstore.PushAggregate(ctx, domainWriteModel, orgAgg) + if err != nil { + return nil, err + } + return orgDomainWriteModelToOrgDomain(domainWriteModel), nil +} + +func (r *CommandSide) addOrgDomain(ctx context.Context, orgAgg *org.Aggregate, addedDomain *OrgDomainWriteModel, orgDomain *domain.OrgDomain) error { + err := r.eventstore.FilterToQueryReducer(ctx, addedDomain) + if err != nil { + return err + } + if addedDomain.State == domain.OrgDomainStateActive { + return caos_errs.ThrowAlreadyExists(nil, "COMMA-Bd2jj", "Errors.Org.Domain.AlreadyExists") + } + orgAgg.PushEvents(org.NewDomainAddedEvent(ctx, orgDomain.Domain)) + if orgDomain.Verified { + //TODO: uniqueness verified domain + //TODO: users with verified domain -> domain claimed + orgAgg.PushEvents(org.NewDomainVerifiedEvent(ctx, orgDomain.Domain)) + } + if orgDomain.Primary { + orgAgg.PushEvents(org.NewDomainPrimarySetEvent(ctx, orgDomain.Domain)) + } + return nil +} diff --git a/internal/v2/command/org_domain_model.go b/internal/v2/command/org_domain_model.go new file mode 100644 index 0000000000..1f06471c4f --- /dev/null +++ b/internal/v2/command/org_domain_model.go @@ -0,0 +1,90 @@ +package command + +import ( + "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/org" +) + +type OrgDomainWriteModel struct { + eventstore.WriteModel + + Domain string + ValidationType domain.OrgDomainValidationType + ValidationCode *crypto.CryptoValue + Primary bool + Verified bool + + State domain.OrgDomainState +} + +func NewOrgDomainWriteModel(orgID string, domain string) *OrgDomainWriteModel { + return &OrgDomainWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: orgID, + }, + Domain: domain, + } +} + +func (wm *OrgDomainWriteModel) AppendEvents(events ...eventstore.EventReader) { + for _, event := range events { + switch e := event.(type) { + case *org.DomainAddedEvent: + if e.Domain != wm.Domain { + continue + } + wm.WriteModel.AppendEvents(e) + case *org.DomainVerificationAddedEvent: + if e.Domain != wm.Domain { + continue + } + wm.WriteModel.AppendEvents(e) + case *org.DomainVerificationFailedEvent: + if e.Domain != wm.Domain { + continue + } + wm.WriteModel.AppendEvents(e) + case *org.DomainVerifiedEvent: + if e.Domain != wm.Domain { + continue + } + wm.WriteModel.AppendEvents(e) + case *org.DomainPrimarySetEvent: + wm.WriteModel.AppendEvents(e) + case *org.DomainRemovedEvent: + if e.Domain != wm.Domain { + continue + } + wm.WriteModel.AppendEvents(e) + } + } +} + +func (wm *OrgDomainWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *org.DomainAddedEvent: + wm.Domain = e.Domain + wm.State = domain.OrgDomainStateActive + case *org.DomainVerificationAddedEvent: + wm.ValidationType = e.ValidationType + wm.ValidationCode = e.ValidationCode + case *org.DomainVerificationFailedEvent: + //TODO: not handled in v1 + case *org.DomainVerifiedEvent: + wm.Verified = true + case *org.DomainPrimarySetEvent: + wm.Primary = e.Domain == wm.Domain + case *org.DomainRemovedEvent: + wm.State = domain.OrgDomainStateRemoved + } + } + return nil +} + +func (wm *OrgDomainWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, org.AggregateType). + AggregateIDs(wm.AggregateID) +} diff --git a/internal/v2/command/org_member.go b/internal/v2/command/org_member.go new file mode 100644 index 0000000000..f377d7a7e4 --- /dev/null +++ b/internal/v2/command/org_member.go @@ -0,0 +1,112 @@ +package command + +import ( + "context" + "reflect" + + "github.com/caos/zitadel/internal/errors" + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/telemetry/tracing" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/org" +) + +func (r *CommandSide) AddOrgMember(ctx context.Context, member *domain.Member) (*domain.Member, error) { + addedMember := NewOrgMemberWriteModel(member.AggregateID, member.UserID) + orgAgg := OrgAggregateFromWriteModel(&addedMember.WriteModel) + err := r.addOrgMember(ctx, orgAgg, addedMember, member) + if err != nil { + return nil, err + } + + err = r.eventstore.PushAggregate(ctx, addedMember, orgAgg) + if err != nil { + return nil, err + } + + return memberWriteModelToMember(&addedMember.MemberWriteModel), nil +} + +func (r *CommandSide) addOrgMember(ctx context.Context, orgAgg *org.Aggregate, addedMember *OrgMemberWriteModel, member *domain.Member) error { + //TODO: check if roles valid + + if !member.IsValid() { + return caos_errs.ThrowPreconditionFailed(nil, "Org-W8m4l", "Errors.Org.MemberInvalid") + } + + err := r.eventstore.FilterToQueryReducer(ctx, addedMember) + if err != nil { + return err + } + if addedMember.State == domain.MemberStateActive { + return errors.ThrowAlreadyExists(nil, "Org-PtXi1", "Errors.Org.Member.AlreadyExists") + } + + orgAgg.PushEvents(org.NewMemberAddedEvent(ctx, member.UserID, member.Roles...)) + + return nil +} + +//ChangeOrgMember updates an existing member +func (r *CommandSide) ChangeOrgMember(ctx context.Context, member *domain.Member) (*domain.Member, error) { + //TODO: check if roles valid + + if !member.IsValid() { + return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-LiaZi", "Errors.Org.MemberInvalid") + } + + existingMember, err := r.orgMemberWriteModelByID(ctx, member.AggregateID, member.UserID) + if err != nil { + return nil, err + } + + if reflect.DeepEqual(existingMember.Roles, member.Roles) { + return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-LiaZi", "Errors.Org.Member.RolesNotChanged") + } + orgAgg := OrgAggregateFromWriteModel(&existingMember.MemberWriteModel.WriteModel) + orgAgg.PushEvents(org.NewMemberChangedEvent(ctx, member.UserID, member.Roles...)) + + events, err := r.eventstore.PushAggregates(ctx, orgAgg) + if err != nil { + return nil, err + } + + existingMember.AppendEvents(events...) + if err = existingMember.Reduce(); err != nil { + return nil, err + } + + return memberWriteModelToMember(&existingMember.MemberWriteModel), nil +} + +func (r *CommandSide) RemoveOrgMember(ctx context.Context, orgID, userID string) error { + m, err := r.orgMemberWriteModelByID(ctx, orgID, userID) + if err != nil && !errors.IsNotFound(err) { + return err + } + if errors.IsNotFound(err) { + return nil + } + + orgAgg := OrgAggregateFromWriteModel(&m.MemberWriteModel.WriteModel) + orgAgg.PushEvents(org.NewMemberRemovedEvent(ctx, userID)) + + return r.eventstore.PushAggregate(ctx, m, orgAgg) +} + +func (r *CommandSide) orgMemberWriteModelByID(ctx context.Context, orgID, userID string) (member *OrgMemberWriteModel, err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + writeModel := NewOrgMemberWriteModel(orgID, userID) + err = r.eventstore.FilterToQueryReducer(ctx, writeModel) + if err != nil { + return nil, err + } + + if writeModel.State == domain.MemberStateUnspecified || writeModel.State == domain.MemberStateRemoved { + return nil, errors.ThrowNotFound(nil, "Org-D8JxR", "Errors.NotFound") + } + + return writeModel, nil +} diff --git a/internal/v2/command/org_member_model.go b/internal/v2/command/org_member_model.go index 97b0e272f8..7549c6a57a 100644 --- a/internal/v2/command/org_member_model.go +++ b/internal/v2/command/org_member_model.go @@ -1,5 +1,52 @@ package command +import ( + "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/repository/org" +) + type OrgMemberWriteModel struct { MemberWriteModel } + +func NewOrgMemberWriteModel(orgID, userID string) *OrgMemberWriteModel { + return &OrgMemberWriteModel{ + MemberWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: orgID, + }, + UserID: userID, + }, + } +} + +func (wm *OrgMemberWriteModel) AppendEvents(events ...eventstore.EventReader) { + for _, event := range events { + switch e := event.(type) { + case *org.MemberAddedEvent: + if e.UserID != wm.MemberWriteModel.UserID { + continue + } + wm.MemberWriteModel.AppendEvents(&e.MemberAddedEvent) + case *org.MemberChangedEvent: + if e.UserID != wm.MemberWriteModel.UserID { + continue + } + wm.MemberWriteModel.AppendEvents(&e.MemberChangedEvent) + case *org.MemberRemovedEvent: + if e.UserID != wm.MemberWriteModel.UserID { + continue + } + wm.MemberWriteModel.AppendEvents(&e.MemberRemovedEvent) + } + } +} + +func (wm *OrgMemberWriteModel) Reduce() error { + return wm.MemberWriteModel.Reduce() +} + +func (wm *OrgMemberWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, org.AggregateType). + AggregateIDs(wm.MemberWriteModel.AggregateID) +} diff --git a/internal/v2/command/org_model.go b/internal/v2/command/org_model.go index 88c29b1eb2..5c6cc52846 100644 --- a/internal/v2/command/org_model.go +++ b/internal/v2/command/org_model.go @@ -2,10 +2,56 @@ package command import ( "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" + "github.com/caos/zitadel/internal/v2/repository/iam" "github.com/caos/zitadel/internal/v2/repository/org" ) -func ORGAggregateFromWriteModel(wm *eventstore.WriteModel) *org.Aggregate { +type OrgWriteModel struct { + eventstore.WriteModel + + Name string + State domain.OrgState +} + +func NewOrgWriteModel(orgID string) *OrgWriteModel { + return &OrgWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: orgID, + }, + } +} + +func (wm *OrgWriteModel) AppendEvents(events ...eventstore.EventReader) { + wm.WriteModel.AppendEvents(events...) + for _, event := range events { + switch e := event.(type) { + case *org.OrgAddedEvent, + *iam.LabelPolicyChangedEvent: + wm.WriteModel.AppendEvents(e) + } + } +} + +func (wm *OrgWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *org.OrgAddedEvent: + wm.Name = e.Name + wm.State = domain.OrgStateActive + case *org.OrgChangedEvent: + wm.Name = e.Name + } + } + return nil +} + +func (wm *OrgWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, org.AggregateType). + AggregateIDs(wm.AggregateID) +} + +func OrgAggregateFromWriteModel(wm *eventstore.WriteModel) *org.Aggregate { return &org.Aggregate{ Aggregate: *eventstore.AggregateFromWriteModel(wm, org.AggregateType, org.AggregateVersion), } diff --git a/internal/v2/command/org_policy_org_iam.go b/internal/v2/command/org_policy_org_iam.go index caecfbbf4a..8b6c200f9a 100644 --- a/internal/v2/command/org_policy_org_iam.go +++ b/internal/v2/command/org_policy_org_iam.go @@ -5,32 +5,20 @@ import ( caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/telemetry/tracing" "github.com/caos/zitadel/internal/v2/domain" - iam_repo "github.com/caos/zitadel/internal/v2/repository/iam" + "github.com/caos/zitadel/internal/v2/repository/org" ) -func (r *CommandSide) GetOrgIAMPolicy(ctx context.Context, orgID string) (*domain.OrgIAMPolicy, error) { - policy := NewORGOrgIAMPolicyWriteModel(orgID) - err := r.eventstore.FilterToQueryReducer(ctx, policy) - if err != nil { - return nil, err - } - if policy.State == domain.PolicyStateActive { - return orgWriteModelToOrgIAMPolicy(policy), nil - } - return r.GetDefaultOrgIAMPolicy(ctx) -} - func (r *CommandSide) AddOrgIAMPolicy(ctx context.Context, policy *domain.OrgIAMPolicy) (*domain.OrgIAMPolicy, error) { addedPolicy := NewORGOrgIAMPolicyWriteModel(policy.AggregateID) - err := r.eventstore.FilterToQueryReducer(ctx, addedPolicy) + orgAgg := OrgAggregateFromWriteModel(&addedPolicy.PolicyOrgIAMWriteModel.WriteModel) + err := r.addOrgIAMPolicy(ctx, orgAgg, addedPolicy, policy) if err != nil { return nil, err } if addedPolicy.State == domain.PolicyStateActive { return nil, caos_errs.ThrowAlreadyExists(nil, "ORG-5M0ds", "Errors.Org.OrgIAMPolicy.AlreadyExists") } - orgAgg := ORGAggregateFromWriteModel(&addedPolicy.PolicyOrgIAMWriteModel.WriteModel) - orgAgg.PushEvents(iam_repo.NewOrgIAMPolicyAddedEvent(ctx, policy.UserLoginMustBeDomain)) + orgAgg.PushEvents(org.NewOrgIAMPolicyAddedEvent(ctx, policy.UserLoginMustBeDomain)) err = r.eventstore.PushAggregate(ctx, addedPolicy, orgAgg) if err != nil { @@ -40,6 +28,18 @@ func (r *CommandSide) AddOrgIAMPolicy(ctx context.Context, policy *domain.OrgIAM return orgWriteModelToOrgIAMPolicy(addedPolicy), nil } +func (r *CommandSide) addOrgIAMPolicy(ctx context.Context, orgAgg *org.Aggregate, addedPolicy *ORGOrgIAMPolicyWriteModel, policy *domain.OrgIAMPolicy) error { + err := r.eventstore.FilterToQueryReducer(ctx, addedPolicy) + if err != nil { + return err + } + if addedPolicy.State == domain.PolicyStateActive { + return caos_errs.ThrowAlreadyExists(nil, "ORG-5M0ds", "Errors.Org.OrgIAMPolicy.AlreadyExists") + } + orgAgg.PushEvents(org.NewOrgIAMPolicyAddedEvent(ctx, policy.UserLoginMustBeDomain)) + return nil +} + func (r *CommandSide) ChangeOrgIAMPolicy(ctx context.Context, policy *domain.OrgIAMPolicy) (*domain.OrgIAMPolicy, error) { existingPolicy, err := r.orgIAMPolicyWriteModelByID(ctx, policy.AggregateID) if err != nil { @@ -54,7 +54,7 @@ func (r *CommandSide) ChangeOrgIAMPolicy(ctx context.Context, policy *domain.Org return nil, caos_errs.ThrowPreconditionFailed(nil, "ORG-3M9ds", "Errors.Org.LabelPolicy.NotChanged") } - orgAgg := ORGAggregateFromWriteModel(&existingPolicy.PolicyOrgIAMWriteModel.WriteModel) + orgAgg := OrgAggregateFromWriteModel(&existingPolicy.PolicyOrgIAMWriteModel.WriteModel) orgAgg.PushEvents(changedEvent) err = r.eventstore.PushAggregate(ctx, existingPolicy, orgAgg) @@ -65,6 +65,17 @@ func (r *CommandSide) ChangeOrgIAMPolicy(ctx context.Context, policy *domain.Org return orgWriteModelToOrgIAMPolicy(existingPolicy), nil } +func (r *CommandSide) getOrgIAMPolicy(ctx context.Context, orgID string) (*domain.OrgIAMPolicy, error) { + policy, err := r.orgIAMPolicyWriteModelByID(ctx, orgID) + if err != nil { + return nil, err + } + if policy.State == domain.PolicyStateActive { + return orgWriteModelToOrgIAMPolicy(policy), nil + } + return r.getDefaultOrgIAMPolicy(ctx) +} + func (r *CommandSide) orgIAMPolicyWriteModelByID(ctx context.Context, orgID string) (policy *ORGOrgIAMPolicyWriteModel, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/v2/command/setup_step1.go b/internal/v2/command/setup_step1.go index 26f71d2756..2a3a070437 100644 --- a/internal/v2/command/setup_step1.go +++ b/internal/v2/command/setup_step1.go @@ -4,6 +4,7 @@ import ( "context" caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v2" "github.com/caos/zitadel/internal/v2/domain" iam_repo "github.com/caos/zitadel/internal/v2/repository/iam" ) @@ -48,8 +49,7 @@ type Org struct { Name string Domain string OrgIamPolicy bool - Users []User - Owners []string + Owner User Projects []Project } @@ -84,25 +84,60 @@ func (r *CommandSide) SetupStep1(ctx context.Context, iamID string, step1 *Step1 return err } //create orgs - //create projects - //create applications + aggregates := make([]eventstore.Aggregater, 0) + for _, organisation := range step1.Orgs { + orgAgg, userAgg, orgMemberAgg, err := r.setUpOrg(ctx, + &domain.Org{ + Name: organisation.Name, + Domains: []*domain.OrgDomain{{Domain: organisation.Domain}}, + }, + &domain.User{ + UserName: organisation.Owner.UserName, + Human: &domain.Human{ + 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, + }, + }, + }) + if err != nil { + return err + } + if organisation.OrgIamPolicy { + err = r.addOrgIAMPolicy(ctx, orgAgg, NewORGOrgIAMPolicyWriteModel(orgAgg.ID()), &domain.OrgIAMPolicy{UserLoginMustBeDomain: false}) + if err != nil { + return err + } + } + aggregates = append(aggregates, orgAgg, userAgg, orgMemberAgg) + //projects + //create applications + } + //set iam owners //set global org //set iam project id /*aggregates: - iam: - default login policy - iam owner - org: - default - caos - zitadel + iam: + default login policy + iam owner + org: + default + caos + zitadel */ iamAgg.PushEvents(iam_repo.NewSetupStepDoneEvent(ctx, domain.Step1)) - _, err = r.eventstore.PushAggregates(ctx, iamAgg) + _, err = r.eventstore.PushAggregates(ctx, append(aggregates, iamAgg)...) if err != nil { return caos_errs.ThrowPreconditionFailed(nil, "EVENT-Gr2hh", "Setup Step1 failed") } diff --git a/internal/v2/command/user.go b/internal/v2/command/user.go index 000e2dc36e..c52b9b4197 100644 --- a/internal/v2/command/user.go +++ b/internal/v2/command/user.go @@ -59,7 +59,7 @@ func (r *CommandSide) ChangeUsername(ctx context.Context, orgID, userID, userNam return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9fs", "Errors.User.UsernameNotChanged") } - orgIAMPolicy, err := r.GetOrgIAMPolicy(ctx, orgID) + orgIAMPolicy, err := r.getOrgIAMPolicy(ctx, orgID) if err != nil { return err } diff --git a/internal/v2/command/user_human.go b/internal/v2/command/user_human.go index 022c36ec69..7ad30b7aac 100644 --- a/internal/v2/command/user_human.go +++ b/internal/v2/command/user_human.go @@ -2,37 +2,51 @@ package command import ( "context" + caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/user" ) func (r *CommandSide) AddHuman(ctx context.Context, orgID, username string, human *domain.Human) (*domain.Human, error) { + userAgg, addedHuman, err := r.addHuman(ctx, orgID, username, human) + if err != nil { + return nil, err + } + err = r.eventstore.PushAggregate(ctx, addedHuman, userAgg) + if err != nil { + return nil, err + } + + return writeModelToHuman(addedHuman), nil +} + +func (r *CommandSide) addHuman(ctx context.Context, orgID, username string, human *domain.Human) (*user.Aggregate, *HumanWriteModel, error) { if !human.IsValid() { - return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M90d", "Errors.User.Invalid") + return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M90d", "Errors.User.Invalid") } userID, err := r.idGenerator.Next() if err != nil { - return nil, err + return nil, nil, err } human.AggregateID = userID - orgIAMPolicy, err := r.GetOrgIAMPolicy(ctx, orgID) + orgIAMPolicy, err := r.getOrgIAMPolicy(ctx, orgID) if err != nil { - return nil, err + return nil, nil, err } pwPolicy, err := r.GetOrgPasswordComplexityPolicy(ctx, orgID) if err != nil { - return nil, err + return nil, nil, err } addedHuman := NewHumanWriteModel(human.AggregateID) //TODO: Check Unique Username if err := human.CheckOrgIAMPolicy(username, orgIAMPolicy); err != nil { - return nil, err + return nil, nil, err } human.SetNamesAsDisplayname() if err := human.HashPasswordIfExisting(pwPolicy, r.userPasswordAlg, true); err != nil { - return nil, err + return nil, nil, err } userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel) @@ -66,7 +80,7 @@ func (r *CommandSide) AddHuman(ctx context.Context, orgID, username string, huma if human.IsInitialState() { initCode, err := domain.NewInitUserCode(r.initializeUserCode) if err != nil { - return nil, err + return nil, nil, err } user.NewHumanInitialCodeAddedEvent(ctx, initCode.Code, initCode.Expiry) } @@ -76,19 +90,14 @@ func (r *CommandSide) AddHuman(ctx context.Context, orgID, username string, huma if human.Phone != nil && human.PhoneNumber != "" && !human.IsPhoneVerified { phoneCode, err := domain.NewPhoneCode(r.phoneVerificationCode) if err != nil { - return nil, err + return nil, nil, err } user.NewHumanPhoneCodeAddedEvent(ctx, phoneCode.Code, phoneCode.Expiry) } else if human.Phone != nil && human.PhoneNumber != "" && human.IsPhoneVerified { userAgg.PushEvents(user.NewHumanPhoneVerifiedEvent(ctx)) } - err = r.eventstore.PushAggregate(ctx, addedHuman, userAgg) - if err != nil { - return nil, err - } - - return writeModelToHuman(addedHuman), nil + return userAgg, addedHuman, nil } func (r *CommandSide) RegisterHuman(ctx context.Context, orgID, username string, human *domain.Human, externalIDP *domain.ExternalIDP) (*domain.Human, error) { @@ -100,7 +109,7 @@ func (r *CommandSide) RegisterHuman(ctx context.Context, orgID, username string, return nil, err } human.AggregateID = userID - orgIAMPolicy, err := r.GetOrgIAMPolicy(ctx, orgID) + orgIAMPolicy, err := r.getOrgIAMPolicy(ctx, orgID) if err != nil { return nil, err } diff --git a/internal/v2/command/user_machine.go b/internal/v2/command/user_machine.go index 69d911ac77..4952e083b0 100644 --- a/internal/v2/command/user_machine.go +++ b/internal/v2/command/user_machine.go @@ -18,7 +18,7 @@ func (r *CommandSide) AddMachine(ctx context.Context, orgID, username string, ma } //TODO: Check Unique username machine.AggregateID = userID - orgIAMPolicy, err := r.GetOrgIAMPolicy(ctx, orgID) + orgIAMPolicy, err := r.getOrgIAMPolicy(ctx, orgID) if err != nil { return nil, err } diff --git a/internal/v2/domain/human_password.go b/internal/v2/domain/human_password.go index 136a5299f4..275e7a943a 100644 --- a/internal/v2/domain/human_password.go +++ b/internal/v2/domain/human_password.go @@ -15,6 +15,12 @@ type Password struct { ChangeRequired bool } +func NewPassword(password string) *Password { + return &Password{ + SecretString: password, + } +} + type PasswordCode struct { es_models.ObjectRoot diff --git a/internal/v2/domain/iam.go b/internal/v2/domain/iam.go index 2f67273de7..3f522ffa8b 100644 --- a/internal/v2/domain/iam.go +++ b/internal/v2/domain/iam.go @@ -11,7 +11,7 @@ type IAM struct { IAMProjectID string SetUpDone Step SetUpStarted Step - Members []*IAMMember + Members []*Member IDPs []*IDPConfig DefaultLoginPolicy *LoginPolicy DefaultLabelPolicy *LabelPolicy diff --git a/internal/v2/domain/iam_member.go b/internal/v2/domain/member.go similarity index 63% rename from internal/v2/domain/iam_member.go rename to internal/v2/domain/member.go index 2a0f9115b1..d58402daec 100644 --- a/internal/v2/domain/iam_member.go +++ b/internal/v2/domain/member.go @@ -4,14 +4,24 @@ import ( es_models "github.com/caos/zitadel/internal/eventstore/models" ) -type IAMMember struct { +type Member struct { es_models.ObjectRoot UserID string Roles []string } -func (i *IAMMember) IsValid() bool { +func NewMember(aggregateID, userID string, roles ...string) *Member { + return &Member{ + ObjectRoot: es_models.ObjectRoot{ + AggregateID: aggregateID, + }, + UserID: userID, + Roles: roles, + } +} + +func (i *Member) IsValid() bool { return i.AggregateID != "" && i.UserID != "" && len(i.Roles) != 0 } diff --git a/internal/v2/domain/org.go b/internal/v2/domain/org.go new file mode 100644 index 0000000000..d9e1af38d1 --- /dev/null +++ b/internal/v2/domain/org.go @@ -0,0 +1,43 @@ +package domain + +import ( + "strings" + + "github.com/caos/zitadel/internal/eventstore/models" +) + +type Org struct { + models.ObjectRoot + + State OrgState + Name string + + Domains []*OrgDomain + Members []*Member + OrgIamPolicy *OrgIAMPolicy + LoginPolicy *LoginPolicy + LabelPolicy *LabelPolicy + PasswordComplexityPolicy *PasswordComplexityPolicy + PasswordAgePolicy *PasswordAgePolicy + PasswordLockoutPolicy *PasswordLockoutPolicy + IDPs []*IDPConfig +} + +func (o *Org) IsValid() bool { + return o.Name != "" +} + +func (o *Org) AddIAMDomain(iamDomain string) { + o.Domains = append(o.Domains, &OrgDomain{Domain: o.nameForDomain(iamDomain), Verified: true, Primary: true}) +} + +func (o *Org) nameForDomain(iamDomain string) string { + return strings.ToLower(strings.ReplaceAll(o.Name, " ", "-") + "." + iamDomain) +} + +type OrgState int32 + +const ( + OrgStateActive OrgState = iota + OrgStateInactive +) diff --git a/internal/v2/domain/org_domain.go b/internal/v2/domain/org_domain.go new file mode 100644 index 0000000000..2fa229159b --- /dev/null +++ b/internal/v2/domain/org_domain.go @@ -0,0 +1,38 @@ +package domain + +import ( + "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/eventstore/models" +) + +type OrgDomain struct { + models.ObjectRoot + + Domain string + Primary bool + Verified bool + ValidationType OrgDomainValidationType + ValidationCode *crypto.CryptoValue +} + +type OrgDomainValidationType int32 + +const ( + OrgDomainValidationTypeUnspecified OrgDomainValidationType = iota + OrgDomainValidationTypeHTTP + OrgDomainValidationTypeDNS +) + +type OrgDomainState int32 + +const ( + OrgDomainStateUnspecified OrgDomainState = iota + OrgDomainStateActive + OrgDomainStateRemoved + + orgDomainStateCount +) + +func (f OrgDomainState) Valid() bool { + return f >= 0 && f < orgDomainStateCount +} diff --git a/internal/v2/domain/roles.go b/internal/v2/domain/roles.go new file mode 100644 index 0000000000..1b3dfb7d54 --- /dev/null +++ b/internal/v2/domain/roles.go @@ -0,0 +1,5 @@ +package domain + +const ( + OrgOwnerRole = "ORG_OWNER" +) diff --git a/internal/v2/repository/org/domain.go b/internal/v2/repository/org/domain.go new file mode 100644 index 0000000000..61116bea8f --- /dev/null +++ b/internal/v2/repository/org/domain.go @@ -0,0 +1,222 @@ +package org + +import ( + "context" + "encoding/json" + + "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/eventstore/v2/repository" + "github.com/caos/zitadel/internal/v2/domain" +) + +const ( + domainEventPrefix = orgEventTypePrefix + "domain." + OrgDomainAdded = domainEventPrefix + "added" + OrgDomainVerificationAdded = domainEventPrefix + "verification.added" + OrgDomainVerificationFailed = domainEventPrefix + "verification.failed" + OrgDomainVerified = domainEventPrefix + "verified" + OrgDomainPrimarySet = domainEventPrefix + "primary.set" + OrgDomainRemoved = domainEventPrefix + "removed" +) + +type DomainAddedEvent struct { + eventstore.BaseEvent `json:"-"` + + Domain string `json:"domain,omitempty"` +} + +func (e *DomainAddedEvent) Data() interface{} { + return e +} + +func NewDomainAddedEvent(ctx context.Context, domain string) *DomainAddedEvent { + return &DomainAddedEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + OrgDomainAdded, + ), + Domain: domain, + } +} + +func DomainAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + orgDomainAdded := &DomainAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + err := json.Unmarshal(event.Data, orgDomainAdded) + if err != nil { + return nil, errors.ThrowInternal(err, "ORG-GBr52", "unable to unmarshal org domain added") + } + + return orgDomainAdded, nil +} + +type DomainVerificationAddedEvent struct { + eventstore.BaseEvent `json:"-"` + + Domain string `json:"domain,omitempty"` + ValidationType domain.OrgDomainValidationType `json:"validationType,omitempty"` + ValidationCode *crypto.CryptoValue `json:"validationCode,omitempty"` +} + +func (e *DomainVerificationAddedEvent) Data() interface{} { + return e +} + +func NewDomainVerificationAddedEvent( + ctx context.Context, + domain string, + validationType domain.OrgDomainValidationType, + validationCode *crypto.CryptoValue) *DomainVerificationAddedEvent { + return &DomainVerificationAddedEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + OrgDomainVerificationAdded, + ), + Domain: domain, + ValidationType: validationType, + ValidationCode: validationCode, + } +} + +func DomainVerificationAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + orgDomainVerificationAdded := &DomainVerificationAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + err := json.Unmarshal(event.Data, orgDomainVerificationAdded) + if err != nil { + return nil, errors.ThrowInternal(err, "ORG-NRN32", "unable to unmarshal org domain verification added") + } + + return orgDomainVerificationAdded, nil +} + +type DomainVerificationFailedEvent struct { + eventstore.BaseEvent `json:"-"` + + Domain string `json:"domain,omitempty"` +} + +func (e *DomainVerificationFailedEvent) Data() interface{} { + return e +} + +func NewDomainVerificationFailedEvent(ctx context.Context, domain string) *DomainVerificationFailedEvent { + return &DomainVerificationFailedEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + OrgDomainVerificationFailed, + ), + Domain: domain, + } +} + +func DomainVerificationFailedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + orgDomainVerificationFailed := &DomainVerificationFailedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + err := json.Unmarshal(event.Data, orgDomainVerificationFailed) + if err != nil { + return nil, errors.ThrowInternal(err, "ORG-Bhm37", "unable to unmarshal org domain verification failed") + } + + return orgDomainVerificationFailed, nil +} + +type DomainVerifiedEvent struct { + eventstore.BaseEvent `json:"-"` + + Domain string `json:"domain,omitempty"` +} + +func (e *DomainVerifiedEvent) Data() interface{} { + return e +} + +func NewDomainVerifiedEvent(ctx context.Context, domain string) *DomainVerifiedEvent { + return &DomainVerifiedEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + OrgDomainVerified, + ), + Domain: domain, + } +} + +func DomainVerifiedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + orgDomainVerified := &DomainVerifiedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + err := json.Unmarshal(event.Data, orgDomainVerified) + if err != nil { + return nil, errors.ThrowInternal(err, "ORG-BFSwt", "unable to unmarshal org domain verified") + } + + return orgDomainVerified, nil +} + +type DomainPrimarySetEvent struct { + eventstore.BaseEvent `json:"-"` + + Domain string `json:"domain,omitempty"` +} + +func (e *DomainPrimarySetEvent) Data() interface{} { + return e +} + +func NewDomainPrimarySetEvent(ctx context.Context, domain string) *DomainPrimarySetEvent { + return &DomainPrimarySetEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + OrgDomainPrimarySet, + ), + Domain: domain, + } +} + +func DomainPrimarySetEventMapper(event *repository.Event) (eventstore.EventReader, error) { + orgDomainPrimarySet := &DomainPrimarySetEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + err := json.Unmarshal(event.Data, orgDomainPrimarySet) + if err != nil { + return nil, errors.ThrowInternal(err, "ORG-N5787", "unable to unmarshal org domain primary set") + } + + return orgDomainPrimarySet, nil +} + +type DomainRemovedEvent struct { + eventstore.BaseEvent `json:"-"` + + Domain string `json:"domain,omitempty"` +} + +func (e *DomainRemovedEvent) Data() interface{} { + return e +} + +func NewDomainRemovedEvent(ctx context.Context, domain string) *DomainRemovedEvent { + return &DomainRemovedEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + OrgDomainRemoved, + ), + Domain: domain, + } +} + +func DomainRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + orgDomainRemoved := &DomainRemovedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + err := json.Unmarshal(event.Data, orgDomainRemoved) + if err != nil { + return nil, errors.ThrowInternal(err, "ORG-BngB2", "unable to unmarshal org domain removed") + } + + return orgDomainRemoved, nil +} diff --git a/internal/v2/repository/org/eventstore.go b/internal/v2/repository/org/eventstore.go new file mode 100644 index 0000000000..67d2e2b787 --- /dev/null +++ b/internal/v2/repository/org/eventstore.go @@ -0,0 +1,19 @@ +package org + +import ( + "github.com/caos/zitadel/internal/eventstore/v2" +) + +func RegisterEventMappers(es *eventstore.Eventstore) { + es.RegisterFilterEventMapper(OrgAdded, OrgAddedEventMapper). + RegisterFilterEventMapper(OrgChanged, OrgChangedEventMapper). + //RegisterFilterEventMapper(OrgDeactivated, OrgChangedEventMapper). TODO: ! + //RegisterFilterEventMapper(OrgReactivated, OrgChangedEventMapper). + //RegisterFilterEventMapper(OrgRemoved, OrgChangedEventMapper). + RegisterFilterEventMapper(OrgDomainAdded, DomainAddedEventMapper). + RegisterFilterEventMapper(OrgDomainVerificationAdded, DomainVerificationAddedEventMapper). + RegisterFilterEventMapper(OrgDomainVerificationFailed, DomainVerificationFailedEventMapper). + RegisterFilterEventMapper(OrgDomainVerified, DomainVerifiedEventMapper). + RegisterFilterEventMapper(OrgDomainPrimarySet, DomainPrimarySetEventMapper). + RegisterFilterEventMapper(OrgDomainRemoved, DomainRemovedEventMapper) +} diff --git a/internal/v2/repository/org/org.go b/internal/v2/repository/org/org.go new file mode 100644 index 0000000000..005e8bb112 --- /dev/null +++ b/internal/v2/repository/org/org.go @@ -0,0 +1,82 @@ +package org + +import ( + "context" + "encoding/json" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/eventstore/v2/repository" +) + +const ( + OrgAdded = orgEventTypePrefix + "added" + OrgChanged = orgEventTypePrefix + "changed" + OrgDeactivated = orgEventTypePrefix + "deactivated" + OrgReactivated = orgEventTypePrefix + "reactivated" + OrgRemoved = orgEventTypePrefix + "removed" +) + +type OrgAddedEvent struct { + eventstore.BaseEvent `json:"-"` + + Name string `json:"name,omitempty"` +} + +func (e *OrgAddedEvent) Data() interface{} { + return e +} + +func NewOrgAddedEvent(ctx context.Context, name string) *OrgAddedEvent { + return &OrgAddedEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + OrgAdded, + ), + Name: name, + } +} + +func OrgAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + orgAdded := &OrgAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + err := json.Unmarshal(event.Data, orgAdded) + if err != nil { + return nil, errors.ThrowInternal(err, "ORG-Bren2", "unable to unmarshal org added") + } + + return orgAdded, nil +} + +type OrgChangedEvent struct { + eventstore.BaseEvent `json:"-"` + + Name string `json:"name,omitempty"` +} + +func (e *OrgChangedEvent) Data() interface{} { + return e +} + +func NewOrgChangedEvent(ctx context.Context, name string) *OrgChangedEvent { + return &OrgChangedEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + OrgChanged, + ), + Name: name, + } +} + +func OrgChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + orgChanged := &OrgChangedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + err := json.Unmarshal(event.Data, orgChanged) + if err != nil { + return nil, errors.ThrowInternal(err, "ORG-Bren2", "unable to unmarshal org added") + } + + return orgChanged, nil +} diff --git a/internal/v2/repository/org/policy_org_iam.go b/internal/v2/repository/org/policy_org_iam.go index da52d29ef1..8616dd4013 100644 --- a/internal/v2/repository/org/policy_org_iam.go +++ b/internal/v2/repository/org/policy_org_iam.go @@ -8,14 +8,38 @@ import ( ) var ( - OrgIAMPolicyAddedEventType = orgEventTypePrefix + policy.OrgIAMPolicyAddedEventType - OrgIAMPolicyChangedEventType = orgEventTypePrefix + policy.OrgIAMPolicyChangedEventType + //TODO: enable when possible + //OrgIAMPolicyAddedEventType = orgEventTypePrefix + policy.OrgIAMPolicyAddedEventType + //OrgIAMPolicyChangedEventType = orgEventTypePrefix + policy.OrgIAMPolicyChangedEventType + OrgIAMPolicyAddedEventType = orgEventTypePrefix + "iam.policy.added" + OrgIAMPolicyChangedEventType = orgEventTypePrefix + "iam.policy.changed" ) type OrgIAMPolicyAddedEvent struct { policy.OrgIAMPolicyAddedEvent } +func NewOrgIAMPolicyAddedEvent( + ctx context.Context, + userLoginMustBeDomain bool, +) *OrgIAMPolicyAddedEvent { + return &OrgIAMPolicyAddedEvent{ + OrgIAMPolicyAddedEvent: *policy.NewOrgIAMPolicyAddedEvent( + eventstore.NewBaseEventForPush(ctx, OrgIAMPolicyAddedEventType), + userLoginMustBeDomain, + ), + } +} + +func OrgIAMPolicyAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) { + e, err := policy.OrgIAMPolicyAddedEventMapper(event) + if err != nil { + return nil, err + } + + return &OrgIAMPolicyAddedEvent{OrgIAMPolicyAddedEvent: *e.(*policy.OrgIAMPolicyAddedEvent)}, nil +} + type OrgIAMPolicyChangedEvent struct { policy.OrgIAMPolicyChangedEvent } diff --git a/internal/v2/repository/policy/policy_org_iam.go b/internal/v2/repository/policy/policy_org_iam.go index 3e425ffb33..2b0f2c4d84 100644 --- a/internal/v2/repository/policy/policy_org_iam.go +++ b/internal/v2/repository/policy/policy_org_iam.go @@ -8,6 +8,7 @@ import ( ) const ( + //TODO: use for org events as suffix (when possible) OrgIAMPolicyAddedEventType = "policy.org.iam.added" OrgIAMPolicyChangedEventType = "policy.org.iam.changed" ) diff --git a/pkg/grpc/admin/proto/admin.proto b/pkg/grpc/admin/proto/admin.proto index d0c44df3e6..365cb77e6c 100644 --- a/pkg/grpc/admin/proto/admin.proto +++ b/pkg/grpc/admin/proto/admin.proto @@ -74,7 +74,7 @@ service AdminService { }; } - rpc SetUpOrg(OrgSetUpRequest) returns (OrgSetUpResponse) { + rpc SetUpOrg(OrgSetUpRequest) returns (google.protobuf.Empty) { option (google.api.http) = { post: "/orgs/_setup" body: "*"