feat: set up org (#1157)

* add setup steps

* refactoring

* omitempty

* cleanup

* begin org

* create org

* setup org

* setup org

* merge

* fixes

* fixes

* fixes
This commit is contained in:
Livio Amstutz 2021-01-08 11:33:45 +01:00 committed by GitHub
parent 26c8113930
commit ff87264f95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 1160 additions and 162 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,6 +15,12 @@ type Password struct {
ChangeRequired bool
}
func NewPassword(password string) *Password {
return &Password{
SecretString: password,
}
}
type PasswordCode struct {
es_models.ObjectRoot

View File

@ -11,7 +11,7 @@ type IAM struct {
IAMProjectID string
SetUpDone Step
SetUpStarted Step
Members []*IAMMember
Members []*Member
IDPs []*IDPConfig
DefaultLoginPolicy *LoginPolicy
DefaultLabelPolicy *LabelPolicy

View File

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

43
internal/v2/domain/org.go Normal file
View File

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

View File

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

View File

@ -0,0 +1,5 @@
package domain
const (
OrgOwnerRole = "ORG_OWNER"
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: "*"