diff --git a/internal/eventstore/mock/eventstore.mock.go b/internal/eventstore/mock/eventstore.mock.go index fbe1990856..9a2ad52d64 100644 --- a/internal/eventstore/mock/eventstore.mock.go +++ b/internal/eventstore/mock/eventstore.mock.go @@ -8,6 +8,7 @@ import ( context "context" eventstore "github.com/caos/zitadel/internal/eventstore" models "github.com/caos/zitadel/internal/eventstore/models" + eventstore0 "github.com/caos/zitadel/internal/eventstore/v2" gomock "github.com/golang/mock/gomock" reflect "reflect" ) @@ -129,3 +130,17 @@ func (mr *MockEventstoreMockRecorder) Subscribe(arg0 ...interface{}) *gomock.Cal mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockEventstore)(nil).Subscribe), arg0...) } + +// V2 mocks base method +func (m *MockEventstore) V2() *eventstore0.Eventstore { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "V2") + ret0, _ := ret[0].(*eventstore0.Eventstore) + return ret0 +} + +// V2 indicates an expected call of V2 +func (mr *MockEventstoreMockRecorder) V2() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "V2", reflect.TypeOf((*MockEventstore)(nil).V2)) +} diff --git a/internal/eventstore/spooler/mock/spooler.go b/internal/eventstore/spooler/mock/spooler.go index 4e49d6ba65..25b2b076a5 100644 --- a/internal/eventstore/spooler/mock/spooler.go +++ b/internal/eventstore/spooler/mock/spooler.go @@ -5,92 +5,11 @@ package mock import ( - models "github.com/caos/zitadel/internal/eventstore/models" gomock "github.com/golang/mock/gomock" reflect "reflect" time "time" ) -// MockHandler is a mock of Handler interface -type MockHandler struct { - ctrl *gomock.Controller - recorder *MockHandlerMockRecorder -} - -// MockHandlerMockRecorder is the mock recorder for MockHandler -type MockHandlerMockRecorder struct { - mock *MockHandler -} - -// NewMockHandler creates a new mock instance -func NewMockHandler(ctrl *gomock.Controller) *MockHandler { - mock := &MockHandler{ctrl: ctrl} - mock.recorder = &MockHandlerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *MockHandler) EXPECT() *MockHandlerMockRecorder { - return m.recorder -} - -// ViewModel mocks base method -func (m *MockHandler) ViewModel() string { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ViewModel") - ret0, _ := ret[0].(string) - return ret0 -} - -// ViewModel indicates an expected call of ViewModel -func (mr *MockHandlerMockRecorder) ViewModel() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ViewModel", reflect.TypeOf((*MockHandler)(nil).ViewModel)) -} - -// EventQuery mocks base method -func (m *MockHandler) EventQuery() (*models.SearchQuery, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "EventQuery") - ret0, _ := ret[0].(*models.SearchQuery) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// EventQuery indicates an expected call of EventQuery -func (mr *MockHandlerMockRecorder) EventQuery() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EventQuery", reflect.TypeOf((*MockHandler)(nil).EventQuery)) -} - -// Reduce mocks base method -func (m *MockHandler) Process(arg0 *models.Event) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Reduce", arg0) - ret0, _ := ret[0].(error) - return ret0 -} - -// Reduce indicates an expected call of Reduce -func (mr *MockHandlerMockRecorder) Process(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Reduce", reflect.TypeOf((*MockHandler)(nil).Process), arg0) -} - -// MinimumCycleDuration mocks base method -func (m *MockHandler) MinimumCycleDuration() time.Duration { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "MinimumCycleDuration") - ret0, _ := ret[0].(time.Duration) - return ret0 -} - -// MinimumCycleDuration indicates an expected call of MinimumCycleDuration -func (mr *MockHandlerMockRecorder) MinimumCycleDuration() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MinimumCycleDuration", reflect.TypeOf((*MockHandler)(nil).MinimumCycleDuration)) -} - // MockLocker is a mock of Locker interface type MockLocker struct { ctrl *gomock.Controller diff --git a/internal/eventstore/v2/event.go b/internal/eventstore/v2/event.go index bd384c5cea..08a4e64aec 100644 --- a/internal/eventstore/v2/event.go +++ b/internal/eventstore/v2/event.go @@ -18,6 +18,8 @@ type EventPusher interface { // * struct which can be marshalled to json // * pointer to struct which can be marshalled to json Data() interface{} + //UniqueConstraints should be added for unique attributes of an event, if nil constraints will not be checked + UniqueConstraints() []*EventUniqueConstraint } type EventReader interface { diff --git a/internal/eventstore/v2/event_base.go b/internal/eventstore/v2/event_base.go index 234122469c..4dc3b2b077 100644 --- a/internal/eventstore/v2/event_base.go +++ b/internal/eventstore/v2/event_base.go @@ -81,3 +81,13 @@ func NewBaseEventForPush(ctx context.Context, typ EventType) *BaseEvent { EventType: typ, } } + +func NewBaseEventForPushWithResourceOwner(ctx context.Context, typ EventType, resourceOwner string) *BaseEvent { + svcName := service.FromContext(ctx) + return &BaseEvent{ + User: authz.GetCtxData(ctx).UserID, + Service: svcName, + EventType: typ, + resourceOwner: resourceOwner, + } +} diff --git a/internal/eventstore/v2/eventstore.go b/internal/eventstore/v2/eventstore.go index 65f8d69678..052d738d42 100644 --- a/internal/eventstore/v2/eventstore.go +++ b/internal/eventstore/v2/eventstore.go @@ -50,12 +50,12 @@ 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) { - events, err := es.aggregatesToEvents(aggregates) + events, uniqueConstraints, err := es.aggregatesToEvents(aggregates) if err != nil { return nil, err } - err = es.repo.Push(ctx, events...) + err = es.repo.Push(ctx, events, uniqueConstraints...) if err != nil { return nil, err } @@ -63,13 +63,14 @@ 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, []*repository.UniqueConstraint, error) { events := make([]*repository.Event, 0, len(aggregates)) + uniqueConstraints := make([]*repository.UniqueConstraint, 0) for _, aggregate := range aggregates { for _, event := range aggregate.Events() { data, err := eventData(event) if err != nil { - return nil, err + return nil, nil, err } events = append(events, &repository.Event{ AggregateID: aggregate.ID(), @@ -81,9 +82,21 @@ func (es *Eventstore) aggregatesToEvents(aggregates []Aggregater) ([]*repository Version: repository.Version(aggregate.Version()), Data: data, }) + if event.UniqueConstraints() != nil { + for _, constraint := range event.UniqueConstraints() { + uniqueConstraints = append(uniqueConstraints, + &repository.UniqueConstraint{ + UniqueType: constraint.UniqueType, + UniqueField: constraint.UniqueField, + Action: uniqueConstraintActionToRepository(constraint.Action), + ErrorMessage: constraint.ErrorMessage, + }, + ) + } + } } } - return events, nil + return events, uniqueConstraints, nil } //FilterEvents filters the stored events based on the searchQuery @@ -209,3 +222,14 @@ func eventData(event EventPusher) ([]byte, error) { } return nil, errors.ThrowInvalidArgument(nil, "V2-91NRm", "wrong type of event data") } + +func uniqueConstraintActionToRepository(action UniqueConstraintAction) repository.UniqueConstraintAction { + switch action { + case UniqueConstraintAdd: + return repository.UniqueConstraintAdd + case UniqueConstraintRemove: + return repository.UniqueConstraintRemoved + default: + return repository.UniqueConstraintAdd + } +} diff --git a/internal/eventstore/v2/eventstore_test.go b/internal/eventstore/v2/eventstore_test.go index 1bc01bc258..a841f99e77 100644 --- a/internal/eventstore/v2/eventstore_test.go +++ b/internal/eventstore/v2/eventstore_test.go @@ -63,6 +63,10 @@ func (e *testEvent) Data() interface{} { return e.data() } +func (e *testEvent) UniqueConstraint() []EventUniqueConstraint { + return nil +} + func testFilterMapper(event *repository.Event) (EventReader, error) { if event == nil { return newTestEvent("hodor", nil, false), nil @@ -543,7 +547,7 @@ func TestEventstore_aggregatesToEvents(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { es := &Eventstore{} - events, err := es.aggregatesToEvents(tt.args.aggregates) + events, _, err := es.aggregatesToEvents(tt.args.aggregates) if (err != nil) != tt.res.wantErr { t.Errorf("Eventstore.aggregatesToEvents() error = %v, wantErr %v", err, tt.res.wantErr) return @@ -576,7 +580,7 @@ func (repo *testRepo) Health(ctx context.Context) error { return nil } -func (repo *testRepo) Push(ctx context.Context, events ...*repository.Event) error { +func (repo *testRepo) Push(ctx context.Context, events []*repository.Event, uniqueConstraints ...*repository.UniqueConstraint) error { if repo.err != nil { return repo.err } diff --git a/internal/eventstore/v2/example_test.go b/internal/eventstore/v2/example_test.go index 33497e02b5..767ee9ac94 100644 --- a/internal/eventstore/v2/example_test.go +++ b/internal/eventstore/v2/example_test.go @@ -29,7 +29,6 @@ func NewUserAggregate(id string) *UserAggregate { "test.user", "caos", "v1", - 0, ), } } @@ -84,6 +83,10 @@ func (e *UserAddedEvent) Data() interface{} { return e } +func (e *UserAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + // ------------------------------------------------------------ // User first name changed event start // ------------------------------------------------------------ @@ -122,6 +125,10 @@ func (e *UserFirstNameChangedEvent) Data() interface{} { return e } +func (e *UserFirstNameChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + // ------------------------------------------------------------ // User password checked event start // ------------------------------------------------------------ @@ -152,6 +159,10 @@ func (e *UserPasswordCheckedEvent) Data() interface{} { return nil } +func (e *UserPasswordCheckedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + // ------------------------------------------------------------ // User deleted event // ------------------------------------------------------------ @@ -182,6 +193,10 @@ func (e *UserDeletedEvent) Data() interface{} { return nil } +func (e *UserDeletedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + // ------------------------------------------------------------ // Users read model start // ------------------------------------------------------------ diff --git a/internal/eventstore/v2/repository/repository.go b/internal/eventstore/v2/repository/repository.go index a8adb6ae2b..74d05ff0fc 100644 --- a/internal/eventstore/v2/repository/repository.go +++ b/internal/eventstore/v2/repository/repository.go @@ -9,8 +9,9 @@ type Repository interface { //Health checks if the connection to the storage is available Health(ctx context.Context) error // PushEvents adds all events of the given aggregates to the eventstreams of the aggregates. + // if unique constraints are pushed, they will be added to the unique table for checking unique constraint violations // This call is transaction save. The transaction will be rolled back if one event fails - Push(ctx context.Context, events ...*Event) error + Push(ctx context.Context, events []*Event, uniqueConstraints ...*UniqueConstraint) error // Filter returns all events matching the given search query Filter(ctx context.Context, searchQuery *SearchQuery) (events []*Event, err error) //LatestSequence returns the latests sequence found by the the search query diff --git a/internal/eventstore/v2/repository/sql/crdb.go b/internal/eventstore/v2/repository/sql/crdb.go index c55a64d541..7b65f616a9 100644 --- a/internal/eventstore/v2/repository/sql/crdb.go +++ b/internal/eventstore/v2/repository/sql/crdb.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "github.com/lib/pq" "regexp" "strconv" @@ -88,6 +89,18 @@ const ( " FROM data " + " ) " + "RETURNING id, event_sequence, previous_sequence, creation_date, resource_owner" + uniqueInsert = `INSERT INTO eventstore.unique_constraints + ( + unique_type, + unique_field + ) + VALUES ( + $1, + $2 + )` + + uniqueDelete = `DELETE FROM eventstore.unique_constraints + WHERE unique_type = $1 and unique_field = $2` ) type CRDB struct { @@ -102,7 +115,7 @@ func (db *CRDB) Health(ctx context.Context) error { return db.client.Ping() } // Push adds all events to the eventstreams of the aggregates. // This call is transaction save. The transaction will be rolled back if one event fails -func (db *CRDB) Push(ctx context.Context, events ...*repository.Event) error { +func (db *CRDB) Push(ctx context.Context, events []*repository.Event, uniqueConstraints ...*repository.UniqueConstraint) error { err := crdb.ExecuteTx(ctx, db.client, nil, func(tx *sql.Tx) error { stmt, err := tx.PrepareContext(ctx, crdbInsert) if err != nil { @@ -136,6 +149,10 @@ func (db *CRDB) Push(ctx context.Context, events ...*repository.Event) error { } } + err = db.handleUniqueConstraints(ctx, tx, uniqueConstraints...) + if err != nil { + return err + } return nil }) if err != nil && !errors.Is(err, &caos_errs.CaosError{}) { @@ -145,6 +162,39 @@ func (db *CRDB) Push(ctx context.Context, events ...*repository.Event) error { return err } +// handleUniqueConstraints adds or removes unique constraints +func (db *CRDB) handleUniqueConstraints(ctx context.Context, tx *sql.Tx, uniqueConstraints ...*repository.UniqueConstraint) (err error) { + if uniqueConstraints == nil || len(uniqueConstraints) == 0 || (len(uniqueConstraints) == 1 && uniqueConstraints[0] == nil) { + return nil + } + + for _, uniqueConstraint := range uniqueConstraints { + if uniqueConstraint.Action == repository.UniqueConstraintAdd { + _, err := tx.ExecContext(ctx, uniqueInsert, uniqueConstraint.UniqueType, uniqueConstraint.UniqueField) + if err != nil { + logging.LogWithFields("SQL-IP3js", + "unique_type", uniqueConstraint.UniqueType, + "unique_field", uniqueConstraint.UniqueField).WithError(err).Info("insert unique constraint failed") + + if db.isUniqueViolationError(err) { + return caos_errs.ThrowAlreadyExists(err, "SQL-M0dsf", uniqueConstraint.ErrorMessage) + } + + return caos_errs.ThrowInternal(err, "SQL-dM9ds", "unable to create unique constraint ") + } + } else if uniqueConstraint.Action == repository.UniqueConstraintRemoved { + _, err := tx.ExecContext(ctx, uniqueDelete, uniqueConstraint.UniqueType, uniqueConstraint.UniqueField) + if err != nil { + logging.LogWithFields("SQL-M0vsf", + "unique_type", uniqueConstraint.UniqueType, + "unique_field", uniqueConstraint.UniqueField).WithError(err).Info("delete unique constraint failed") + return caos_errs.ThrowInternal(err, "SQL-2M9fs", "unable to remove unique constraint ") + } + } + } + return nil +} + // Filter returns all events matching the given search query func (db *CRDB) Filter(ctx context.Context, searchQuery *repository.SearchQuery) (events []*repository.Event, err error) { events = []*repository.Event{} @@ -262,3 +312,12 @@ func (db *CRDB) placeholder(query string) string { } return replaced } + +func (db *CRDB) isUniqueViolationError(err error) bool { + if pqErr, ok := err.(*pq.Error); ok { + if pqErr.Code == "23505" { + return true + } + } + return false +} diff --git a/internal/eventstore/v2/repository/sql/crdb_test.go b/internal/eventstore/v2/repository/sql/crdb_test.go index 0098fa5656..3544677079 100644 --- a/internal/eventstore/v2/repository/sql/crdb_test.go +++ b/internal/eventstore/v2/repository/sql/crdb_test.go @@ -266,11 +266,15 @@ func TestCRDB_columnName(t *testing.T) { func TestCRDB_Push_OneAggregate(t *testing.T) { type args struct { - ctx context.Context - events []*repository.Event + ctx context.Context + events []*repository.Event + uniqueConstraints *repository.UniqueConstraint + uniqueDataType string + uniqueDataField string } type eventsRes struct { pushedEventsCount int + uniqueCount int aggType repository.AggregateType aggID []string } @@ -334,26 +338,84 @@ func TestCRDB_Push_OneAggregate(t *testing.T) { }, }, }, + { + name: "push 1 event and add unique constraint", + args: args{ + ctx: context.Background(), + events: []*repository.Event{ + generateEvent(t, "10"), + }, + uniqueConstraints: generateAddUniqueConstraint(t, "usernames", "field"), + }, + res: res{ + wantErr: false, + eventsRes: eventsRes{ + pushedEventsCount: 1, + uniqueCount: 1, + aggID: []string{"10"}, + aggType: repository.AggregateType(t.Name()), + }}, + }, + { + name: "push 1 event and remove unique constraint", + args: args{ + ctx: context.Background(), + events: []*repository.Event{ + generateEvent(t, "11"), + }, + uniqueConstraints: generateRemoveUniqueConstraint(t, "usernames", "testremove"), + uniqueDataType: "usernames", + uniqueDataField: "testremove", + }, + res: res{ + wantErr: false, + eventsRes: eventsRes{ + pushedEventsCount: 1, + uniqueCount: 0, + aggID: []string{"11"}, + aggType: repository.AggregateType(t.Name()), + }}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { db := &CRDB{ client: testCRDBClient, } - if err := db.Push(tt.args.ctx, tt.args.events...); (err != nil) != tt.res.wantErr { + if tt.args.uniqueDataType != "" && tt.args.uniqueDataField != "" { + err := fillUniqueData(tt.args.uniqueDataType, tt.args.uniqueDataField) + if err != nil { + t.Error("unable to prefill insert unique data: ", err) + return + } + } + if err := db.Push(tt.args.ctx, tt.args.events, tt.args.uniqueConstraints); (err != nil) != tt.res.wantErr { t.Errorf("CRDB.Push() error = %v, wantErr %v", err, tt.res.wantErr) } - countRow := testCRDBClient.QueryRow("SELECT COUNT(*) FROM eventstore.events where aggregate_type = $1 AND aggregate_id = ANY($2)", tt.res.eventsRes.aggType, pq.Array(tt.res.eventsRes.aggID)) - var count int - err := countRow.Scan(&count) + countEventRow := testCRDBClient.QueryRow("SELECT COUNT(*) FROM eventstore.events where aggregate_type = $1 AND aggregate_id = ANY($2)", tt.res.eventsRes.aggType, pq.Array(tt.res.eventsRes.aggID)) + var eventCount int + err := countEventRow.Scan(&eventCount) if err != nil { t.Error("unable to query inserted rows: ", err) return } - if count != tt.res.eventsRes.pushedEventsCount { - t.Errorf("expected push count %d got %d", tt.res.eventsRes.pushedEventsCount, count) + if eventCount != tt.res.eventsRes.pushedEventsCount { + t.Errorf("expected push count %d got %d", tt.res.eventsRes.pushedEventsCount, eventCount) } + if tt.args.uniqueConstraints != nil { + countUniqueRow := testCRDBClient.QueryRow("SELECT COUNT(*) FROM eventstore.unique_constraints where unique_type = $1 AND unique_field = $2", tt.args.uniqueConstraints.UniqueType, tt.args.uniqueConstraints.UniqueField) + var uniqueCount int + err := countUniqueRow.Scan(&uniqueCount) + if err != nil { + t.Error("unable to query inserted rows: ", err) + return + } + if uniqueCount != tt.res.eventsRes.uniqueCount { + t.Errorf("expected unique count %d got %d", tt.res.eventsRes.uniqueCount, uniqueCount) + } + } + }) } } @@ -445,7 +507,7 @@ func TestCRDB_Push_MultipleAggregate(t *testing.T) { db := &CRDB{ client: testCRDBClient, } - if err := db.Push(context.Background(), tt.args.events...); (err != nil) != tt.res.wantErr { + if err := db.Push(context.Background(), tt.args.events); (err != nil) != tt.res.wantErr { t.Errorf("CRDB.Push() error = %v, wantErr %v", err, tt.res.wantErr) } @@ -616,7 +678,7 @@ func TestCRDB_Push_Parallel(t *testing.T) { for _, events := range tt.args.events { wg.Add(1) go func(events []*repository.Event) { - err := db.Push(context.Background(), events...) + err := db.Push(context.Background(), events) if err != nil { errsMu.Lock() errs = append(errs, err) @@ -728,7 +790,7 @@ func TestCRDB_Filter(t *testing.T) { } // setup initial data for query - if err := db.Push(context.Background(), tt.fields.existingEvents...); err != nil { + if err := db.Push(context.Background(), tt.fields.existingEvents); err != nil { t.Errorf("error in setup = %v", err) return } @@ -814,7 +876,7 @@ func TestCRDB_LatestSequence(t *testing.T) { } // setup initial data for query - if err := db.Push(context.Background(), tt.fields.existingEvents...); err != nil { + if err := db.Push(context.Background(), tt.fields.existingEvents); err != nil { t.Errorf("error in setup = %v", err) return } @@ -956,7 +1018,7 @@ func TestCRDB_Push_ResourceOwner(t *testing.T) { db := &CRDB{ client: testCRDBClient, } - if err := db.Push(context.Background(), tt.args.events...); err != nil { + if err := db.Push(context.Background(), tt.args.events); err != nil { t.Errorf("CRDB.Push() error = %v", err) } @@ -1036,3 +1098,25 @@ func generateEventWithData(t *testing.T, aggregateID string, data []byte) *repos Data: data, } } + +func generateAddUniqueConstraint(t *testing.T, table, uniqueField string) *repository.UniqueConstraint { + t.Helper() + e := &repository.UniqueConstraint{ + UniqueType: table, + UniqueField: uniqueField, + Action: repository.UniqueConstraintAdd, + } + + return e +} + +func generateRemoveUniqueConstraint(t *testing.T, table, uniqueField string) *repository.UniqueConstraint { + t.Helper() + e := &repository.UniqueConstraint{ + UniqueType: table, + UniqueField: uniqueField, + Action: repository.UniqueConstraintRemoved, + } + + return e +} diff --git a/internal/eventstore/v2/repository/sql/local_crdb_test.go b/internal/eventstore/v2/repository/sql/local_crdb_test.go index 0da55050ab..1a956eb747 100644 --- a/internal/eventstore/v2/repository/sql/local_crdb_test.go +++ b/internal/eventstore/v2/repository/sql/local_crdb_test.go @@ -80,6 +80,11 @@ func executeMigrations() error { return nil } +func fillUniqueData(unique_type, field string) error { + _, err := testCRDBClient.Exec("INSERT INTO eventstore.unique_constraints (unique_type, unique_field) VALUES ($1, $2)", unique_type, field) + return err +} + type migrationPaths []string type version struct { diff --git a/internal/eventstore/v2/repository/sql/query_test.go b/internal/eventstore/v2/repository/sql/query_test.go index d3342bd7a4..46fe6efdb3 100644 --- a/internal/eventstore/v2/repository/sql/query_test.go +++ b/internal/eventstore/v2/repository/sql/query_test.go @@ -521,7 +521,7 @@ func Test_query_events_with_crdb(t *testing.T) { } // setup initial data for query - if err := db.Push(context.Background(), tt.fields.existingEvents...); err != nil { + if err := db.Push(context.Background(), tt.fields.existingEvents); err != nil { t.Errorf("error in setup = %v", err) return } diff --git a/internal/eventstore/v2/repository/unique_constraint.go b/internal/eventstore/v2/repository/unique_constraint.go new file mode 100644 index 0000000000..573b5d4ed8 --- /dev/null +++ b/internal/eventstore/v2/repository/unique_constraint.go @@ -0,0 +1,29 @@ +package repository + +//UniqueCheck represents all information about a unique attribute +type UniqueConstraint struct { + //UniqueField is the field which should be unique + UniqueField string + + //UniqueType is the type of the unique field + UniqueType string + + //Action defines if unique constraint should be added or removed + Action UniqueConstraintAction + + //ErrorMessage is the message key which should be returned if constraint is violated + ErrorMessage string +} + +type UniqueConstraintAction int32 + +const ( + UniqueConstraintAdd UniqueConstraintAction = iota + UniqueConstraintRemoved + + uniqueConstraintActionCount +) + +func (f UniqueConstraintAction) Valid() bool { + return f >= 0 && f < uniqueConstraintActionCount +} diff --git a/internal/eventstore/v2/unique_constraint.go b/internal/eventstore/v2/unique_constraint.go new file mode 100644 index 0000000000..54ce5d8302 --- /dev/null +++ b/internal/eventstore/v2/unique_constraint.go @@ -0,0 +1,43 @@ +package eventstore + +type EventUniqueConstraint struct { + // UniqueType is the table name for the unique constraint + UniqueType string + //UniqueField is the unique key + UniqueField string + //Action defines if unique constraint should be added or removed + Action UniqueConstraintAction + //ErrorMessage defines the translation file key for the error message + ErrorMessage string +} + +type UniqueConstraintAction int32 + +const ( + UniqueConstraintAdd UniqueConstraintAction = iota + UniqueConstraintRemove + + uniqueConstraintActionCount +) + +func NewAddEventUniqueConstraint( + uniqueType, + uniqueField, + errMessage string) *EventUniqueConstraint { + return &EventUniqueConstraint{ + UniqueType: uniqueType, + UniqueField: uniqueField, + ErrorMessage: errMessage, + Action: UniqueConstraintAdd, + } +} + +func NewRemoveEventUniqueConstraint( + uniqueType, + uniqueField string) *EventUniqueConstraint { + return &EventUniqueConstraint{ + UniqueType: uniqueType, + UniqueField: uniqueField, + Action: UniqueConstraintRemove, + } +} diff --git a/internal/iam/repository/eventsourcing/eventstore_test.go b/internal/iam/repository/eventsourcing/eventstore_test.go index 8437a1199e..5db2bfcc9a 100644 --- a/internal/iam/repository/eventsourcing/eventstore_test.go +++ b/internal/iam/repository/eventsourcing/eventstore_test.go @@ -76,78 +76,79 @@ func TestIamByID(t *testing.T) { } } -func TestSetUpStarted(t *testing.T) { - ctrl := gomock.NewController(t) - type args struct { - es *IAMEventstore - ctx context.Context - iamID string - step iam_model.Step - } - type res struct { - iam *iam_model.IAM - errFunc func(err error) bool - } - tests := []struct { - name string - args args - res res - }{ - { - name: "setup started iam, ok", - args: args{ - es: GetMockManipulateIAMNotExisting(ctrl), - ctx: authz.NewMockContext("orgID", "userID"), - iamID: "iamID", - step: iam_model.Step1, - }, - res: res{ - iam: &iam_model.IAM{ObjectRoot: es_models.ObjectRoot{AggregateID: "iamID", Sequence: 1}, SetUpStarted: iam_model.Step1}, - }, - }, - { - name: "setup already started", - args: args{ - es: GetMockManipulateIAM(ctrl), - ctx: authz.NewMockContext("orgID", "userID"), - iamID: "iamID", - step: iam_model.Step1, - }, - res: res{ - errFunc: caos_errs.IsPreconditionFailed, - }, - }, - { - name: "setup iam no id", - args: args{ - es: GetMockManipulateIAM(ctrl), - ctx: authz.NewMockContext("orgID", "userID"), - step: iam_model.Step1, - }, - res: res{ - errFunc: caos_errs.IsPreconditionFailed, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := tt.args.es.StartSetup(tt.args.ctx, tt.args.iamID, tt.args.step) - if (tt.res.errFunc != nil && !tt.res.errFunc(err)) || (err != nil && tt.res.errFunc == nil) { - t.Errorf("got wrong err: %v ", err) - return - } - if tt.res.errFunc != nil && tt.res.errFunc(err) { - return - } - if result.AggregateID == "" { - t.Errorf("result has no id") - } - if result.SetUpStarted != tt.res.iam.SetUpStarted { - t.Errorf("got wrong result setupStarted: expected: %v, actual: %v ", tt.res.iam.SetUpStarted, result.SetUpStarted) - } - }) - } -} +// +//func TestSetUpStarted(t *testing.T) { +// ctrl := gomock.NewController(t) +// type args struct { +// es *IAMEventstore +// ctx context.Context +// iamID string +// step iam_model.Step +// } +// type res struct { +// iam *iam_model.IAM +// errFunc func(err error) bool +// } +// tests := []struct { +// name string +// args args +// res res +// }{ +// { +// name: "setup started iam, ok", +// args: args{ +// es: GetMockManipulateIAMNotExisting(ctrl), +// ctx: authz.NewMockContext("orgID", "userID"), +// iamID: "iamID", +// step: iam_model.Step1, +// }, +// res: res{ +// iam: &iam_model.IAM{ObjectRoot: es_models.ObjectRoot{AggregateID: "iamID", Sequence: 1}, SetUpStarted: iam_model.Step1}, +// }, +// }, +// { +// name: "setup already started", +// args: args{ +// es: GetMockManipulateIAM(ctrl), +// ctx: authz.NewMockContext("orgID", "userID"), +// iamID: "iamID", +// step: iam_model.Step1, +// }, +// res: res{ +// errFunc: caos_errs.IsPreconditionFailed, +// }, +// }, +// { +// name: "setup iam no id", +// args: args{ +// es: GetMockManipulateIAM(ctrl), +// ctx: authz.NewMockContext("orgID", "userID"), +// step: iam_model.Step1, +// }, +// res: res{ +// errFunc: caos_errs.IsPreconditionFailed, +// }, +// }, +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// result, err := tt.args.es.StartSetup(tt.args.ctx, tt.args.iamID, tt.args.step) +// if (tt.res.errFunc != nil && !tt.res.errFunc(err)) || (err != nil && tt.res.errFunc == nil) { +// t.Errorf("got wrong err: %v ", err) +// return +// } +// if tt.res.errFunc != nil && tt.res.errFunc(err) { +// return +// } +// if result.AggregateID == "" { +// t.Errorf("result has no id") +// } +// if result.SetUpStarted != tt.res.iam.SetUpStarted { +// t.Errorf("got wrong result setupStarted: expected: %v, actual: %v ", tt.res.iam.SetUpStarted, result.SetUpStarted) +// } +// }) +// } +//} func TestSetUpDone(t *testing.T) { ctrl := gomock.NewController(t) @@ -2731,7 +2732,7 @@ func TestAddOrgIAMPolicy(t *testing.T) { return } if result.UserLoginMustBeDomain != tt.res.result.UserLoginMustBeDomain { - t.Errorf("got wrong result UserLoginMustBeDomain: expected: %v, actual: %v ", tt.res.result.UserLoginMustBeDomain, result.UserLoginMustBeDomain) + t.Errorf("got wrong result userLoginMustBeDomain: expected: %v, actual: %v ", tt.res.result.UserLoginMustBeDomain, result.UserLoginMustBeDomain) } }) } @@ -2810,7 +2811,7 @@ func TestChangeOrgIAMPolicy(t *testing.T) { return } if result.UserLoginMustBeDomain != tt.res.result.UserLoginMustBeDomain { - t.Errorf("got wrong result UserLoginMustBeDomain: expected: %v, actual: %v ", tt.res.result.UserLoginMustBeDomain, result.UserLoginMustBeDomain) + t.Errorf("got wrong result userLoginMustBeDomain: expected: %v, actual: %v ", tt.res.result.UserLoginMustBeDomain, result.UserLoginMustBeDomain) } }) } diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index bc790a76d2..552414f556 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -5,6 +5,7 @@ Errors: IDMissing: ID fehlt User: NotFound: Benutzer konnte nicht gefunden werden + AlreadyExists: Benutzer existierts bereits NotFoundOnOrg: Benutzer konnte in der gewünschten Organisation nicht gefunden werden NotAllowedOrg: Benutzer gehört nicht der benötigten Organisation an UserIDMissing: User ID fehlt @@ -41,6 +42,7 @@ Errors: NotMachine: Der Benutzer muss technisch sein NotAllowedToLink: Der Benutzer darf nicht mit einem externen Login Provider verlinkt werden Username: + AlreadyExists: Benutzername ist bereits vergeben Reservied: Benutzername ist bereits vergeben Code: Empty: Code ist leer @@ -64,6 +66,7 @@ Errors: IDPConfigNotExisting: IDP Provider ungültig für diese Organisation NotAllowed: Externer IDP ist auf dieser Organisation nicht erlaubt. MinimumExternalIDPNeeded: Mindestens ein IDP muss hinzugefügt werden. + AlreadyExists: External IDP ist bereits vergeben MFA: OTP: AlreadyReady: Multifaktor OTP (OneTimePassword) ist bereits eingerichtet @@ -84,6 +87,7 @@ Errors: ValidateLoginFailed: Zugangsdaten konnten nicht validiert werden CloneWarning: Authentifizierungsdaten wurden möglicherweise geklont Org: + AlreadyExist: Organisationsname existiert bereits Invalid: Organisation ist ungültig AlreadyDeactivated: Organisation ist bereits deaktiviert AlreadyActive: Organisation ist bereits aktiv @@ -250,6 +254,8 @@ Errors: NotActive: Benutzer Berechtigung ist nicht aktiv NotInactive: Benutzer Berechtigung ist nicht deaktiviert NoPermissionForProject: Benutzer hat keine Rechte auf diesem Projekt + IDPConfig: + AlreadyExists: IDP Konfiguration mit diesem Name existiert bereits Changes: NotFound: Es konnte kein Änderungsverlauf gefunden werden Token: diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index 42c09a2928..c04914945a 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -5,6 +5,7 @@ Errors: IDMissing: ID missing User: NotFound: User could not be found + AlreadyExists: User already exists NotFoundOnOrg: User could not be found on chosen organisation NotAllowedOrg: User is no member of the required organisation UserIDMissing: User ID missing @@ -41,6 +42,7 @@ Errors: NotMachine: The User must be technical NotAllowedToLink: User is not allowed to link with external login provider Username: + AlreadyExists: Username already taken Reservied: Username is already taken Code: Empty: Code is empty @@ -64,6 +66,7 @@ Errors: IDPConfigNotExisting: IDP provider invalid for this organisation NotAllowed: External IDP not allowed on this organisation MinimumExternalIDPNeeded: At least one IDP must be added + AlreadyExists: External IDP already taken MFA: OTP: AlreadyReady: Multifactor OTP (OneTimePassword) is already set up @@ -84,6 +87,7 @@ Errors: ValidateLoginFailed: Error on validate login credentials CloneWarning: Credentials may be cloned Org: + AlreadyExist: Organisationname already taken Invalid: Organisation is invalid AlreadyDeactivated: Organisation is already deactivated AlreadyActive: Organisation is already ative @@ -247,6 +251,8 @@ Errors: NotActive: User grant is not active NotInactive: User grant is not deactivated NoPermissionForProject: User has no permissions on this project + IDPConfig: + AlreadyExists: IDP Configuration with this name already exists Changes: NotFound: No history found Token: diff --git a/internal/v2/command/iam_idp_config.go b/internal/v2/command/iam_idp_config.go index 62cca6cada..11326d7d30 100644 --- a/internal/v2/command/iam_idp_config.go +++ b/internal/v2/command/iam_idp_config.go @@ -9,7 +9,6 @@ import ( "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/v2/repository/iam" iam_repo "github.com/caos/zitadel/internal/v2/repository/iam" ) @@ -110,27 +109,17 @@ func (r *CommandSide) ReactivateDefaultIDPConfig(ctx context.Context, idpID stri } func (r *CommandSide) RemoveDefaultIDPConfig(ctx context.Context, idpID string) error { - _, err := r.pushDefaultIDPWriteModel(ctx, idpID, func(a *iam.Aggregate, _ *IAMIDPConfigWriteModel) *iam.Aggregate { - a.Aggregate = *a.PushEvents(iam_repo.NewIDPConfigRemovedEvent(ctx, idpID)) - return a - }) - return err -} - -func (r *CommandSide) pushDefaultIDPWriteModel(ctx context.Context, idpID string, eventSetter func(*iam.Aggregate, *IAMIDPConfigWriteModel) *iam.Aggregate) (*IAMIDPConfigWriteModel, error) { - writeModel := NewIAMIDPConfigWriteModel(idpID) - err := r.eventstore.FilterToQueryReducer(ctx, writeModel) + existingIDP, err := r.iamIDPConfigWriteModelByID(ctx, idpID) if err != nil { - return nil, err + return err } - - aggregate := eventSetter(IAMAggregateFromWriteModel(&writeModel.WriteModel), writeModel) - err = r.eventstore.PushAggregate(ctx, writeModel, aggregate) - if err != nil { - return nil, err + if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified { + return caos_errs.ThrowNotFound(nil, "IAM-4M0xy", "Errors.IAM.IDPConfig.NotExisting") } + iamAgg := IAMAggregateFromWriteModel(&existingIDP.WriteModel) + iamAgg.PushEvents(iam_repo.NewIDPConfigRemovedEvent(ctx, existingIDP.ResourceOwner, idpID, existingIDP.Name)) - return writeModel, nil + return r.eventstore.PushAggregate(ctx, existingIDP, iamAgg) } func (r *CommandSide) iamIDPConfigWriteModelByID(ctx context.Context, idpID string) (policy *IAMIDPConfigWriteModel, err error) { diff --git a/internal/v2/command/org.go b/internal/v2/command/org.go index 601eceeb2b..2062c6b681 100644 --- a/internal/v2/command/org.go +++ b/internal/v2/command/org.go @@ -122,7 +122,6 @@ func (r *CommandSide) addOrg(ctx context.Context, organisation *domain.Org) (_ * 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 { diff --git a/internal/v2/command/org_idp_config.go b/internal/v2/command/org_idp_config.go index ce82948a15..119b4c925c 100644 --- a/internal/v2/command/org_idp_config.go +++ b/internal/v2/command/org_idp_config.go @@ -9,7 +9,6 @@ import ( "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/v2/repository/org" org_repo "github.com/caos/zitadel/internal/v2/repository/org" ) @@ -34,6 +33,7 @@ func (r *CommandSide) AddIDPConfig(ctx context.Context, config *domain.IDPConfig orgAgg.PushEvents( org_repo.NewIDPConfigAddedEvent( ctx, + orgAgg.ResourceOwner(), idpConfigID, config.Name, config.Type, @@ -110,27 +110,21 @@ func (r *CommandSide) ReactivateIDPConfig(ctx context.Context, idpID, orgID stri } func (r *CommandSide) RemoveIDPConfig(ctx context.Context, idpID, orgID string) error { - _, err := r.pushIDPWriteModel(ctx, idpID, orgID, func(a *org.Aggregate, _ *OrgIDPConfigWriteModel) *org.Aggregate { - a.Aggregate = *a.PushEvents(org_repo.NewIDPConfigRemovedEvent(ctx, idpID)) - return a - }) - return err -} - -func (r *CommandSide) pushIDPWriteModel(ctx context.Context, idpID, orgID string, eventSetter func(*org.Aggregate, *OrgIDPConfigWriteModel) *org.Aggregate) (*OrgIDPConfigWriteModel, error) { - writeModel := NewOrgIDPConfigWriteModel(idpID, orgID) - err := r.eventstore.FilterToQueryReducer(ctx, writeModel) + existingIDP, err := r.orgIDPConfigWriteModelByID(ctx, idpID, orgID) if err != nil { - return nil, err + return err } - aggregate := eventSetter(OrgAggregateFromWriteModel(&writeModel.WriteModel), writeModel) - err = r.eventstore.PushAggregate(ctx, writeModel, aggregate) - if err != nil { - return nil, err + if existingIDP.State == domain.IDPConfigStateRemoved || existingIDP.State == domain.IDPConfigStateUnspecified { + return caos_errs.ThrowNotFound(nil, "Org-Yx9vd", "Errors.Org.IDPConfig.NotExisting") } + if existingIDP.State != domain.IDPConfigStateInactive { + return caos_errs.ThrowPreconditionFailed(nil, "Org-5Mo0d", "Errors.Org.IDPConfig.NotInactive") + } + orgAgg := OrgAggregateFromWriteModel(&existingIDP.WriteModel) + orgAgg.PushEvents(org_repo.NewIDPConfigRemovedEvent(ctx, existingIDP.ResourceOwner, idpID, existingIDP.Name)) - return writeModel, nil + return r.eventstore.PushAggregate(ctx, existingIDP, orgAgg) } func (r *CommandSide) orgIDPConfigWriteModelByID(ctx context.Context, idpID, orgID string) (policy *OrgIDPConfigWriteModel, err error) { diff --git a/internal/v2/command/project.go b/internal/v2/command/project.go index bcc8b25b6c..d4cb2c6d18 100644 --- a/internal/v2/command/project.go +++ b/internal/v2/command/project.go @@ -29,7 +29,6 @@ func (r *CommandSide) addProject(ctx context.Context, projectAdd *domain.Project if err != nil { return nil, nil, err } -// TODO: Add uniqueness check addedProject := NewProjectWriteModel(projectAdd.AggregateID, resourceOwner) projectAgg := ProjectAggregateFromWriteModel(&addedProject.WriteModel) @@ -38,7 +37,7 @@ func (r *CommandSide) addProject(ctx context.Context, projectAdd *domain.Project // projectRole = domain.RoleProjectOwnerGlobal //} projectAgg.PushEvents( - project.NewProjectAddedEvent(ctx, projectAdd.Name), + project.NewProjectAddedEvent(ctx, projectAdd.Name, resourceOwner), project.NewMemberAddedEvent(ctx, ownerUserID, projectRole), ) return projectAgg, addedProject, nil diff --git a/internal/v2/command/setup.go b/internal/v2/command/setup.go index ab95b82fbe..250ed584fd 100644 --- a/internal/v2/command/setup.go +++ b/internal/v2/command/setup.go @@ -88,7 +88,7 @@ func (r *CommandSide) setup(ctx context.Context, step Step, iamAggregateProvider _, err = r.eventstore.PushAggregates(ctx, iamAgg) if err != nil { - return caos_errs.ThrowPreconditionFailedf(nil, "EVENT-dbG31", "Setup %s failed", step.Step()) + return caos_errs.ThrowPreconditionFailedf(nil, "EVENT-dbG31", "Setup %v failed", step.Step()) } logging.LogWithFields("SETUP-Sg1t1", "step", step.Step()).Info("setup step done") return nil diff --git a/internal/v2/command/user.go b/internal/v2/command/user.go index 1f890b8a34..07c140515f 100644 --- a/internal/v2/command/user.go +++ b/internal/v2/command/user.go @@ -32,9 +32,7 @@ func (r *CommandSide) ChangeUsername(ctx context.Context, orgID, userID, userNam return err } userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) - userAgg.PushEvents(user.NewUsernameChangedEvent(ctx, userName)) - //TODO: Check Uniqueness - //TODO: release old username, set new unique username + userAgg.PushEvents(user.NewUsernameChangedEvent(ctx, existingUser.UserName, userName, orgIAMPolicy.UserLoginMustBeDomain)) return r.eventstore.PushAggregate(ctx, existingUser, userAgg) } @@ -130,9 +128,12 @@ func (r *CommandSide) RemoveUser(ctx context.Context, userID, resourceOwner stri if existingUser.UserState == domain.UserStateUnspecified || existingUser.UserState == domain.UserStateDeleted { return caos_errs.ThrowNotFound(nil, "COMMAND-5M0od", "Errors.User.NotFound") } + orgIAMPolicy, err := r.getOrgIAMPolicy(ctx, existingUser.ResourceOwner) + if err != nil { + return err + } userAgg := UserAggregateFromWriteModel(&existingUser.WriteModel) - userAgg.PushEvents(user.NewUserRemovedEvent(ctx)) - //TODO: release unqie username + userAgg.PushEvents(user.NewUserRemovedEvent(ctx, existingUser.ResourceOwner, existingUser.UserName, orgIAMPolicy.UserLoginMustBeDomain)) //TODO: remove user grants return r.eventstore.PushAggregate(ctx, existingUser, userAgg) diff --git a/internal/v2/command/user_human.go b/internal/v2/command/user_human.go index a22b163d61..32f28b833f 100644 --- a/internal/v2/command/user_human.go +++ b/internal/v2/command/user_human.go @@ -27,6 +27,9 @@ func (r *CommandSide) AddHuman(ctx context.Context, orgID string, human *domain. } err = r.eventstore.PushAggregate(ctx, addedHuman, userAgg) if err != nil { + if caos_errs.IsErrorAlreadyExists(err) { + return nil, caos_errs.ThrowAlreadyExists(err, "COMMAND-4kSff", "Errors.User.AlreadyExists") + } return nil, err } @@ -47,6 +50,9 @@ func (r *CommandSide) RegisterHuman(ctx context.Context, orgID string, human *do } err = r.eventstore.PushAggregate(ctx, addedHuman, userAgg) if err != nil { + if caos_errs.IsErrorAlreadyExists(err) { + return nil, caos_errs.ThrowAlreadyExists(err, "COMMAND-4kSff", "Errors.User.AlreadyExists") + } return nil, err } @@ -76,7 +82,6 @@ func (r *CommandSide) createHuman(ctx context.Context, orgID string, human *doma } addedHuman := NewHumanWriteModel(human.AggregateID, orgID) - //TODO: Check Unique Username or unique external idp if err := human.CheckOrgIAMPolicy(human.Username, orgIAMPolicy); err != nil { return nil, nil, err } @@ -88,9 +93,9 @@ func (r *CommandSide) createHuman(ctx context.Context, orgID string, human *doma userAgg := UserAggregateFromWriteModel(&addedHuman.WriteModel) var createEvent eventstore.EventPusher if selfregister { - createEvent = createRegisterHumanEvent(ctx, human.Username, human) + createEvent = createRegisterHumanEvent(ctx, orgID, human, orgIAMPolicy.UserLoginMustBeDomain) } else { - createEvent = createAddHumanEvent(ctx, human.Username, human) + createEvent = createAddHumanEvent(ctx, orgID, human, orgIAMPolicy.UserLoginMustBeDomain) } userAgg.PushEvents(createEvent) @@ -152,10 +157,11 @@ func (r *CommandSide) ResendInitialMail(ctx context.Context, userID, email, reso return r.eventstore.PushAggregate(ctx, existingEmail, userAgg) } -func createAddHumanEvent(ctx context.Context, username string, human *domain.Human) *user.HumanAddedEvent { +func createAddHumanEvent(ctx context.Context, orgID string, human *domain.Human, userLoginMustBeDomain bool) *user.HumanAddedEvent { addEvent := user.NewHumanAddedEvent( ctx, - username, + orgID, + human.Username, human.FirstName, human.LastName, human.NickName, @@ -163,6 +169,7 @@ func createAddHumanEvent(ctx context.Context, username string, human *domain.Hum human.PreferredLanguage, human.Gender, human.EmailAddress, + userLoginMustBeDomain, ) if human.Phone != nil { addEvent.AddPhoneData(human.PhoneNumber) @@ -181,10 +188,11 @@ func createAddHumanEvent(ctx context.Context, username string, human *domain.Hum return addEvent } -func createRegisterHumanEvent(ctx context.Context, username string, human *domain.Human) *user.HumanRegisteredEvent { +func createRegisterHumanEvent(ctx context.Context, orgID string, human *domain.Human, userLoginMustBeDomain bool) *user.HumanRegisteredEvent { addEvent := user.NewHumanRegisteredEvent( ctx, - username, + orgID, + human.Username, human.FirstName, human.LastName, human.NickName, @@ -192,6 +200,7 @@ func createRegisterHumanEvent(ctx context.Context, username string, human *domai human.PreferredLanguage, human.Gender, human.EmailAddress, + userLoginMustBeDomain, ) if human.Phone != nil { addEvent.AddPhoneData(human.PhoneNumber) diff --git a/internal/v2/command/user_human_externalidp.go b/internal/v2/command/user_human_externalidp.go index 2ca0c5ee3f..cba2b273c3 100644 --- a/internal/v2/command/user_human_externalidp.go +++ b/internal/v2/command/user_human_externalidp.go @@ -34,8 +34,6 @@ func (r *CommandSide) removeHumanExternalIDP(ctx context.Context, externalIDP *d user.NewHumanExternalIDPCascadeRemovedEvent(ctx, externalIDP.IDPConfigID, externalIDP.ExternalUserID), ) } - - //TODO: Release unique externalidp return r.eventstore.PushAggregate(ctx, existingExternalIDP, userAgg) } diff --git a/internal/v2/command/user_machine.go b/internal/v2/command/user_machine.go index ea35a9f9a1..c46cdfe503 100644 --- a/internal/v2/command/user_machine.go +++ b/internal/v2/command/user_machine.go @@ -16,7 +16,6 @@ func (r *CommandSide) AddMachine(ctx context.Context, orgID string, machine *dom if err != nil { return nil, err } - //TODO: Check Unique username machine.AggregateID = userID orgIAMPolicy, err := r.getOrgIAMPolicy(ctx, orgID) if err != nil { @@ -34,6 +33,7 @@ func (r *CommandSide) AddMachine(ctx context.Context, orgID string, machine *dom machine.Username, machine.Name, machine.Description, + orgIAMPolicy.UserLoginMustBeDomain, ), ) err = r.eventstore.PushAggregate(ctx, addedMachine, userAgg) diff --git a/internal/v2/repository/iam/event_iam_project_set.go b/internal/v2/repository/iam/event_iam_project_set.go index fd21b0f176..f3ba02bd12 100644 --- a/internal/v2/repository/iam/event_iam_project_set.go +++ b/internal/v2/repository/iam/event_iam_project_set.go @@ -23,6 +23,10 @@ func (e *ProjectSetEvent) Data() interface{} { return e } +func (e *ProjectSetEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewIAMProjectSetEvent(ctx context.Context, projectID string) *ProjectSetEvent { return &ProjectSetEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/iam/event_org_set.go b/internal/v2/repository/iam/event_org_set.go index b80059ddfd..dc091885d5 100644 --- a/internal/v2/repository/iam/event_org_set.go +++ b/internal/v2/repository/iam/event_org_set.go @@ -23,6 +23,10 @@ func (e *GlobalOrgSetEvent) Data() interface{} { return e } +func (e *GlobalOrgSetEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewGlobalOrgSetEventEvent(ctx context.Context, orgID string) *GlobalOrgSetEvent { return &GlobalOrgSetEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/iam/events_step.go b/internal/v2/repository/iam/events_step.go index 3e13fb043e..a414220fb0 100644 --- a/internal/v2/repository/iam/events_step.go +++ b/internal/v2/repository/iam/events_step.go @@ -26,6 +26,10 @@ func (e *SetupStepEvent) Data() interface{} { return e } +func (e *SetupStepEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func SetupStepMapper(event *repository.Event) (eventstore.EventReader, error) { step := &SetupStepEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), diff --git a/internal/v2/repository/iam/idp_config.go b/internal/v2/repository/iam/idp_config.go index fe14732dc0..9bf4edf08c 100644 --- a/internal/v2/repository/iam/idp_config.go +++ b/internal/v2/repository/iam/idp_config.go @@ -23,7 +23,7 @@ type IDPConfigAddedEvent struct { func NewIDPConfigAddedEvent( ctx context.Context, - configID string, + configID, name string, configType domain.IDPConfigType, stylingType domain.IDPConfigStylingType, @@ -31,9 +31,10 @@ func NewIDPConfigAddedEvent( return &IDPConfigAddedEvent{ IDPConfigAddedEvent: *idpconfig.NewIDPConfigAddedEvent( - eventstore.NewBaseEventForPush( + eventstore.NewBaseEventForPushWithResourceOwner( ctx, IDPConfigAddedEventType, + domain.IAMID, ), configID, name, @@ -87,16 +88,19 @@ type IDPConfigRemovedEvent struct { func NewIDPConfigRemovedEvent( ctx context.Context, - configID string, + resourceOwner, + configID, + name string, ) *IDPConfigRemovedEvent { - return &IDPConfigRemovedEvent{ IDPConfigRemovedEvent: *idpconfig.NewIDPConfigRemovedEvent( - eventstore.NewBaseEventForPush( + eventstore.NewBaseEventForPushWithResourceOwner( ctx, IDPConfigRemovedEventType, + resourceOwner, ), configID, + name, ), } } diff --git a/internal/v2/repository/idpconfig/idp_config.go b/internal/v2/repository/idpconfig/idp_config.go index 717bfa08cd..3b8779af3e 100644 --- a/internal/v2/repository/idpconfig/idp_config.go +++ b/internal/v2/repository/idpconfig/idp_config.go @@ -9,6 +9,23 @@ import ( "github.com/caos/zitadel/internal/v2/domain" ) +const ( + uniqueIDPConfigNameType = "idp_config_names" +) + +func NewAddIDPConfigNameUniqueConstraint(idpConfigName, resourceOwner string) *eventstore.EventUniqueConstraint { + return eventstore.NewAddEventUniqueConstraint( + uniqueIDPConfigNameType, + idpConfigName+resourceOwner, + "Errors.IDPConfig.AlreadyExists") +} + +func NewRemoveIDPConfigNameUniqueConstraint(idpConfigName, resourceOwner string) *eventstore.EventUniqueConstraint { + return eventstore.NewRemoveEventUniqueConstraint( + uniqueIDPConfigNameType, + idpConfigName+resourceOwner) +} + type IDPConfigAddedEvent struct { eventstore.BaseEvent `json:"-"` @@ -20,12 +37,11 @@ type IDPConfigAddedEvent struct { func NewIDPConfigAddedEvent( base *eventstore.BaseEvent, - configID string, + configID, name string, configType domain.IDPConfigType, stylingType domain.IDPConfigStylingType, ) *IDPConfigAddedEvent { - return &IDPConfigAddedEvent{ BaseEvent: *base, ConfigID: configID, @@ -39,6 +55,10 @@ func (e *IDPConfigAddedEvent) Data() interface{} { return e } +func (e *IDPConfigAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return []*eventstore.EventUniqueConstraint{NewAddIDPConfigNameUniqueConstraint(e.Name, e.ResourceOwner())} +} + func IDPConfigAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) { e := &IDPConfigAddedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), @@ -64,6 +84,10 @@ func (e *IDPConfigChangedEvent) Data() interface{} { return e } +func (e *IDPConfigChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewIDPConfigChangedEvent( base *eventstore.BaseEvent, configID string, @@ -130,6 +154,10 @@ func (e *IDPConfigDeactivatedEvent) Data() interface{} { return e } +func (e *IDPConfigDeactivatedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func IDPConfigDeactivatedEventMapper(event *repository.Event) (eventstore.EventReader, error) { e := &IDPConfigDeactivatedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), @@ -164,6 +192,10 @@ func (e *IDPConfigReactivatedEvent) Data() interface{} { return e } +func (e *IDPConfigReactivatedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func IDPConfigReactivatedEventMapper(event *repository.Event) (eventstore.EventReader, error) { e := &IDPConfigReactivatedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), @@ -181,16 +213,19 @@ type IDPConfigRemovedEvent struct { eventstore.BaseEvent `json:"-"` ConfigID string `json:"idpConfigId"` + Name string } func NewIDPConfigRemovedEvent( base *eventstore.BaseEvent, configID string, + name string, ) *IDPConfigRemovedEvent { return &IDPConfigRemovedEvent{ BaseEvent: *base, ConfigID: configID, + Name: name, } } @@ -198,6 +233,10 @@ func (e *IDPConfigRemovedEvent) Data() interface{} { return e } +func (e *IDPConfigRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return []*eventstore.EventUniqueConstraint{NewRemoveIDPConfigNameUniqueConstraint(e.Name, e.ResourceOwner())} +} + func IDPConfigRemovedEventMapper(event *repository.Event) (eventstore.EventReader, error) { e := &IDPConfigRemovedEvent{ BaseEvent: *eventstore.BaseEventFromRepo(event), diff --git a/internal/v2/repository/idpconfig/oidc_config.go b/internal/v2/repository/idpconfig/oidc_config.go index 24b2997e9d..a0830708d8 100644 --- a/internal/v2/repository/idpconfig/oidc_config.go +++ b/internal/v2/repository/idpconfig/oidc_config.go @@ -32,6 +32,10 @@ func (e *OIDCConfigAddedEvent) Data() interface{} { return e } +func (e *OIDCConfigAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewOIDCConfigAddedEvent( base *eventstore.BaseEvent, clientID, @@ -86,6 +90,10 @@ func (e *OIDCConfigChangedEvent) Data() interface{} { return e } +func (e *OIDCConfigChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewOIDCConfigChangedEvent( base *eventstore.BaseEvent, idpConfigID string, diff --git a/internal/v2/repository/member/events.go b/internal/v2/repository/member/events.go index c4d8c018e3..2118285dfa 100644 --- a/internal/v2/repository/member/events.go +++ b/internal/v2/repository/member/events.go @@ -24,6 +24,10 @@ func (e *MemberAddedEvent) Data() interface{} { return e } +func (e *MemberAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewMemberAddedEvent( base *eventstore.BaseEvent, userID string, @@ -61,6 +65,10 @@ func (e *MemberChangedEvent) Data() interface{} { return e } +func (e *MemberChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewMemberChangedEvent( base *eventstore.BaseEvent, userID string, @@ -96,6 +104,10 @@ func (e *MemberRemovedEvent) Data() interface{} { return e } +func (e *MemberRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewRemovedEvent( base *eventstore.BaseEvent, userID string, diff --git a/internal/v2/repository/org/domain.go b/internal/v2/repository/org/domain.go index c3505e8b65..6bbf4a589b 100644 --- a/internal/v2/repository/org/domain.go +++ b/internal/v2/repository/org/domain.go @@ -31,6 +31,10 @@ func (e *DomainAddedEvent) Data() interface{} { return e } +func (e *DomainAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewDomainAddedEvent(ctx context.Context, domain string) *DomainAddedEvent { return &DomainAddedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -65,6 +69,10 @@ func (e *DomainVerificationAddedEvent) Data() interface{} { return e } +func (e *DomainVerificationAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewDomainVerificationAddedEvent( ctx context.Context, domain string, @@ -103,6 +111,10 @@ func (e *DomainVerificationFailedEvent) Data() interface{} { return e } +func (e *DomainVerificationFailedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewDomainVerificationFailedEvent(ctx context.Context, domain string) *DomainVerificationFailedEvent { return &DomainVerificationFailedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -135,6 +147,10 @@ func (e *DomainVerifiedEvent) Data() interface{} { return e } +func (e *DomainVerifiedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewDomainVerifiedEvent(ctx context.Context, domain string) *DomainVerifiedEvent { return &DomainVerifiedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -167,6 +183,10 @@ func (e *DomainPrimarySetEvent) Data() interface{} { return e } +func (e *DomainPrimarySetEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewDomainPrimarySetEvent(ctx context.Context, domain string) *DomainPrimarySetEvent { return &DomainPrimarySetEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -199,6 +219,10 @@ func (e *DomainRemovedEvent) Data() interface{} { return e } +func (e *DomainRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewDomainRemovedEvent(ctx context.Context, domain string) *DomainRemovedEvent { return &DomainRemovedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/org/idp_config.go b/internal/v2/repository/org/idp_config.go index 61f2029ab1..21882186ae 100644 --- a/internal/v2/repository/org/idp_config.go +++ b/internal/v2/repository/org/idp_config.go @@ -22,7 +22,8 @@ type IDPConfigAddedEvent struct { func NewIDPConfigAddedEvent( ctx context.Context, - configID string, + resourceOwner, + configID, name string, configType domain.IDPConfigType, stylingType domain.IDPConfigStylingType, @@ -30,9 +31,10 @@ func NewIDPConfigAddedEvent( return &IDPConfigAddedEvent{ IDPConfigAddedEvent: *idpconfig.NewIDPConfigAddedEvent( - eventstore.NewBaseEventForPush( + eventstore.NewBaseEventForPushWithResourceOwner( ctx, IDPConfigAddedEventType, + resourceOwner, ), configID, name, @@ -86,16 +88,20 @@ type IDPConfigRemovedEvent struct { func NewIDPConfigRemovedEvent( ctx context.Context, - configID string, + resourceOwner, + configID, + name string, ) *IDPConfigRemovedEvent { return &IDPConfigRemovedEvent{ IDPConfigRemovedEvent: *idpconfig.NewIDPConfigRemovedEvent( - eventstore.NewBaseEventForPush( + eventstore.NewBaseEventForPushWithResourceOwner( ctx, IDPConfigRemovedEventType, + resourceOwner, ), configID, + name, ), } } diff --git a/internal/v2/repository/org/org.go b/internal/v2/repository/org/org.go index 064c38a9d2..5ea7b2224e 100644 --- a/internal/v2/repository/org/org.go +++ b/internal/v2/repository/org/org.go @@ -10,6 +10,7 @@ import ( ) const ( + uniqueOrgname = "org_name" OrgAddedEventType = orgEventTypePrefix + "added" OrgChangedEventType = orgEventTypePrefix + "changed" OrgDeactivatedEventType = orgEventTypePrefix + "deactivated" @@ -17,6 +18,25 @@ const ( OrgRemovedEventType = orgEventTypePrefix + "removed" ) +type OrgnameUniqueConstraint struct { + uniqueType string + orgName string + action eventstore.UniqueConstraintAction +} + +func NewAddOrgnameUniqueConstraint(orgName string) *eventstore.EventUniqueConstraint { + return eventstore.NewAddEventUniqueConstraint( + uniqueOrgname, + orgName, + "Errors.Org.AlreadyExists") +} + +func NewRemoveUsernameUniqueConstraint(orgName string) *eventstore.EventUniqueConstraint { + return eventstore.NewRemoveEventUniqueConstraint( + uniqueOrgname, + orgName) +} + type OrgAddedEvent struct { eventstore.BaseEvent `json:"-"` @@ -27,6 +47,10 @@ func (e *OrgAddedEvent) Data() interface{} { return e } +func (e *OrgAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return []*eventstore.EventUniqueConstraint{NewAddOrgnameUniqueConstraint(e.Name)} +} + func NewOrgAddedEvent(ctx context.Context, name string) *OrgAddedEvent { return &OrgAddedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -59,6 +83,10 @@ func (e *OrgChangedEvent) Data() interface{} { return e } +func (e *OrgChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewOrgChangedEvent(ctx context.Context, name string) *OrgChangedEvent { return &OrgChangedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -89,6 +117,10 @@ func (e *OrgDeactivatedEvent) Data() interface{} { return e } +func (e *OrgDeactivatedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewOrgDeactivatedEvent(ctx context.Context) *OrgDeactivatedEvent { return &OrgDeactivatedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -118,6 +150,10 @@ func (e *OrgReactivatedEvent) Data() interface{} { return e } +func (e *OrgReactivatedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewOrgReactivatedEvent(ctx context.Context) *OrgReactivatedEvent { return &OrgReactivatedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/policy/label.go b/internal/v2/repository/policy/label.go index b349037914..400a6bf025 100644 --- a/internal/v2/repository/policy/label.go +++ b/internal/v2/repository/policy/label.go @@ -24,6 +24,10 @@ func (e *LabelPolicyAddedEvent) Data() interface{} { return e } +func (e *LabelPolicyAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewLabelPolicyAddedEvent( base *eventstore.BaseEvent, primaryColor, @@ -61,6 +65,10 @@ func (e *LabelPolicyChangedEvent) Data() interface{} { return e } +func (e *LabelPolicyChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewLabelPolicyChangedEvent( base *eventstore.BaseEvent, changes []LabelPolicyChanges, @@ -112,6 +120,10 @@ func (e *LabelPolicyRemovedEvent) Data() interface{} { return nil } +func (e *LabelPolicyRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewLabelPolicyRemovedEvent(base *eventstore.BaseEvent) *LabelPolicyRemovedEvent { return &LabelPolicyRemovedEvent{ BaseEvent: *base, diff --git a/internal/v2/repository/policy/login.go b/internal/v2/repository/policy/login.go index 3f2fb07bd5..ae1304609f 100644 --- a/internal/v2/repository/policy/login.go +++ b/internal/v2/repository/policy/login.go @@ -29,6 +29,10 @@ func (e *LoginPolicyAddedEvent) Data() interface{} { return e } +func (e *LoginPolicyAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewLoginPolicyAddedEvent( base *eventstore.BaseEvent, allowUserNamePassword, @@ -77,6 +81,10 @@ func (e *LoginPolicyChangedEvent) Data() interface{} { return e } +func (e *LoginPolicyChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewLoginPolicyChangedEvent( base *eventstore.BaseEvent, changes []LoginPolicyChanges, @@ -146,6 +154,10 @@ func (e *LoginPolicyRemovedEvent) Data() interface{} { return nil } +func (e *LoginPolicyRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewLoginPolicyRemovedEvent(base *eventstore.BaseEvent) *LoginPolicyRemovedEvent { return &LoginPolicyRemovedEvent{ BaseEvent: *base, diff --git a/internal/v2/repository/policy/policy_login_factors.go b/internal/v2/repository/policy/policy_login_factors.go index 0c85207f56..02866d3abe 100644 --- a/internal/v2/repository/policy/policy_login_factors.go +++ b/internal/v2/repository/policy/policy_login_factors.go @@ -51,6 +51,10 @@ func (e *SecondFactorAddedEvent) Data() interface{} { return e } +func (e *SecondFactorAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + type SecondFactorRemovedEvent struct { eventstore.BaseEvent `json:"-"` MFAType domain.SecondFactorType `json:"mfaType"` @@ -83,6 +87,10 @@ func (e *SecondFactorRemovedEvent) Data() interface{} { return e } +func (e *SecondFactorRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + type MultiFactorAddedEvent struct { eventstore.BaseEvent `json:"-"` @@ -116,6 +124,10 @@ func (e *MultiFactorAddedEvent) Data() interface{} { return e } +func (e *MultiFactorAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + type MultiFactorRemovedEvent struct { eventstore.BaseEvent `json:"-"` MFAType domain.MultiFactorType `json:"mfaType"` @@ -147,3 +159,7 @@ func MultiFactorRemovedEventMapper(event *repository.Event) (eventstore.EventRea func (e *MultiFactorRemovedEvent) Data() interface{} { return e } + +func (e *MultiFactorRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} diff --git a/internal/v2/repository/policy/policy_login_identity_provider.go b/internal/v2/repository/policy/policy_login_identity_provider.go index 70c3197e54..e780316eaa 100644 --- a/internal/v2/repository/policy/policy_login_identity_provider.go +++ b/internal/v2/repository/policy/policy_login_identity_provider.go @@ -25,6 +25,10 @@ func (e *IdentityProviderAddedEvent) Data() interface{} { return e } +func (e *IdentityProviderAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewIdentityProviderAddedEvent( base *eventstore.BaseEvent, idpConfigID string, @@ -61,6 +65,10 @@ func (e *IdentityProviderRemovedEvent) Data() interface{} { return e } +func (e *IdentityProviderRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewIdentityProviderRemovedEvent( base *eventstore.BaseEvent, idpConfigID string, diff --git a/internal/v2/repository/policy/policy_org_iam.go b/internal/v2/repository/policy/policy_org_iam.go index c5cde64658..81cae5c60f 100644 --- a/internal/v2/repository/policy/policy_org_iam.go +++ b/internal/v2/repository/policy/policy_org_iam.go @@ -24,6 +24,10 @@ func (e *OrgIAMPolicyAddedEvent) Data() interface{} { return e } +func (e *OrgIAMPolicyAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewOrgIAMPolicyAddedEvent( base *eventstore.BaseEvent, userLoginMustBeDomain bool, @@ -58,6 +62,10 @@ func (e *OrgIAMPolicyChangedEvent) Data() interface{} { return e } +func (e *OrgIAMPolicyChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewOrgIAMPolicyChangedEvent( base *eventstore.BaseEvent, changes []OrgIAMPolicyChanges, @@ -103,6 +111,10 @@ func (e *OrgIAMPolicyRemovedEvent) Data() interface{} { return nil } +func (e *OrgIAMPolicyRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewOrgIAMPolicyRemovedEvent(base *eventstore.BaseEvent) *OrgIAMPolicyRemovedEvent { return &OrgIAMPolicyRemovedEvent{ BaseEvent: *base, diff --git a/internal/v2/repository/policy/policy_password_age.go b/internal/v2/repository/policy/policy_password_age.go index e3ead7b2e9..0a39dfb4d5 100644 --- a/internal/v2/repository/policy/policy_password_age.go +++ b/internal/v2/repository/policy/policy_password_age.go @@ -24,6 +24,10 @@ func (e *PasswordAgePolicyAddedEvent) Data() interface{} { return e } +func (e *PasswordAgePolicyAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewPasswordAgePolicyAddedEvent( base *eventstore.BaseEvent, expireWarnDays, @@ -61,6 +65,10 @@ func (e *PasswordAgePolicyChangedEvent) Data() interface{} { return e } +func (e *PasswordAgePolicyChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewPasswordAgePolicyChangedEvent( base *eventstore.BaseEvent, changes []PasswordAgePolicyChanges, @@ -112,6 +120,10 @@ func (e *PasswordAgePolicyRemovedEvent) Data() interface{} { return nil } +func (e *PasswordAgePolicyRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewPasswordAgePolicyRemovedEvent(base *eventstore.BaseEvent) *PasswordAgePolicyRemovedEvent { return &PasswordAgePolicyRemovedEvent{ BaseEvent: *base, diff --git a/internal/v2/repository/policy/policy_password_complexity.go b/internal/v2/repository/policy/policy_password_complexity.go index aafe100227..7053761a7c 100644 --- a/internal/v2/repository/policy/policy_password_complexity.go +++ b/internal/v2/repository/policy/policy_password_complexity.go @@ -28,6 +28,10 @@ func (e *PasswordComplexityPolicyAddedEvent) Data() interface{} { return e } +func (e *PasswordComplexityPolicyAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewPasswordComplexityPolicyAddedEvent( base *eventstore.BaseEvent, minLength uint64, @@ -73,6 +77,10 @@ func (e *PasswordComplexityPolicyChangedEvent) Data() interface{} { return e } +func (e *PasswordComplexityPolicyChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewPasswordComplexityPolicyChangedEvent( base *eventstore.BaseEvent, changes []PasswordComplexityPolicyChanges, @@ -142,6 +150,10 @@ func (e *PasswordComplexityPolicyRemovedEvent) Data() interface{} { return nil } +func (e *PasswordComplexityPolicyRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewPasswordComplexityPolicyRemovedEvent(base *eventstore.BaseEvent) *PasswordComplexityPolicyRemovedEvent { return &PasswordComplexityPolicyRemovedEvent{ BaseEvent: *base, diff --git a/internal/v2/repository/policy/policy_password_lockout.go b/internal/v2/repository/policy/policy_password_lockout.go index 820ca44d52..5bae5ad6f7 100644 --- a/internal/v2/repository/policy/policy_password_lockout.go +++ b/internal/v2/repository/policy/policy_password_lockout.go @@ -25,6 +25,10 @@ func (e *PasswordLockoutPolicyAddedEvent) Data() interface{} { return e } +func (e *PasswordLockoutPolicyAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewPasswordLockoutPolicyAddedEvent( base *eventstore.BaseEvent, maxAttempts uint64, @@ -62,6 +66,10 @@ func (e *PasswordLockoutPolicyChangedEvent) Data() interface{} { return e } +func (e *PasswordLockoutPolicyChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewPasswordLockoutPolicyChangedEvent( base *eventstore.BaseEvent, changes []PasswordLockoutPolicyChanges, @@ -113,6 +121,10 @@ func (e *PasswordLockoutPolicyRemovedEvent) Data() interface{} { return nil } +func (e *PasswordLockoutPolicyRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewPasswordLockoutPolicyRemovedEvent(base *eventstore.BaseEvent) *PasswordLockoutPolicyRemovedEvent { return &PasswordLockoutPolicyRemovedEvent{ BaseEvent: *base, diff --git a/internal/v2/repository/project/application.go b/internal/v2/repository/project/application.go index 978cc093c9..553a864003 100644 --- a/internal/v2/repository/project/application.go +++ b/internal/v2/repository/project/application.go @@ -31,6 +31,10 @@ func (e *ApplicationAddedEvent) Data() interface{} { return e } +func (e *ApplicationAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewApplicationAddedEvent(ctx context.Context, appID, name string, appType domain.AppType) *ApplicationAddedEvent { return &ApplicationAddedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/project/oidc_config.go b/internal/v2/repository/project/oidc_config.go index 608e9ee58e..1b76a0bb25 100644 --- a/internal/v2/repository/project/oidc_config.go +++ b/internal/v2/repository/project/oidc_config.go @@ -45,6 +45,10 @@ func (e *OIDCConfigAddedEvent) Data() interface{} { return e } +func (e *OIDCConfigAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewOIDCConfigAddedEvent( ctx context.Context, version domain.OIDCVersion, diff --git a/internal/v2/repository/project/project.go b/internal/v2/repository/project/project.go index 6614c2bfe3..875d5571ae 100644 --- a/internal/v2/repository/project/project.go +++ b/internal/v2/repository/project/project.go @@ -10,6 +10,7 @@ import ( ) const ( + uniqueProjectnameTable = "project_names" projectEventTypePrefix = eventstore.EventType("project.") ProjectAdded = projectEventTypePrefix + "added" ProjectChanged = projectEventTypePrefix + "changed" @@ -18,6 +19,19 @@ const ( ProjectRemoved = projectEventTypePrefix + "removed" ) +func NewAddProjectNameUniqueConstraint(projectName, resourceOwner string) *eventstore.EventUniqueConstraint { + return eventstore.NewAddEventUniqueConstraint( + uniqueProjectnameTable, + projectName+resourceOwner, + "Errors.Project.AlreadyExists") +} + +func NewRemoveProjectNameUniqueConstraint(projectName, resourceOwner string) *eventstore.EventUniqueConstraint { + return eventstore.NewRemoveEventUniqueConstraint( + uniqueProjectnameTable, + projectName+resourceOwner) +} + type ProjectAddedEvent struct { eventstore.BaseEvent `json:"-"` @@ -30,11 +44,16 @@ func (e *ProjectAddedEvent) Data() interface{} { return e } -func NewProjectAddedEvent(ctx context.Context, name string) *ProjectAddedEvent { +func (e *ProjectAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return []*eventstore.EventUniqueConstraint{NewAddProjectNameUniqueConstraint(e.Name, e.ResourceOwner())} +} + +func NewProjectAddedEvent(ctx context.Context, name, resourceOwner string) *ProjectAddedEvent { return &ProjectAddedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: *eventstore.NewBaseEventForPushWithResourceOwner( ctx, ProjectAdded, + resourceOwner, ), Name: name, } diff --git a/internal/v2/repository/user/human.go b/internal/v2/repository/user/human.go index 11414148cd..fd2f56c28b 100644 --- a/internal/v2/repository/user/human.go +++ b/internal/v2/repository/user/human.go @@ -26,7 +26,8 @@ const ( type HumanAddedEvent struct { eventstore.BaseEvent `json:"-"` - UserName string `json:"userName"` + UserName string `json:"userName"` + userLoginMustBeDomain bool FirstName string `json:"firstName,omitempty"` LastName string `json:"lastName,omitempty"` @@ -53,6 +54,10 @@ func (e *HumanAddedEvent) Data() interface{} { return e } +func (e *HumanAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return []*eventstore.EventUniqueConstraint{NewAddUsernameUniqueConstraint(e.UserName, e.ResourceOwner(), e.userLoginMustBeDomain)} +} + func (e *HumanAddedEvent) AddAddressData( country, locality, @@ -83,6 +88,7 @@ func (e *HumanAddedEvent) AddPasswordData( func NewHumanAddedEvent( ctx context.Context, + resourceOwner, userName, firstName, lastName, @@ -91,20 +97,23 @@ func NewHumanAddedEvent( preferredLanguage language.Tag, gender domain.Gender, emailAddress string, + userLoginMustBeDomain bool, ) *HumanAddedEvent { return &HumanAddedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: *eventstore.NewBaseEventForPushWithResourceOwner( ctx, HumanAddedType, + resourceOwner, ), - UserName: userName, - FirstName: firstName, - LastName: lastName, - NickName: nickName, - DisplayName: displayName, - PreferredLanguage: preferredLanguage, - Gender: gender, - EmailAddress: emailAddress, + UserName: userName, + FirstName: firstName, + LastName: lastName, + NickName: nickName, + DisplayName: displayName, + PreferredLanguage: preferredLanguage, + Gender: gender, + EmailAddress: emailAddress, + userLoginMustBeDomain: userLoginMustBeDomain, } } @@ -123,7 +132,8 @@ func HumanAddedEventMapper(event *repository.Event) (eventstore.EventReader, err type HumanRegisteredEvent struct { eventstore.BaseEvent `json:"-"` - UserName string `json:"userName"` + UserName string `json:"userName"` + userLoginMustBeDomain bool FirstName string `json:"firstName,omitempty"` LastName string `json:"lastName,omitempty"` @@ -150,6 +160,10 @@ func (e *HumanRegisteredEvent) Data() interface{} { return e } +func (e *HumanRegisteredEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return []*eventstore.EventUniqueConstraint{NewAddUsernameUniqueConstraint(e.UserName, e.ResourceOwner(), e.userLoginMustBeDomain)} +} + func (e *HumanRegisteredEvent) AddAddressData( country, locality, @@ -180,6 +194,7 @@ func (e *HumanRegisteredEvent) AddPasswordData( func NewHumanRegisteredEvent( ctx context.Context, + resourceOwner, userName, firstName, lastName, @@ -188,20 +203,23 @@ func NewHumanRegisteredEvent( preferredLanguage language.Tag, gender domain.Gender, emailAddress string, + userLoginMustBeDomain bool, ) *HumanRegisteredEvent { return &HumanRegisteredEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: *eventstore.NewBaseEventForPushWithResourceOwner( ctx, HumanRegisteredType, + resourceOwner, ), - UserName: userName, - FirstName: firstName, - LastName: lastName, - NickName: nickName, - DisplayName: displayName, - PreferredLanguage: preferredLanguage, - Gender: gender, - EmailAddress: emailAddress, + UserName: userName, + FirstName: firstName, + LastName: lastName, + NickName: nickName, + DisplayName: displayName, + PreferredLanguage: preferredLanguage, + Gender: gender, + EmailAddress: emailAddress, + userLoginMustBeDomain: userLoginMustBeDomain, } } @@ -227,6 +245,10 @@ func (e *HumanInitialCodeAddedEvent) Data() interface{} { return e } +func (e *HumanInitialCodeAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanInitialCodeAddedEvent( ctx context.Context, code *crypto.CryptoValue, @@ -262,6 +284,10 @@ func (e *HumanInitialCodeSentEvent) Data() interface{} { return nil } +func (e *HumanInitialCodeSentEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanInitialCodeSentEvent(ctx context.Context) *HumanInitialCodeSentEvent { return &HumanInitialCodeSentEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -285,6 +311,10 @@ func (e *HumanInitializedCheckSucceededEvent) Data() interface{} { return nil } +func (e *HumanInitializedCheckSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanInitializedCheckSucceededEvent(ctx context.Context) *HumanInitializedCheckSucceededEvent { return &HumanInitializedCheckSucceededEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -308,6 +338,10 @@ func (e *HumanInitializedCheckFailedEvent) Data() interface{} { return nil } +func (e *HumanInitializedCheckFailedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanInitializedCheckFailedEvent(ctx context.Context) *HumanInitializedCheckFailedEvent { return &HumanInitializedCheckFailedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -331,6 +365,10 @@ func (e *HumanSignedOutEvent) Data() interface{} { return nil } +func (e *HumanSignedOutEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanSignedOutEvent(ctx context.Context) *HumanSignedOutEvent { return &HumanSignedOutEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/user/human_address.go b/internal/v2/repository/user/human_address.go index a0d6bfad2e..77d9311381 100644 --- a/internal/v2/repository/user/human_address.go +++ b/internal/v2/repository/user/human_address.go @@ -27,6 +27,10 @@ func (e *HumanAddressChangedEvent) Data() interface{} { return e } +func (e *HumanAddressChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanAddressChangedEvent(ctx context.Context) *HumanAddressChangedEvent { return &HumanAddressChangedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/user/human_email.go b/internal/v2/repository/user/human_email.go index 302259efee..e777960520 100644 --- a/internal/v2/repository/user/human_email.go +++ b/internal/v2/repository/user/human_email.go @@ -29,6 +29,10 @@ func (e *HumanEmailChangedEvent) Data() interface{} { return e } +func (e *HumanEmailChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanEmailChangedEvent(ctx context.Context) *HumanEmailChangedEvent { return &HumanEmailChangedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -60,6 +64,10 @@ func (e *HumanEmailVerifiedEvent) Data() interface{} { return nil } +func (e *HumanEmailVerifiedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanEmailVerifiedEvent(ctx context.Context) *HumanEmailVerifiedEvent { return &HumanEmailVerifiedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -85,6 +93,10 @@ func (e *HumanEmailVerificationFailedEvent) Data() interface{} { return nil } +func (e *HumanEmailVerificationFailedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanEmailVerificationFailedEvent(ctx context.Context) *HumanEmailVerificationFailedEvent { return &HumanEmailVerificationFailedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -111,6 +123,10 @@ func (e *HumanEmailCodeAddedEvent) Data() interface{} { return e } +func (e *HumanEmailCodeAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanEmailCodeAddedEvent( ctx context.Context, code *crypto.CryptoValue, @@ -145,6 +161,10 @@ func (e *HumanEmailCodeSentEvent) Data() interface{} { return nil } +func (e *HumanEmailCodeSentEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanEmailCodeSentEvent(ctx context.Context) *HumanEmailCodeSentEvent { return &HumanEmailCodeSentEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/user/human_external_idp.go b/internal/v2/repository/user/human_external_idp.go index 73fc14d178..e7bba60c80 100644 --- a/internal/v2/repository/user/human_external_idp.go +++ b/internal/v2/repository/user/human_external_idp.go @@ -9,6 +9,7 @@ import ( ) const ( + uniqueExternalIDPType = "external_idps" externalIDPEventPrefix = humanEventPrefix + "externalidp." externalLoginEventPrefix = humanEventPrefix + "externallogin." @@ -23,38 +24,17 @@ const ( HumanExternalLoginCheckSucceededType = externalLoginEventPrefix + "check.succeeded" ) -type HumanExternalIDPReservedEvent struct { - eventstore.BaseEvent `json:"-"` +func NewAddExternalIDPUniqueConstraint(idpConfigID, externalUserID string) *eventstore.EventUniqueConstraint { + return eventstore.NewAddEventUniqueConstraint( + uniqueExternalIDPType, + idpConfigID+externalUserID, + "Errors.User.ExternalIDP.AlreadyExists") } -func (e *HumanExternalIDPReservedEvent) Data() interface{} { - return nil -} - -func NewHumanExternalIDPReservedEvent(ctx context.Context) *HumanExternalIDPReservedEvent { - return &HumanExternalIDPReservedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanExternalIDPReservedType, - ), - } -} - -type HumanExternalIDPReleasedEvent struct { - eventstore.BaseEvent `json:"-"` -} - -func (e *HumanExternalIDPReleasedEvent) Data() interface{} { - return nil -} - -func NewHumanExternalIDPReleasedEvent(ctx context.Context) *HumanExternalIDPReleasedEvent { - return &HumanExternalIDPReleasedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( - ctx, - HumanExternalIDPReleasedType, - ), - } +func NewRemoveExternalIDPUniqueConstraint(idpConfigID, externalUserID string) *eventstore.EventUniqueConstraint { + return eventstore.NewRemoveEventUniqueConstraint( + uniqueExternalIDPType, + idpConfigID+externalUserID) } type HumanExternalIDPAddedEvent struct { @@ -69,6 +49,10 @@ func (e *HumanExternalIDPAddedEvent) Data() interface{} { return e } +func (e *HumanExternalIDPAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return []*eventstore.EventUniqueConstraint{NewAddExternalIDPUniqueConstraint(e.IDPConfigID, e.UserID)} +} + func NewHumanExternalIDPAddedEvent(ctx context.Context, idpConfigID, displayName string) *HumanExternalIDPAddedEvent { return &HumanExternalIDPAddedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -104,6 +88,10 @@ func (e *HumanExternalIDPRemovedEvent) Data() interface{} { return e } +func (e *HumanExternalIDPRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return []*eventstore.EventUniqueConstraint{NewRemoveExternalIDPUniqueConstraint(e.IDPConfigID, e.UserID)} +} + func NewHumanExternalIDPRemovedEvent(ctx context.Context, idpConfigID, externalUserID string) *HumanExternalIDPRemovedEvent { return &HumanExternalIDPRemovedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -139,6 +127,10 @@ func (e *HumanExternalIDPCascadeRemovedEvent) Data() interface{} { return e } +func (e *HumanExternalIDPCascadeRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return []*eventstore.EventUniqueConstraint{NewRemoveExternalIDPUniqueConstraint(e.IDPConfigID, e.UserID)} +} + func NewHumanExternalIDPCascadeRemovedEvent(ctx context.Context, idpConfigID, externalUserID string) *HumanExternalIDPCascadeRemovedEvent { return &HumanExternalIDPCascadeRemovedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -171,6 +163,10 @@ func (e *HumanExternalIDPCheckSucceededEvent) Data() interface{} { return nil } +func (e *HumanExternalIDPCheckSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanExternalIDPCheckSucceededEvent(ctx context.Context) *HumanExternalIDPCheckSucceededEvent { return &HumanExternalIDPCheckSucceededEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/user/human_mfa_events.go b/internal/v2/repository/user/human_mfa_events.go index eed43b5cdd..60ba93ec17 100644 --- a/internal/v2/repository/user/human_mfa_events.go +++ b/internal/v2/repository/user/human_mfa_events.go @@ -19,6 +19,10 @@ func (e *HumanMFAInitSkippedEvent) Data() interface{} { return e } +func (e *HumanMFAInitSkippedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanMFAInitSkippedEvent(ctx context.Context) *HumanMFAInitSkippedEvent { return &HumanMFAInitSkippedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/user/human_mfa_otp.go b/internal/v2/repository/user/human_mfa_otp.go index 347a2057f6..c05accf71f 100644 --- a/internal/v2/repository/user/human_mfa_otp.go +++ b/internal/v2/repository/user/human_mfa_otp.go @@ -28,6 +28,10 @@ func (e *HumanOTPAddedEvent) Data() interface{} { return e } +func (e *HumanOTPAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanOTPAddedEvent(ctx context.Context, secret *crypto.CryptoValue) *HumanOTPAddedEvent { return &HumanOTPAddedEvent{ @@ -59,6 +63,10 @@ func (e *HumanOTPVerifiedEvent) Data() interface{} { return nil } +func (e *HumanOTPVerifiedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanOTPVerifiedEvent(ctx context.Context, userAgentID string) *HumanOTPVerifiedEvent { return &HumanOTPVerifiedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -83,6 +91,10 @@ func (e *HumanOTPRemovedEvent) Data() interface{} { return nil } +func (e *HumanOTPRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanOTPRemovedEvent(ctx context.Context) *HumanOTPRemovedEvent { return &HumanOTPRemovedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -106,6 +118,10 @@ func (e *HumanOTPCheckSucceededEvent) Data() interface{} { return nil } +func (e *HumanOTPCheckSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanOTPCheckSucceededEvent(ctx context.Context) *HumanOTPCheckSucceededEvent { return &HumanOTPCheckSucceededEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -129,6 +145,10 @@ func (e *HumanOTPCheckFailedEvent) Data() interface{} { return nil } +func (e *HumanOTPCheckFailedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanOTPCheckFailedEvent(ctx context.Context) *HumanOTPCheckFailedEvent { return &HumanOTPCheckFailedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/user/human_mfa_web_auth_n.go b/internal/v2/repository/user/human_mfa_web_auth_n.go index 2a1a401acf..8f4914d9db 100644 --- a/internal/v2/repository/user/human_mfa_web_auth_n.go +++ b/internal/v2/repository/user/human_mfa_web_auth_n.go @@ -40,6 +40,10 @@ func (e *HumanWebAuthNAddedEvent) Data() interface{} { return e } +func (e *HumanWebAuthNAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanU2FAddedEvent( ctx context.Context, webAuthNTokenID, @@ -97,6 +101,10 @@ func (e *HumanWebAuthNVerifiedEvent) Data() interface{} { return e } +func (e *HumanWebAuthNVerifiedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanU2FVerifiedEvent( ctx context.Context, webAuthNTokenID, @@ -169,6 +177,10 @@ func (e *HumanWebAuthNSignCountChangedEvent) Data() interface{} { return e } +func (e *HumanWebAuthNSignCountChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanU2FSignCountChangedEvent( ctx context.Context, webAuthNTokenID string, @@ -221,6 +233,10 @@ func (e *HumanWebAuthNRemovedEvent) Data() interface{} { return e } +func (e *HumanWebAuthNRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanU2FRemovedEvent( ctx context.Context, webAuthNTokenID string, @@ -271,6 +287,10 @@ func (e *HumanWebAuthNBeginLoginEvent) Data() interface{} { return e } +func (e *HumanWebAuthNBeginLoginEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanU2FBeginLoginEvent( ctx context.Context, webAuthNTokenID, @@ -323,6 +343,10 @@ func (e *HumanWebAuthNCheckSucceededEvent) Data() interface{} { return e } +func (e *HumanWebAuthNCheckSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanU2FCheckSucceededEvent(ctx context.Context) *HumanWebAuthNCheckSucceededEvent { return &HumanWebAuthNCheckSucceededEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -363,6 +387,10 @@ func (e *HumanWebAuthNCheckFailedEvent) Data() interface{} { return e } +func (e *HumanWebAuthNCheckFailedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanU2FCheckFailedEvent(ctx context.Context) *HumanWebAuthNCheckFailedEvent { return &HumanWebAuthNCheckFailedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/user/human_password.go b/internal/v2/repository/user/human_password.go index 1ed703aa48..f281e72215 100644 --- a/internal/v2/repository/user/human_password.go +++ b/internal/v2/repository/user/human_password.go @@ -32,6 +32,10 @@ func (e *HumanPasswordChangedEvent) Data() interface{} { return e } +func (e *HumanPasswordChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanPasswordChangedEvent( ctx context.Context, secret *crypto.CryptoValue, @@ -73,6 +77,10 @@ func (e *HumanPasswordCodeAddedEvent) Data() interface{} { return e } +func (e *HumanPasswordCodeAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanPasswordCodeAddedEvent( ctx context.Context, code *crypto.CryptoValue, @@ -110,6 +118,10 @@ func (e *HumanPasswordCodeSentEvent) Data() interface{} { return nil } +func (e *HumanPasswordCodeSentEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanPasswordCodeSentEvent(ctx context.Context) *HumanPasswordCodeSentEvent { return &HumanPasswordCodeSentEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -133,6 +145,10 @@ func (e *HumanPasswordCheckSucceededEvent) Data() interface{} { return nil } +func (e *HumanPasswordCheckSucceededEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanPasswordCheckSucceededEvent(ctx context.Context) *HumanPasswordCheckSucceededEvent { return &HumanPasswordCheckSucceededEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -156,6 +172,10 @@ func (e *HumanPasswordCheckFailedEvent) Data() interface{} { return nil } +func (e *HumanPasswordCheckFailedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanPasswordCheckFailedEvent(ctx context.Context) *HumanPasswordCheckFailedEvent { return &HumanPasswordCheckFailedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/user/human_phone.go b/internal/v2/repository/user/human_phone.go index 580fa73c6f..47fac6329f 100644 --- a/internal/v2/repository/user/human_phone.go +++ b/internal/v2/repository/user/human_phone.go @@ -30,6 +30,10 @@ func (e *HumanPhoneChangedEvent) Data() interface{} { return e } +func (e *HumanPhoneChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanPhoneChangedEvent(ctx context.Context) *HumanPhoneChangedEvent { return &HumanPhoneChangedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -59,6 +63,10 @@ func (e *HumanPhoneRemovedEvent) Data() interface{} { return nil } +func (e *HumanPhoneRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanPhoneRemovedEvent(ctx context.Context) *HumanPhoneRemovedEvent { return &HumanPhoneRemovedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -84,6 +92,10 @@ func (e *HumanPhoneVerifiedEvent) Data() interface{} { return nil } +func (e *HumanPhoneVerifiedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanPhoneVerifiedEvent(ctx context.Context) *HumanPhoneVerifiedEvent { return &HumanPhoneVerifiedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -108,6 +120,10 @@ func (e *HumanPhoneVerificationFailedEvent) Data() interface{} { return nil } +func (e *HumanPhoneVerificationFailedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanPhoneVerificationFailedEvent(ctx context.Context) *HumanPhoneVerificationFailedEvent { return &HumanPhoneVerificationFailedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -134,6 +150,10 @@ func (e *HumanPhoneCodeAddedEvent) Data() interface{} { return e } +func (e *HumanPhoneCodeAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanPhoneCodeAddedEvent( ctx context.Context, code *crypto.CryptoValue, @@ -169,6 +189,10 @@ func (e *HumanPhoneCodeSentEvent) Data() interface{} { return e } +func (e *HumanPhoneCodeSentEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanPhoneCodeSentEvent(ctx context.Context) *HumanPhoneCodeSentEvent { return &HumanPhoneCodeSentEvent{ BaseEvent: *eventstore.NewBaseEventForPush( diff --git a/internal/v2/repository/user/human_profile.go b/internal/v2/repository/user/human_profile.go index 4944b10855..4b3e8154f6 100644 --- a/internal/v2/repository/user/human_profile.go +++ b/internal/v2/repository/user/human_profile.go @@ -30,6 +30,10 @@ func (e *HumanProfileChangedEvent) Data() interface{} { return e } +func (e *HumanProfileChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewHumanProfileChangedEvent( ctx context.Context) *HumanProfileChangedEvent { return &HumanProfileChangedEvent{ diff --git a/internal/v2/repository/user/machine.go b/internal/v2/repository/user/machine.go index 01a9382803..ddc408be69 100644 --- a/internal/v2/repository/user/machine.go +++ b/internal/v2/repository/user/machine.go @@ -17,7 +17,8 @@ const ( type MachineAddedEvent struct { eventstore.BaseEvent `json:"-"` - UserName string `json:"userName"` + UserName string `json:"userName"` + UserLoginMustBeDomain bool Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` @@ -27,20 +28,26 @@ func (e *MachineAddedEvent) Data() interface{} { return e } +func (e *MachineAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return []*eventstore.EventUniqueConstraint{NewAddUsernameUniqueConstraint(e.UserName, e.ResourceOwner(), e.UserLoginMustBeDomain)} +} + func NewMachineAddedEvent( ctx context.Context, userName, name, description string, + userLoginMustBeDomain bool, ) *MachineAddedEvent { return &MachineAddedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, MachineAddedEventType, ), - UserName: userName, - Name: name, - Description: description, + UserName: userName, + Name: name, + Description: description, + UserLoginMustBeDomain: userLoginMustBeDomain, } } @@ -69,6 +76,10 @@ func (e *MachineChangedEvent) Data() interface{} { return e } +func (e *MachineChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewMachineChangedEvent( ctx context.Context, ) *MachineChangedEvent { diff --git a/internal/v2/repository/user/machine_key.go b/internal/v2/repository/user/machine_key.go index 8730e1e306..43d70b0ccb 100644 --- a/internal/v2/repository/user/machine_key.go +++ b/internal/v2/repository/user/machine_key.go @@ -29,6 +29,10 @@ func (e *MachineKeyAddedEvent) Data() interface{} { return e } +func (e *MachineKeyAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewMachineKeyAddedEvent( ctx context.Context, keyID string, @@ -70,6 +74,10 @@ func (e *MachineKeyRemovedEvent) Data() interface{} { return e } +func (e *MachineKeyRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewMachineKeyRemovedEvent( ctx context.Context, keyID string, diff --git a/internal/v2/repository/user/user.go b/internal/v2/repository/user/user.go index 01ecf03cfe..b02dad50ad 100644 --- a/internal/v2/repository/user/user.go +++ b/internal/v2/repository/user/user.go @@ -10,6 +10,7 @@ import ( ) const ( + uniqueUsername = "usernames" userEventTypePrefix = eventstore.EventType("user.") UserLockedType = userEventTypePrefix + "locked" UserUnlockedType = userEventTypePrefix + "unlocked" @@ -22,6 +23,27 @@ const ( UserUserNameChangedType = userEventTypePrefix + "username.changed" ) +func NewAddUsernameUniqueConstraint(userName, resourceOwner string, userLoginMustBeDomain bool) *eventstore.EventUniqueConstraint { + uniqueUserName := userName + if userLoginMustBeDomain { + uniqueUserName = userName + resourceOwner + } + return eventstore.NewAddEventUniqueConstraint( + uniqueUsername, + uniqueUserName, + "Errors.User.AlreadyExists") +} + +func NewRemoveUsernameUniqueConstraint(userName, resourceOwner string, userLoginMustBeDomain bool) *eventstore.EventUniqueConstraint { + uniqueUserName := userName + if userLoginMustBeDomain { + uniqueUserName = userName + resourceOwner + } + return eventstore.NewRemoveEventUniqueConstraint( + uniqueUsername, + uniqueUserName) +} + type UserLockedEvent struct { eventstore.BaseEvent `json:"-"` } @@ -30,6 +52,10 @@ func (e *UserLockedEvent) Data() interface{} { return nil } +func (e *UserLockedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewUserLockedEvent(ctx context.Context) *UserLockedEvent { return &UserLockedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -53,6 +79,10 @@ func (e *UserUnlockedEvent) Data() interface{} { return nil } +func (e *UserUnlockedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewUserUnlockedEvent(ctx context.Context) *UserUnlockedEvent { return &UserUnlockedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -76,6 +106,10 @@ func (e *UserDeactivatedEvent) Data() interface{} { return nil } +func (e *UserDeactivatedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewUserDeactivatedEvent(ctx context.Context) *UserDeactivatedEvent { return &UserDeactivatedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -99,6 +133,10 @@ func (e *UserReactivatedEvent) Data() interface{} { return nil } +func (e *UserReactivatedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewUserReactivatedEvent(ctx context.Context) *UserReactivatedEvent { return &UserReactivatedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( @@ -116,18 +154,28 @@ func UserReactivatedEventMapper(event *repository.Event) (eventstore.EventReader type UserRemovedEvent struct { eventstore.BaseEvent `json:"-"` + + UserName string + UserLoginMustBeDomain bool } func (e *UserRemovedEvent) Data() interface{} { return nil } -func NewUserRemovedEvent(ctx context.Context) *UserRemovedEvent { +func (e *UserRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return []*eventstore.EventUniqueConstraint{NewRemoveUsernameUniqueConstraint(e.UserName, e.ResourceOwner(), e.UserLoginMustBeDomain)} +} + +func NewUserRemovedEvent(ctx context.Context, resourceOwner, userName string, userLoginMustBeDomain bool) *UserRemovedEvent { return &UserRemovedEvent{ - BaseEvent: *eventstore.NewBaseEventForPush( + BaseEvent: *eventstore.NewBaseEventForPushWithResourceOwner( ctx, UserRemovedType, + resourceOwner, ), + UserName: userName, + UserLoginMustBeDomain: userLoginMustBeDomain, } } @@ -153,6 +201,10 @@ func (e *UserTokenAddedEvent) Data() interface{} { return e } +func (e *UserTokenAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewUserTokenAddedEvent( ctx context.Context, tokenID, @@ -168,12 +220,13 @@ func NewUserTokenAddedEvent( ctx, UserTokenAddedType, ), - TokenID: tokenID, - ApplicationID: applicationID, - UserAgentID: userAgentID, - Audience: audience, - Scopes: scopes, - Expiration: expiration, + TokenID: tokenID, + ApplicationID: applicationID, + UserAgentID: userAgentID, + Audience: audience, + Scopes: scopes, + Expiration: expiration, + PreferredLanguage: preferredLanguage, } } @@ -199,6 +252,10 @@ func (e *DomainClaimedEvent) Data() interface{} { return e } +func (e *DomainClaimedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewDomainClaimedEvent( ctx context.Context, userName string, @@ -232,6 +289,10 @@ func (e *DomainClaimedSentEvent) Data() interface{} { return nil } +func (e *DomainClaimedSentEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + func NewDomainClaimedSentEvent( ctx context.Context, ) *DomainClaimedSentEvent { @@ -252,23 +313,36 @@ func DomainClaimedSentEventMapper(event *repository.Event) (eventstore.EventRead type UsernameChangedEvent struct { eventstore.BaseEvent `json:"-"` - UserName string `json:"userName"` + UserName string `json:"userName"` + OldUserName string + UserLoginMustBeDomain bool } func (e *UsernameChangedEvent) Data() interface{} { return e } +func (e *UsernameChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return []*eventstore.EventUniqueConstraint{ + NewRemoveUsernameUniqueConstraint(e.OldUserName, e.ResourceOwner(), e.UserLoginMustBeDomain), + NewAddUsernameUniqueConstraint(e.UserName, e.ResourceOwner(), e.UserLoginMustBeDomain), + } +} + func NewUsernameChangedEvent( ctx context.Context, - userName string, + oldUserName, + newUserName string, + userLoginMustBeDomain bool, ) *UsernameChangedEvent { return &UsernameChangedEvent{ BaseEvent: *eventstore.NewBaseEventForPush( ctx, UserUserNameChangedType, ), - UserName: userName, + UserName: newUserName, + OldUserName: oldUserName, + UserLoginMustBeDomain: userLoginMustBeDomain, } } diff --git a/internal/v2/view/iam.go b/internal/v2/view/iam.go index 5291166536..8232e50355 100644 --- a/internal/v2/view/iam.go +++ b/internal/v2/view/iam.go @@ -2,14 +2,15 @@ package view import ( "github.com/caos/zitadel/internal/eventstore/v2" + "github.com/caos/zitadel/internal/v2/domain" "github.com/caos/zitadel/internal/v2/repository/iam" ) type IAM struct { eventstore.ReadModel - SetUpStarted iam.Step - SetUpDone iam.Step + SetUpStarted domain.Step + SetUpDone domain.Step GlobalOrgID string ProjectID string diff --git a/migrations/cockroach/V1.27__unique_tables.sql b/migrations/cockroach/V1.27__unique_tables.sql new file mode 100644 index 0000000000..45a1b9d7f6 --- /dev/null +++ b/migrations/cockroach/V1.27__unique_tables.sql @@ -0,0 +1,5 @@ +CREATE TABLE eventstore.unique_constraints ( + unique_type TEXT, + unique_field TEXT, + PRIMARY KEY (unique_type, unique_field) +);