diff --git a/build/zitadel/generate-grpc.sh b/build/zitadel/generate-grpc.sh index 8b6e2d99e3..771d12f261 100755 --- a/build/zitadel/generate-grpc.sh +++ b/build/zitadel/generate-grpc.sh @@ -152,6 +152,10 @@ protoc \ -I=/proto/include \ --doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,user.md \ ${PROTO_PATH}/user.proto +protoc \ + -I=/proto/include \ + --doc_out=${DOCS_PATH} --doc_opt=${PROTO_PATH}/docs/zitadel-md.tmpl,settings.md \ + ${PROTO_PATH}/settings.proto echo "done generating grpc" \ No newline at end of file diff --git a/cmd/admin/start/start.go b/cmd/admin/start/start.go index 72737e6080..35bf2db1ca 100644 --- a/cmd/admin/start/start.go +++ b/cmd/admin/start/start.go @@ -171,7 +171,7 @@ func startZitadel(config *startConfig) error { return fmt.Errorf("cannot start commands: %w", err) } - notification.Start(config.Notification, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix, smtpPasswordCrypto) + notification.Start(config.Notification, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix, smtpPasswordCrypto, smsCrypto) router := mux.NewRouter() err = startAPIs(ctx, router, commands, queries, eventstoreClient, dbClient, keyChan, config, storage, authZRepo) diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md index 1864bebc60..748c211d51 100644 --- a/docs/docs/apis/proto/admin.md +++ b/docs/docs/apis/proto/admin.md @@ -188,6 +188,30 @@ Update twilio sms provider token PUT: /sms/twilio/{id}/token +### GetFileSystemNotificationProvider + +> **rpc** GetFileSystemNotificationProvider([GetFileSystemNotificationProviderRequest](#getfilesystemnotificationproviderrequest)) +[GetFileSystemNotificationProviderResponse](#getfilesystemnotificationproviderresponse) + +Get file system notification provider + + + + GET: /notification/provider/file + + +### GetLogNotificationProvider + +> **rpc** GetLogNotificationProvider([GetLogNotificationProviderRequest](#getlognotificationproviderrequest)) +[GetLogNotificationProviderResponse](#getlognotificationproviderresponse) + +Get log notification provider + + + + GET: /notification/provider/log + + ### GetOIDCSettings > **rpc** GetOIDCSettings([GetOIDCSettingsRequest](#getoidcsettingsrequest)) @@ -1997,6 +2021,23 @@ This is an empty request +### GetFileSystemNotificationProviderRequest +This is an empty request + + + + +### GetFileSystemNotificationProviderResponse + + + +| Field | Type | Description | Validation | +| ----- | ---- | ----------- | ----------- | +| provider | zitadel.settings.v1.DebugNotificationProvider | - | | + + + + ### GetIDPByIDRequest @@ -2053,6 +2094,23 @@ This is an empty request +### GetLogNotificationProviderRequest +This is an empty request + + + + +### GetLogNotificationProviderResponse + + + +| Field | Type | Description | Validation | +| ----- | ---- | ----------- | ----------- | +| provider | zitadel.settings.v1.DebugNotificationProvider | - | | + + + + ### GetLoginPolicyRequest This is an empty request diff --git a/internal/api/grpc/admin/notification_provider.go b/internal/api/grpc/admin/notification_provider.go new file mode 100644 index 0000000000..b14405e18a --- /dev/null +++ b/internal/api/grpc/admin/notification_provider.go @@ -0,0 +1,31 @@ +package admin + +import ( + "context" + + "github.com/caos/zitadel/internal/api/grpc/settings" + "github.com/caos/zitadel/internal/domain" + admin_pb "github.com/caos/zitadel/pkg/grpc/admin" +) + +func (s *Server) GetFileSystemNotificationProvider(ctx context.Context, req *admin_pb.GetFileSystemNotificationProviderRequest) (*admin_pb.GetFileSystemNotificationProviderResponse, error) { + result, err := s.query.NotificationProviderByIDAndType(ctx, domain.IAMID, domain.NotificationProviderTypeFile) + if err != nil { + return nil, err + + } + return &admin_pb.GetFileSystemNotificationProviderResponse{ + Provider: settings.NotificationProviderToPb(result), + }, nil +} + +func (s *Server) GetLogNotificationProvider(ctx context.Context, req *admin_pb.GetLogNotificationProviderRequest) (*admin_pb.GetLogNotificationProviderResponse, error) { + result, err := s.query.NotificationProviderByIDAndType(ctx, domain.IAMID, domain.NotificationProviderTypeLog) + if err != nil { + return nil, err + + } + return &admin_pb.GetLogNotificationProviderResponse{ + Provider: settings.NotificationProviderToPb(result), + }, nil +} diff --git a/internal/api/grpc/settings/converter.go b/internal/api/grpc/settings/converter.go new file mode 100644 index 0000000000..660cfd69fb --- /dev/null +++ b/internal/api/grpc/settings/converter.go @@ -0,0 +1,15 @@ +package settings + +import ( + obj_pb "github.com/caos/zitadel/internal/api/grpc/object" + "github.com/caos/zitadel/internal/query" + settings_pb "github.com/caos/zitadel/pkg/grpc/settings" +) + +func NotificationProviderToPb(provider *query.DebugNotificationProvider) *settings_pb.DebugNotificationProvider { + mapped := &settings_pb.DebugNotificationProvider{ + Compact: provider.Compact, + Details: obj_pb.ToViewDetailsPb(provider.Sequence, provider.CreationDate, provider.ChangeDate, provider.AggregateID), + } + return mapped +} diff --git a/internal/command/debug_notification_model.go b/internal/command/debug_notification_model.go new file mode 100644 index 0000000000..0f6746855b --- /dev/null +++ b/internal/command/debug_notification_model.go @@ -0,0 +1,31 @@ +package command + +import ( + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/settings" +) + +type DebugNotificationWriteModel struct { + eventstore.WriteModel + + Compact bool + State domain.NotificationProviderState +} + +func (wm *DebugNotificationWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *settings.DebugNotificationProviderAddedEvent: + wm.Compact = e.Compact + wm.State = domain.NotificationProviderStateActive + case *settings.DebugNotificationProviderChangedEvent: + if e.Compact != nil { + wm.Compact = *e.Compact + } + case *settings.DebugNotificationProviderRemovedEvent: + wm.State = domain.NotificationProviderStateRemoved + } + } + return wm.WriteModel.Reduce() +} diff --git a/internal/command/iam_debug_notification_file.go b/internal/command/iam_debug_notification_file.go new file mode 100644 index 0000000000..2b231cca56 --- /dev/null +++ b/internal/command/iam_debug_notification_file.go @@ -0,0 +1,120 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/domain" + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/notification/channels/fs" + iam_repo "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/telemetry/tracing" +) + +func (c *Commands) AddDebugNotificationProviderFile(ctx context.Context, fileSystemProvider *fs.FSConfig) (*domain.ObjectDetails, error) { + writeModel := NewIAMDebugNotificationFileWriteModel() + iamAgg := IAMAggregateFromWriteModel(&writeModel.WriteModel) + events, err := c.addDefaultDebugNotificationFile(ctx, iamAgg, writeModel, fileSystemProvider) + if err != nil { + return nil, err + } + pushedEvents, err := c.eventstore.Push(ctx, events...) + if err != nil { + return nil, err + } + err = AppendAndReduce(writeModel, pushedEvents...) + if err != nil { + return nil, err + } + return writeModelToObjectDetails(&writeModel.DebugNotificationWriteModel.WriteModel), nil +} + +func (c *Commands) addDefaultDebugNotificationFile(ctx context.Context, iamAgg *eventstore.Aggregate, addedWriteModel *IAMDebugNotificationFileWriteModel, fileSystemProvider *fs.FSConfig) ([]eventstore.Command, error) { + err := c.eventstore.FilterToQueryReducer(ctx, addedWriteModel) + if err != nil { + return nil, err + } + if addedWriteModel.State.Exists() { + return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-d93nfs", "Errors.IAM.DebugNotificationProvider.AlreadyExists") + } + + events := []eventstore.Command{ + iam_repo.NewDebugNotificationProviderFileAddedEvent(ctx, + iamAgg, + fileSystemProvider.Compact), + } + return events, nil +} + +func (c *Commands) ChangeDefaultNotificationFile(ctx context.Context, fileSystemProvider *fs.FSConfig) (*domain.ObjectDetails, error) { + writeModel := NewIAMDebugNotificationFileWriteModel() + iamAgg := IAMAggregateFromWriteModel(&writeModel.WriteModel) + events, err := c.changeDefaultDebugNotificationProviderFile(ctx, iamAgg, writeModel, fileSystemProvider) + if err != nil { + return nil, err + } + pushedEvents, err := c.eventstore.Push(ctx, events...) + if err != nil { + return nil, err + } + err = AppendAndReduce(writeModel, pushedEvents...) + if err != nil { + return nil, err + } + return writeModelToObjectDetails(&writeModel.DebugNotificationWriteModel.WriteModel), nil +} + +func (c *Commands) changeDefaultDebugNotificationProviderFile(ctx context.Context, iamAgg *eventstore.Aggregate, existingProvider *IAMDebugNotificationFileWriteModel, fileSystemProvider *fs.FSConfig) ([]eventstore.Command, error) { + err := c.defaultDebugNotificationProviderFileWriteModelByID(ctx, existingProvider) + if err != nil { + return nil, err + } + if !existingProvider.State.Exists() { + return nil, caos_errs.ThrowNotFound(nil, "IAM-fm9wl", "Errors.IAM.DebugNotificationProvider.NotFound") + } + events := make([]eventstore.Command, 0) + changedEvent, hasChanged := existingProvider.NewChangedEvent(ctx, + iamAgg, + fileSystemProvider.Compact) + if hasChanged { + events = append(events, changedEvent) + } + if len(events) == 0 { + return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-5M9vdd", "Errors.IAM.LoginPolicy.NotChanged") + + } + return events, nil +} + +func (c *Commands) RemoveDefaultNotificationFile(ctx context.Context) (*domain.ObjectDetails, error) { + existingProvider := NewIAMDebugNotificationFileWriteModel() + iamAgg := IAMAggregateFromWriteModel(&existingProvider.WriteModel) + err := c.defaultDebugNotificationProviderFileWriteModelByID(ctx, existingProvider) + if err != nil { + return nil, err + } + if !existingProvider.State.Exists() { + return nil, caos_errs.ThrowNotFound(nil, "IAM-dj9ew", "Errors.IAM.DebugNotificationProvider.NotFound") + } + + events, err := c.eventstore.Push(ctx, iam_repo.NewDebugNotificationProviderFileRemovedEvent(ctx, iamAgg)) + if err != nil { + return nil, err + } + err = AppendAndReduce(existingProvider, events...) + if err != nil { + return nil, err + } + return writeModelToObjectDetails(&existingProvider.DebugNotificationWriteModel.WriteModel), nil +} + +func (c *Commands) defaultDebugNotificationProviderFileWriteModelByID(ctx context.Context, writeModel *IAMDebugNotificationFileWriteModel) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + err = c.eventstore.FilterToQueryReducer(ctx, writeModel) + if err != nil { + return err + } + return nil +} diff --git a/internal/command/iam_debug_notification_file_model.go b/internal/command/iam_debug_notification_file_model.go new file mode 100644 index 0000000000..f431539377 --- /dev/null +++ b/internal/command/iam_debug_notification_file_model.go @@ -0,0 +1,79 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/settings" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/repository/iam" +) + +type IAMDebugNotificationFileWriteModel struct { + DebugNotificationWriteModel +} + +func NewIAMDebugNotificationFileWriteModel() *IAMDebugNotificationFileWriteModel { + return &IAMDebugNotificationFileWriteModel{ + DebugNotificationWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: domain.IAMID, + ResourceOwner: domain.IAMID, + }, + }, + } +} + +func (wm *IAMDebugNotificationFileWriteModel) AppendEvents(events ...eventstore.Event) { + for _, event := range events { + switch e := event.(type) { + case *iam.DebugNotificationProviderFileAddedEvent: + wm.DebugNotificationWriteModel.AppendEvents(&e.DebugNotificationProviderAddedEvent) + case *iam.DebugNotificationProviderFileChangedEvent: + wm.DebugNotificationWriteModel.AppendEvents(&e.DebugNotificationProviderChangedEvent) + case *iam.DebugNotificationProviderFileRemovedEvent: + wm.DebugNotificationWriteModel.AppendEvents(&e.DebugNotificationProviderRemovedEvent) + } + } +} + +func (wm *IAMDebugNotificationFileWriteModel) IsValid() bool { + return wm.AggregateID != "" +} + +func (wm *IAMDebugNotificationFileWriteModel) Reduce() error { + return wm.DebugNotificationWriteModel.Reduce() +} + +func (wm *IAMDebugNotificationFileWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + ResourceOwner(wm.ResourceOwner). + AddQuery(). + AggregateTypes(iam.AggregateType). + AggregateIDs(wm.DebugNotificationWriteModel.AggregateID). + EventTypes( + iam.DebugNotificationProviderFileAddedEventType, + iam.DebugNotificationProviderFileChangedEventType, + iam.DebugNotificationProviderFileRemovedEventType). + Builder() +} + +func (wm *IAMDebugNotificationFileWriteModel) NewChangedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + compact bool) (*iam.DebugNotificationProviderFileChangedEvent, bool) { + + changes := make([]settings.DebugNotificationProviderChanges, 0) + if wm.Compact != compact { + changes = append(changes, settings.ChangeCompact(compact)) + } + if len(changes) == 0 { + return nil, false + } + changedEvent, err := iam.NewDebugNotificationProviderFileChangedEvent(ctx, aggregate, changes) + if err != nil { + return nil, false + } + return changedEvent, true +} diff --git a/internal/command/iam_debug_notification_file_test.go b/internal/command/iam_debug_notification_file_test.go new file mode 100644 index 0000000000..e86649f02a --- /dev/null +++ b/internal/command/iam_debug_notification_file_test.go @@ -0,0 +1,348 @@ +package command + +import ( + "context" + "testing" + + "github.com/caos/zitadel/internal/domain" + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/notification/channels/fs" + "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/settings" + + "github.com/stretchr/testify/assert" +) + +func TestCommandSide_AddDefaultDebugNotificationProviderFile(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + } + type args struct { + ctx context.Context + provider *fs.FSConfig + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "provider already existing, already exists error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + iam.NewDebugNotificationProviderFileAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + true, + ), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + provider: &fs.FSConfig{ + Compact: true, + Enabled: true, + }, + }, + res: res{ + err: caos_errs.IsErrorAlreadyExists, + }, + }, + { + name: "add provider,ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + expectPush( + []*repository.Event{ + eventFromEventPusher( + iam.NewDebugNotificationProviderFileAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + true, + ), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + provider: &fs.FSConfig{ + Compact: true, + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: domain.IAMID, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore, + } + got, err := r.AddDebugNotificationProviderFile(tt.args.ctx, tt.args.provider) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_ChangeDebugNotificationProviderFile(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + } + type args struct { + ctx context.Context + provider *fs.FSConfig + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "provider not existing, not found error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + ), + }, + args: args{ + ctx: context.Background(), + provider: &fs.FSConfig{ + Compact: true, + Enabled: true, + }, + }, + res: res{ + err: caos_errs.IsNotFound, + }, + }, + { + name: "no changes, precondition error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + iam.NewDebugNotificationProviderFileAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + true, + ), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + provider: &fs.FSConfig{ + Compact: true, + Enabled: false, + }, + }, + res: res{ + err: caos_errs.IsPreconditionFailed, + }, + }, + { + name: "no changes enabled, precondition error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + iam.NewDebugNotificationProviderFileAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + true, + ), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + provider: &fs.FSConfig{ + Compact: true, + Enabled: true, + }, + }, + res: res{ + err: caos_errs.IsPreconditionFailed, + }, + }, + { + name: "change, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + iam.NewDebugNotificationProviderFileAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + true, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + newDefaultDebugNotificationFileChangedEvent(context.Background(), + false), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + provider: &fs.FSConfig{ + Compact: false, + Enabled: false, + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "IAM", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore, + } + got, err := r.ChangeDefaultNotificationFile(tt.args.ctx, tt.args.provider) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_RemoveDebugNotificationProviderFile(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + } + type args struct { + ctx context.Context + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "provider not existing, not found error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + ), + }, + args: args{ + ctx: context.Background(), + }, + res: res{ + err: caos_errs.IsNotFound, + }, + }, + { + name: "remove, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + iam.NewDebugNotificationProviderFileAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + true, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + iam.NewDebugNotificationProviderFileRemovedEvent(context.Background(), + &iam.NewAggregate().Aggregate), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "IAM", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore, + } + got, err := r.RemoveDefaultNotificationFile(tt.args.ctx) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} +func newDefaultDebugNotificationFileChangedEvent(ctx context.Context, compact bool) *iam.DebugNotificationProviderFileChangedEvent { + event, _ := iam.NewDebugNotificationProviderFileChangedEvent(ctx, + &iam.NewAggregate().Aggregate, + []settings.DebugNotificationProviderChanges{ + settings.ChangeCompact(compact), + }, + ) + return event +} diff --git a/internal/command/iam_debug_notification_log.go b/internal/command/iam_debug_notification_log.go new file mode 100644 index 0000000000..00942d7bc9 --- /dev/null +++ b/internal/command/iam_debug_notification_log.go @@ -0,0 +1,115 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/domain" + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/notification/channels/fs" + iam_repo "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/telemetry/tracing" +) + +func (c *Commands) AddDebugNotificationProviderLog(ctx context.Context, fileSystemProvider *fs.FSConfig) (*domain.ObjectDetails, error) { + writeModel := NewIAMDebugNotificationLogWriteModel() + iamAgg := IAMAggregateFromWriteModel(&writeModel.WriteModel) + events, err := c.addDefaultDebugNotificationLog(ctx, iamAgg, writeModel, fileSystemProvider) + if err != nil { + return nil, err + } + pushedEvents, err := c.eventstore.Push(ctx, events...) + if err != nil { + return nil, err + } + err = AppendAndReduce(writeModel, pushedEvents...) + if err != nil { + return nil, err + } + return writeModelToObjectDetails(&writeModel.DebugNotificationWriteModel.WriteModel), nil +} + +func (c *Commands) addDefaultDebugNotificationLog(ctx context.Context, iamAgg *eventstore.Aggregate, addedWriteModel *IAMDebugNotificationLogWriteModel, fileSystemProvider *fs.FSConfig) ([]eventstore.Command, error) { + err := c.eventstore.FilterToQueryReducer(ctx, addedWriteModel) + if err != nil { + return nil, err + } + if addedWriteModel.State.Exists() { + return nil, caos_errs.ThrowAlreadyExists(nil, "IAM-3h0fs", "Errors.IAM.DebugNotificationProvider.AlreadyExists") + } + + events := []eventstore.Command{ + iam_repo.NewDebugNotificationProviderLogAddedEvent(ctx, + iamAgg, + fileSystemProvider.Compact), + } + return events, nil +} + +func (c *Commands) ChangeDefaultNotificationLog(ctx context.Context, fileSystemProvider *fs.FSConfig) (*domain.ObjectDetails, error) { + writeModel := NewIAMDebugNotificationLogWriteModel() + iamAgg := IAMAggregateFromWriteModel(&writeModel.WriteModel) + event, err := c.changeDefaultDebugNotificationProviderLog(ctx, iamAgg, writeModel, fileSystemProvider) + if err != nil { + return nil, err + } + pushedEvents, err := c.eventstore.Push(ctx, event) + if err != nil { + return nil, err + } + err = AppendAndReduce(writeModel, pushedEvents...) + if err != nil { + return nil, err + } + return writeModelToObjectDetails(&writeModel.DebugNotificationWriteModel.WriteModel), nil +} + +func (c *Commands) changeDefaultDebugNotificationProviderLog(ctx context.Context, iamAgg *eventstore.Aggregate, existingProvider *IAMDebugNotificationLogWriteModel, fileSystemProvider *fs.FSConfig) (eventstore.Command, error) { + err := c.defaultDebugNotificationProviderLogWriteModelByID(ctx, existingProvider) + if err != nil { + return nil, err + } + if !existingProvider.State.Exists() { + return nil, caos_errs.ThrowNotFound(nil, "IAM-2h0s3", "Errors.IAM.DebugNotificationProvider.NotFound") + } + changedEvent, hasChanged := existingProvider.NewChangedEvent(ctx, + iamAgg, + fileSystemProvider.Compact) + if !hasChanged { + return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-fn9p3", "Errors.IAM.LoginPolicy.NotChanged") + } + return changedEvent, nil +} + +func (c *Commands) RemoveDefaultNotificationLog(ctx context.Context) (*domain.ObjectDetails, error) { + existingProvider := NewIAMDebugNotificationLogWriteModel() + iamAgg := IAMAggregateFromWriteModel(&existingProvider.WriteModel) + err := c.defaultDebugNotificationProviderLogWriteModelByID(ctx, existingProvider) + if err != nil { + return nil, err + } + if !existingProvider.State.Exists() { + return nil, caos_errs.ThrowNotFound(nil, "IAM-39lse", "Errors.IAM.DebugNotificationProvider.NotFound") + } + + events, err := c.eventstore.Push(ctx, iam_repo.NewDebugNotificationProviderLogRemovedEvent(ctx, iamAgg)) + if err != nil { + return nil, err + } + err = AppendAndReduce(existingProvider, events...) + if err != nil { + return nil, err + } + return writeModelToObjectDetails(&existingProvider.DebugNotificationWriteModel.WriteModel), nil +} + +func (c *Commands) defaultDebugNotificationProviderLogWriteModelByID(ctx context.Context, writeModel *IAMDebugNotificationLogWriteModel) (err error) { + ctx, span := tracing.NewSpan(ctx) + defer func() { span.EndWithError(err) }() + + err = c.eventstore.FilterToQueryReducer(ctx, writeModel) + if err != nil { + return err + } + return nil +} diff --git a/internal/command/iam_debug_notification_log_model.go b/internal/command/iam_debug_notification_log_model.go new file mode 100644 index 0000000000..a4f955ec96 --- /dev/null +++ b/internal/command/iam_debug_notification_log_model.go @@ -0,0 +1,81 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/settings" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/repository/iam" +) + +type IAMDebugNotificationLogWriteModel struct { + DebugNotificationWriteModel +} + +func NewIAMDebugNotificationLogWriteModel() *IAMDebugNotificationLogWriteModel { + return &IAMDebugNotificationLogWriteModel{ + DebugNotificationWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: domain.IAMID, + ResourceOwner: domain.IAMID, + }, + }, + } +} + +func (wm *IAMDebugNotificationLogWriteModel) AppendEvents(events ...eventstore.Event) { + for _, event := range events { + switch e := event.(type) { + case *iam.DebugNotificationProviderLogAddedEvent: + wm.DebugNotificationWriteModel.AppendEvents(&e.DebugNotificationProviderAddedEvent) + case *iam.DebugNotificationProviderLogChangedEvent: + wm.DebugNotificationWriteModel.AppendEvents(&e.DebugNotificationProviderChangedEvent) + case *iam.DebugNotificationProviderLogRemovedEvent: + wm.DebugNotificationWriteModel.AppendEvents(&e.DebugNotificationProviderRemovedEvent) + } + } +} + +func (wm *IAMDebugNotificationLogWriteModel) IsValid() bool { + return wm.AggregateID != "" +} + +func (wm *IAMDebugNotificationLogWriteModel) Reduce() error { + return wm.DebugNotificationWriteModel.Reduce() +} + +func (wm *IAMDebugNotificationLogWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + ResourceOwner(wm.ResourceOwner). + AddQuery(). + AggregateTypes(iam.AggregateType). + AggregateIDs(wm.DebugNotificationWriteModel.AggregateID). + EventTypes( + iam.DebugNotificationProviderLogAddedEventType, + iam.DebugNotificationProviderLogChangedEventType, + iam.DebugNotificationProviderLogEnabledEventType, + iam.DebugNotificationProviderLogDisabledEventType, + iam.DebugNotificationProviderLogRemovedEventType). + Builder() +} + +func (wm *IAMDebugNotificationLogWriteModel) NewChangedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + compact bool) (*iam.DebugNotificationProviderLogChangedEvent, bool) { + + changes := make([]settings.DebugNotificationProviderChanges, 0) + if wm.Compact != compact { + changes = append(changes, settings.ChangeCompact(compact)) + } + if len(changes) == 0 { + return nil, false + } + changedEvent, err := iam.NewDebugNotificationProviderLogChangedEvent(ctx, aggregate, changes) + if err != nil { + return nil, false + } + return changedEvent, true +} diff --git a/internal/command/iam_debug_notification_log_test.go b/internal/command/iam_debug_notification_log_test.go new file mode 100644 index 0000000000..23d78fcd10 --- /dev/null +++ b/internal/command/iam_debug_notification_log_test.go @@ -0,0 +1,389 @@ +package command + +import ( + "context" + "testing" + + "github.com/caos/zitadel/internal/domain" + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/notification/channels/fs" + "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/settings" + + "github.com/stretchr/testify/assert" +) + +func TestCommandSide_AddDefaultDebugNotificationProviderLog(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + } + type args struct { + ctx context.Context + provider *fs.FSConfig + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "provider already existing, already exists error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + iam.NewDebugNotificationProviderLogAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + true, + ), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + provider: &fs.FSConfig{ + Compact: true, + Enabled: true, + }, + }, + res: res{ + err: caos_errs.IsErrorAlreadyExists, + }, + }, + { + name: "add disabled provider,ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + expectPush( + []*repository.Event{ + eventFromEventPusher( + iam.NewDebugNotificationProviderLogAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + true, + ), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + provider: &fs.FSConfig{ + Compact: true, + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: domain.IAMID, + }, + }, + }, + { + name: "add provider,ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + expectPush( + []*repository.Event{ + eventFromEventPusher( + iam.NewDebugNotificationProviderLogAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + true, + ), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + provider: &fs.FSConfig{ + Compact: true, + Enabled: true, + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: domain.IAMID, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore, + } + got, err := r.AddDebugNotificationProviderLog(tt.args.ctx, tt.args.provider) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_ChangeDebugNotificationProviderLog(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + } + type args struct { + ctx context.Context + provider *fs.FSConfig + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "provider not existing, not found error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + ), + }, + args: args{ + ctx: context.Background(), + provider: &fs.FSConfig{ + Compact: true, + Enabled: true, + }, + }, + res: res{ + err: caos_errs.IsNotFound, + }, + }, + { + name: "no changes, precondition error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + iam.NewDebugNotificationProviderLogAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + true, + ), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + provider: &fs.FSConfig{ + Compact: true, + Enabled: false, + }, + }, + res: res{ + err: caos_errs.IsPreconditionFailed, + }, + }, + { + name: "change, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + iam.NewDebugNotificationProviderLogAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + true, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + newDefaultDebugNotificationLogChangedEvent(context.Background(), + false), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + provider: &fs.FSConfig{ + Compact: false, + Enabled: false, + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "IAM", + }, + }, + }, + { + name: "change, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + iam.NewDebugNotificationProviderLogAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + true, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + newDefaultDebugNotificationLogChangedEvent(context.Background(), + false), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + provider: &fs.FSConfig{ + Compact: false, + Enabled: true, + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "IAM", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore, + } + got, err := r.ChangeDefaultNotificationLog(tt.args.ctx, tt.args.provider) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_RemoveDebugNotificationProviderLog(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + } + type args struct { + ctx context.Context + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "provider not existing, not found error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + ), + }, + args: args{ + ctx: context.Background(), + }, + res: res{ + err: caos_errs.IsNotFound, + }, + }, + { + name: "remove, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + iam.NewDebugNotificationProviderLogAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + true, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + iam.NewDebugNotificationProviderLogRemovedEvent(context.Background(), + &iam.NewAggregate().Aggregate), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "IAM", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore, + } + got, err := r.RemoveDefaultNotificationLog(tt.args.ctx) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} +func newDefaultDebugNotificationLogChangedEvent(ctx context.Context, compact bool) *iam.DebugNotificationProviderLogChangedEvent { + event, _ := iam.NewDebugNotificationProviderLogChangedEvent(ctx, + &iam.NewAggregate().Aggregate, + []settings.DebugNotificationProviderChanges{ + settings.ChangeCompact(compact), + }, + ) + return event +} diff --git a/internal/config/systemdefaults/system_defaults.go b/internal/config/systemdefaults/system_defaults.go index 5e598f4901..76f9d5d0e5 100644 --- a/internal/config/systemdefaults/system_defaults.go +++ b/internal/config/systemdefaults/system_defaults.go @@ -6,7 +6,6 @@ import ( "golang.org/x/text/language" "github.com/caos/zitadel/internal/crypto" - "github.com/caos/zitadel/internal/notification/channels/chat" "github.com/caos/zitadel/internal/notification/channels/fs" "github.com/caos/zitadel/internal/notification/channels/log" "github.com/caos/zitadel/internal/notification/channels/twilio" @@ -54,9 +53,10 @@ type DomainVerification struct { } type Notifications struct { - DebugMode bool - Endpoints Endpoints - Providers Channels + DebugMode bool + Endpoints Endpoints + FileSystemPath string + //Providers Channels } type Endpoints struct { @@ -68,7 +68,6 @@ type Endpoints struct { } type Channels struct { - Chat chat.ChatConfig Twilio twilio.TwilioConfig FileSystem fs.FSConfig Log log.LogConfig diff --git a/internal/domain/notification.go b/internal/domain/notification.go index 1a45c99461..f4af3ea515 100644 --- a/internal/domain/notification.go +++ b/internal/domain/notification.go @@ -12,3 +12,26 @@ const ( func (f NotificationType) Valid() bool { return f >= 0 && f < notificationCount } + +type NotificationProviderState int32 + +const ( + NotificationProviderStateUnspecified NotificationProviderState = iota + NotificationProviderStateActive + NotificationProviderStateRemoved + + notificationProviderCount +) + +func (s NotificationProviderState) Exists() bool { + return s == NotificationProviderStateActive +} + +type NotificationProviderType int32 + +const ( + NotificationProviderTypeFile NotificationProviderType = iota + NotificationProviderTypeLog + + notificationProviderTypeCount +) diff --git a/internal/notification/channels/chat/channel.go b/internal/notification/channels/chat/channel.go deleted file mode 100644 index 88f21f0e0b..0000000000 --- a/internal/notification/channels/chat/channel.go +++ /dev/null @@ -1,80 +0,0 @@ -package chat - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "net/http" - "net/url" - "unicode/utf8" - - "github.com/k3a/html2text" - - "github.com/caos/logging" - caos_errs "github.com/caos/zitadel/internal/errors" - "github.com/caos/zitadel/internal/notification/channels" -) - -func InitChatChannel(config ChatConfig) (channels.NotificationChannel, error) { - - url, err := url.Parse(config.Url) - if err != nil { - return nil, err - } - - logging.Log("NOTIF-kSvPp").Debug("successfully initialized chat email and sms channel") - - return channels.HandleMessageFunc(func(message channels.Message) error { - contentText := message.GetContent() - if config.Compact { - contentText = html2text.HTML2Text(contentText) - } - for _, splittedMsg := range splitMessage(contentText, config.SplitCount) { - if err := sendMessage(splittedMsg, url); err != nil { - return err - } - } - return nil - }), nil -} - -func sendMessage(message string, chatUrl *url.URL) error { - chatMsg := &struct { - Text string `json:"text"` - }{Text: message} - req, err := json.Marshal(chatMsg) - if err != nil { - return caos_errs.ThrowInternal(err, "PROVI-s8uie", "Could not unmarshal content") - } - - response, err := http.Post(chatUrl.String(), "application/json; charset=UTF-8", bytes.NewReader(req)) - if err != nil { - return caos_errs.ThrowInternal(err, "PROVI-si93s", "unable to send message") - } - if response.StatusCode != 200 { - defer response.Body.Close() - bodyBytes, err := ioutil.ReadAll(response.Body) - if err != nil { - return caos_errs.ThrowInternal(err, "PROVI-PSLd3", "unable to read response message") - } - logging.LogWithFields("PROVI-PS0kx", "Body", string(bodyBytes)).Warn("Chat Message post didnt get 200 OK") - return caos_errs.ThrowInternal(nil, "PROVI-LSopw", string(bodyBytes)) - } - return nil -} - -func splitMessage(message string, count int) []string { - if count == 0 { - return []string{message} - } - var splits []string - var l, r int - for l, r = 0, count; r < len(message); l, r = r, r+count { - for !utf8.RuneStart(message[r]) { - r-- - } - splits = append(splits, message[l:r]) - } - splits = append(splits, message[l:]) - return splits -} diff --git a/internal/notification/channels/chat/config.go b/internal/notification/channels/chat/config.go deleted file mode 100644 index 0c125d7cc8..0000000000 --- a/internal/notification/channels/chat/config.go +++ /dev/null @@ -1,9 +0,0 @@ -package chat - -type ChatConfig struct { - // Defaults to true if DebugMode is set to true - Enabled *bool - Url string - SplitCount int - Compact bool -} diff --git a/internal/notification/channels/fs/channel.go b/internal/notification/channels/fs/channel.go index 751d225360..c2362f1242 100644 --- a/internal/notification/channels/fs/channel.go +++ b/internal/notification/channels/fs/channel.go @@ -19,9 +19,11 @@ import ( "github.com/caos/zitadel/internal/notification/messages" ) -func InitFSChannel(config FSConfig) (channels.NotificationChannel, error) { - - if err := os.MkdirAll(config.Path, os.ModePerm); err != nil { +func InitFSChannel(path string, config FSConfig) (channels.NotificationChannel, error) { + if path == "" { + return nil, nil + } + if err := os.MkdirAll(path, os.ModePerm); err != nil { return nil, err } @@ -46,6 +48,6 @@ func InitFSChannel(config FSConfig) (channels.NotificationChannel, error) { return caos_errors.ThrowUnimplementedf(nil, "NOTIF-6f9a1", "filesystem provider doesn't support message type %T", message) } - return ioutil.WriteFile(filepath.Join(config.Path, fileName), []byte(content), 0666) + return ioutil.WriteFile(filepath.Join(path, fileName), []byte(content), 0666) }), nil } diff --git a/internal/notification/channels/fs/config.go b/internal/notification/channels/fs/config.go index eb64e1eecd..5e8eb15486 100644 --- a/internal/notification/channels/fs/config.go +++ b/internal/notification/channels/fs/config.go @@ -2,6 +2,5 @@ package fs type FSConfig struct { Enabled bool - Path string Compact bool } diff --git a/internal/notification/channels/smtp/channel.go b/internal/notification/channels/smtp/channel.go index 251e66efa8..2dc9212454 100644 --- a/internal/notification/channels/smtp/channel.go +++ b/internal/notification/channels/smtp/channel.go @@ -24,12 +24,15 @@ type Email struct { func InitSMTPChannel(ctx context.Context, getSMTPConfig func(ctx context.Context) (*EmailConfig, error)) (*Email, error) { smtpConfig, err := getSMTPConfig(ctx) + if err != nil { + return nil, err + } client, err := smtpConfig.SMTP.connectToSMTP(smtpConfig.Tls) if err != nil { return nil, err } - logging.Log("NOTIF-4n4Ih").Debug("successfully initialized smtp email channel") + logging.New().Debug("successfully initialized smtp email channel") return &Email{ smtpClient: client, diff --git a/internal/notification/notification.go b/internal/notification/notification.go index 4a2169442a..5ff3bfab53 100644 --- a/internal/notification/notification.go +++ b/internal/notification/notification.go @@ -18,10 +18,10 @@ type Config struct { Repository eventsourcing.Config } -func Start(config Config, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm) { +func Start(config Config, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm, smsCrypto *crypto.AESCrypto) { statikFS, err := fs.NewWithNamespace("notification") logging.OnError(err).Panic("unable to start listener") - _, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults, command, queries, dbClient, assetsPrefix, smtpPasswordEncAlg) + _, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults, command, queries, dbClient, assetsPrefix, smtpPasswordEncAlg, smsCrypto) logging.OnError(err).Panic("unable to start app") } diff --git a/internal/notification/repository/eventsourcing/handler/handler.go b/internal/notification/repository/eventsourcing/handler/handler.go index 2182c881c5..bb967f8780 100644 --- a/internal/notification/repository/eventsourcing/handler/handler.go +++ b/internal/notification/repository/eventsourcing/handler/handler.go @@ -34,7 +34,7 @@ func (h *handler) Eventstore() v1.Eventstore { return h.es } -func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, command *command.Commands, queries *query.Queries, systemDefaults sd.SystemDefaults, dir http.FileSystem, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm) []queryv1.Handler { +func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, command *command.Commands, queries *query.Queries, systemDefaults sd.SystemDefaults, dir http.FileSystem, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm, smsCrypto *crypto.AESCrypto) []queryv1.Handler { aesCrypto, err := crypto.NewAESCrypto(systemDefaults.UserVerificationKey) logging.OnError(err).Fatal("error create new aes crypto") @@ -52,6 +52,7 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es dir, assetsPrefix, smtpPasswordEncAlg, + smsCrypto, ), } } diff --git a/internal/notification/repository/eventsourcing/handler/notification.go b/internal/notification/repository/eventsourcing/handler/notification.go index c151861fd5..b44681ca9f 100644 --- a/internal/notification/repository/eventsourcing/handler/notification.go +++ b/internal/notification/repository/eventsourcing/handler/notification.go @@ -7,6 +7,9 @@ import ( "time" "github.com/caos/logging" + "github.com/caos/zitadel/internal/notification/channels/fs" + "github.com/caos/zitadel/internal/notification/channels/log" + "github.com/caos/zitadel/internal/notification/channels/twilio" "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/command" @@ -43,18 +46,10 @@ type Notification struct { assetsPrefix string queries *query.Queries smtpPasswordCrypto crypto.EncryptionAlgorithm + smsTokenCrypto crypto.EncryptionAlgorithm } -func newNotification( - handler handler, - command *command.Commands, - query *query.Queries, - defaults sd.SystemDefaults, - aesCrypto crypto.EncryptionAlgorithm, - statikDir http.FileSystem, - assetsPrefix string, - smtpPasswordEncAlg crypto.EncryptionAlgorithm, -) *Notification { +func newNotification(handler handler, command *command.Commands, query *query.Queries, defaults sd.SystemDefaults, aesCrypto crypto.EncryptionAlgorithm, statikDir http.FileSystem, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm, smsCrypto *crypto.AESCrypto) *Notification { h := &Notification{ handler: handler, command: command, @@ -64,6 +59,7 @@ func newNotification( assetsPrefix: assetsPrefix, queries: query, smtpPasswordCrypto: smtpPasswordEncAlg, + smsTokenCrypto: smsCrypto, } h.subscribe() @@ -165,7 +161,7 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) { return err } - err = types.SendUserInitCode(ctx, string(template.Template), translator, user, initCode, n.systemDefaults, n.getSMTPConfig, n.AesCrypto, colors, n.assetsPrefix) + err = types.SendUserInitCode(ctx, string(template.Template), translator, user, initCode, n.systemDefaults, n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, n.AesCrypto, colors, n.assetsPrefix) if err != nil { return err } @@ -203,7 +199,7 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) { if err != nil { return err } - err = types.SendPasswordCode(ctx, string(template.Template), translator, user, pwCode, n.systemDefaults, n.getSMTPConfig, n.AesCrypto, colors, n.assetsPrefix) + err = types.SendPasswordCode(ctx, string(template.Template), translator, user, pwCode, n.systemDefaults, n.getSMTPConfig, n.getTwilioConfig, n.getFileSystemProvider, n.getLogProvider, n.AesCrypto, colors, n.assetsPrefix) if err != nil { return err } @@ -242,7 +238,7 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err return err } - err = types.SendEmailVerificationCode(ctx, string(template.Template), translator, user, emailCode, n.systemDefaults, n.getSMTPConfig, n.AesCrypto, colors, n.assetsPrefix) + err = types.SendEmailVerificationCode(ctx, string(template.Template), translator, user, emailCode, n.systemDefaults, n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, n.AesCrypto, colors, n.assetsPrefix) if err != nil { return err } @@ -268,7 +264,7 @@ func (n *Notification) handlePhoneVerificationCode(event *models.Event) (err err if err != nil { return err } - err = types.SendPhoneVerificationCode(translator, user, phoneCode, n.systemDefaults, n.AesCrypto) + err = types.SendPhoneVerificationCode(context.Background(), translator, user, phoneCode, n.systemDefaults, n.getTwilioConfig, n.getFileSystemProvider, n.getLogProvider, n.AesCrypto) if err != nil { return err } @@ -308,7 +304,7 @@ func (n *Notification) handleDomainClaimed(event *models.Event) (err error) { return err } - err = types.SendDomainClaimed(ctx, string(template.Template), translator, user, data["userName"], n.systemDefaults, n.getSMTPConfig, colors, n.assetsPrefix) + err = types.SendDomainClaimed(ctx, string(template.Template), translator, user, data["userName"], n.systemDefaults, n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, colors, n.assetsPrefix) if err != nil { return err } @@ -355,7 +351,7 @@ func (n *Notification) handlePasswordlessRegistrationLink(event *models.Event) ( return err } - err = types.SendPasswordlessRegistrationLink(ctx, string(template.Template), translator, user, addedEvent, n.systemDefaults, n.getSMTPConfig, n.AesCrypto, colors, n.assetsPrefix) + err = types.SendPasswordlessRegistrationLink(ctx, string(template.Template), translator, user, addedEvent, n.systemDefaults, n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, n.AesCrypto, colors, n.assetsPrefix) if err != nil { return err } @@ -394,7 +390,7 @@ func (n *Notification) getUserEvents(userID string, sequence uint64) ([]*models. } func (n *Notification) OnError(event *models.Event, err error) error { - logging.LogWithFields("SPOOL-s9opc", "id", event.AggregateID, "sequence", event.Sequence).WithError(err).Warn("something went wrong in notification handler") + logging.WithFields("id", event.AggregateID, "sequence", event.Sequence).WithError(err).Warn("something went wrong in notification handler") return spooler.HandleError(event, err, n.view.GetLatestNotificationFailedEvent, n.view.ProcessedNotificationFailedEvent, n.view.ProcessedNotificationSequence, n.errorCountUntilSkip) } @@ -437,6 +433,48 @@ func (n *Notification) getSMTPConfig(ctx context.Context) (*smtp.EmailConfig, er }, nil } +// Read iam twilio config +func (n *Notification) getTwilioConfig(ctx context.Context) (*twilio.TwilioConfig, error) { + config, err := n.queries.SMSProviderConfigByID(ctx, domain.IAMID) + if err != nil { + return nil, err + } + if config.TwilioConfig == nil { + return nil, errors.ThrowNotFound(nil, "HANDLER-8nfow", "Errors.SMS.Twilio.NotFound") + } + token, err := crypto.Decrypt(config.TwilioConfig.Token, n.smtpPasswordCrypto) + if err != nil { + return nil, err + } + return &twilio.TwilioConfig{ + SID: config.TwilioConfig.SID, + Token: string(token), + SenderNumber: config.TwilioConfig.SenderNumber, + }, nil +} + +// Read iam filesystem provider config +func (n *Notification) getFileSystemProvider(ctx context.Context) (*fs.FSConfig, error) { + config, err := n.queries.NotificationProviderByIDAndType(ctx, domain.IAMID, domain.NotificationProviderTypeFile) + if err != nil { + return nil, err + } + return &fs.FSConfig{ + Compact: config.Compact, + }, nil +} + +// Read iam log provider config +func (n *Notification) getLogProvider(ctx context.Context) (*log.LogConfig, error) { + config, err := n.queries.NotificationProviderByIDAndType(ctx, domain.IAMID, domain.NotificationProviderTypeLog) + if err != nil { + return nil, err + } + return &log.LogConfig{ + Compact: config.Compact, + }, nil +} + func (n *Notification) getTranslatorWithOrgTexts(orgID, textType string) (*i18n.Translator, error) { ctx := context.Background() translator, err := i18n.NewTranslator(n.statikDir, i18n.TranslatorConfig{DefaultLanguage: n.queries.GetDefaultLanguage(ctx)}) diff --git a/internal/notification/repository/eventsourcing/repository.go b/internal/notification/repository/eventsourcing/repository.go index d9b4d4a9b3..76ba54e946 100644 --- a/internal/notification/repository/eventsourcing/repository.go +++ b/internal/notification/repository/eventsourcing/repository.go @@ -22,7 +22,7 @@ type EsRepository struct { spooler *es_spol.Spooler } -func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm) (*EsRepository, error) { +func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm, smsCrypto *crypto.AESCrypto) (*EsRepository, error) { es, err := v1.Start(dbClient) if err != nil { return nil, err @@ -33,7 +33,7 @@ func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, c return nil, err } - spool := spooler.StartSpooler(conf.Spooler, es, view, dbClient, command, queries, systemDefaults, dir, assetsPrefix, smtpPasswordEncAlg) + spool := spooler.StartSpooler(conf.Spooler, es, view, dbClient, command, queries, systemDefaults, dir, assetsPrefix, smtpPasswordEncAlg, smsCrypto) return &EsRepository{ spool, diff --git a/internal/notification/repository/eventsourcing/spooler/spooler.go b/internal/notification/repository/eventsourcing/spooler/spooler.go index ac264e6de7..67dec942b4 100644 --- a/internal/notification/repository/eventsourcing/spooler/spooler.go +++ b/internal/notification/repository/eventsourcing/spooler/spooler.go @@ -21,12 +21,12 @@ type SpoolerConfig struct { Handlers handler.Configs } -func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, command *command.Commands, queries *query.Queries, systemDefaults sd.SystemDefaults, dir http.FileSystem, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm) *spooler.Spooler { +func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, command *command.Commands, queries *query.Queries, systemDefaults sd.SystemDefaults, dir http.FileSystem, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm, smsCrypto *crypto.AESCrypto) *spooler.Spooler { spoolerConfig := spooler.Config{ Eventstore: es, Locker: &locker{dbClient: sql}, ConcurrentWorkers: c.ConcurrentWorkers, - ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, queries, systemDefaults, dir, assetsPrefix, smtpPasswordEncAlg), + ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, queries, systemDefaults, dir, assetsPrefix, smtpPasswordEncAlg, smsCrypto), } spool := spoolerConfig.New() spool.Start() diff --git a/internal/notification/senders/chain.go b/internal/notification/senders/chain.go index 07de483f47..dce46b3247 100644 --- a/internal/notification/senders/chain.go +++ b/internal/notification/senders/chain.go @@ -22,3 +22,7 @@ func (c *Chain) HandleMessage(message channels.Message) error { } return nil } + +func (c *Chain) Len() int { + return len(c.channels) +} diff --git a/internal/notification/senders/debug.go b/internal/notification/senders/debug.go index c7257813af..e6d35541cb 100644 --- a/internal/notification/senders/debug.go +++ b/internal/notification/senders/debug.go @@ -1,45 +1,28 @@ package senders import ( + "context" + "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/notification/channels" - "github.com/caos/zitadel/internal/notification/channels/chat" "github.com/caos/zitadel/internal/notification/channels/fs" "github.com/caos/zitadel/internal/notification/channels/log" ) -func debugChannels(config systemdefaults.Notifications) (channels.NotificationChannel, error) { - +func debugChannels(ctx context.Context, config systemdefaults.Notifications, getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error)) (*Chain, error) { var ( - providers []channels.NotificationChannel - enableChat bool + providers []channels.NotificationChannel ) - if config.Providers.Chat.Enabled != nil { - enableChat = *config.Providers.Chat.Enabled - } else { - // ensures backward compatible configuration - enableChat = config.DebugMode - } - - if enableChat { - p, err := chat.InitChatChannel(config.Providers.Chat) - if err != nil { - return nil, err + if fsProvider, err := getFileSystemProvider(ctx); err == nil { + p, err := fs.InitFSChannel(config.FileSystemPath, *fsProvider) + if err == nil { + providers = append(providers, p) } - providers = append(providers, p) } - if config.Providers.FileSystem.Enabled { - p, err := fs.InitFSChannel(config.Providers.FileSystem) - if err != nil { - return nil, err - } - providers = append(providers, p) - } - - if config.Providers.Log.Enabled { - providers = append(providers, log.InitStdoutChannel(config.Providers.Log)) + if logProvider, err := getLogProvider(ctx); err == nil { + providers = append(providers, log.InitStdoutChannel(*logProvider)) } return chainChannels(providers...), nil diff --git a/internal/notification/senders/email.go b/internal/notification/senders/email.go index 90240b5b53..43b89c7b1c 100644 --- a/internal/notification/senders/email.go +++ b/internal/notification/senders/email.go @@ -3,25 +3,21 @@ package senders import ( "context" + "github.com/caos/logging" "github.com/caos/zitadel/internal/config/systemdefaults" - "github.com/caos/zitadel/internal/notification/channels" + "github.com/caos/zitadel/internal/notification/channels/fs" + "github.com/caos/zitadel/internal/notification/channels/log" "github.com/caos/zitadel/internal/notification/channels/smtp" ) -func EmailChannels(ctx context.Context, config systemdefaults.Notifications, emailConfig func(ctx context.Context) (*smtp.EmailConfig, error)) (channels.NotificationChannel, error) { - - debug, err := debugChannels(config) +func EmailChannels(ctx context.Context, config systemdefaults.Notifications, emailConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error)) (chain *Chain, err error) { + p, err := smtp.InitSMTPChannel(ctx, emailConfig) + if err == nil { + chain.channels = append(chain.channels, p) + } + chain, err = debugChannels(ctx, config, getFileSystemProvider, getLogProvider) if err != nil { - return nil, err + logging.New().Info("Error in creating debug channels") } - - if !config.DebugMode { - p, err := smtp.InitSMTPChannel(ctx, emailConfig) - if err != nil { - return nil, err - } - return chainChannels(debug, p), nil - } - - return debug, nil + return chain, nil } diff --git a/internal/notification/senders/sms.go b/internal/notification/senders/sms.go index 5da4a670ec..1e7c35b38d 100644 --- a/internal/notification/senders/sms.go +++ b/internal/notification/senders/sms.go @@ -1,21 +1,21 @@ package senders import ( + "context" + "github.com/caos/zitadel/internal/config/systemdefaults" - "github.com/caos/zitadel/internal/notification/channels" + "github.com/caos/zitadel/internal/notification/channels/fs" + "github.com/caos/zitadel/internal/notification/channels/log" "github.com/caos/zitadel/internal/notification/channels/twilio" ) -func SMSChannels(config systemdefaults.Notifications) (channels.NotificationChannel, error) { - - debug, err := debugChannels(config) +func SMSChannels(ctx context.Context, config systemdefaults.Notifications, twilioConfig *twilio.TwilioConfig, getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error)) (chain *Chain, err error) { + if twilioConfig != nil { + chain.channels = append(chain.channels, twilio.InitTwilioChannel(*twilioConfig)) + } + chain, err = debugChannels(ctx, config, getFileSystemProvider, getLogProvider) if err != nil { return nil, err } - - if !config.DebugMode { - return chainChannels(debug, twilio.InitTwilioChannel(config.Providers.Twilio)), nil - } - - return debug, nil + return chain, nil } diff --git a/internal/notification/types/domain_claimed.go b/internal/notification/types/domain_claimed.go index e22a689c16..6275a138f1 100644 --- a/internal/notification/types/domain_claimed.go +++ b/internal/notification/types/domain_claimed.go @@ -7,6 +7,8 @@ import ( "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/i18n" + "github.com/caos/zitadel/internal/notification/channels/fs" + "github.com/caos/zitadel/internal/notification/channels/log" "github.com/caos/zitadel/internal/notification/channels/smtp" "github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/query" @@ -18,7 +20,7 @@ type DomainClaimedData struct { URL string } -func SendDomainClaimed(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, emailConfig func(ctx context.Context) (*smtp.EmailConfig, error), colors *query.LabelPolicy, assetsPrefix string) error { +func SendDomainClaimed(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, emailConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), colors *query.LabelPolicy, assetsPrefix string) error { url, err := templates.ParseTemplateText(systemDefaults.Notifications.Endpoints.DomainClaimed, &UrlData{UserID: user.ID}) if err != nil { return err @@ -35,5 +37,5 @@ func SendDomainClaimed(ctx context.Context, mailhtml string, translator *i18n.Tr if err != nil { return err } - return generateEmail(ctx, user, domainClaimedData.Subject, template, systemDefaults.Notifications, emailConfig, true) + return generateEmail(ctx, user, domainClaimedData.Subject, template, systemDefaults.Notifications, emailConfig, getFileSystemProvider, getLogProvider, true) } diff --git a/internal/notification/types/email_verification_code.go b/internal/notification/types/email_verification_code.go index 37f4045253..f94e152f17 100644 --- a/internal/notification/types/email_verification_code.go +++ b/internal/notification/types/email_verification_code.go @@ -7,6 +7,8 @@ import ( "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/i18n" + "github.com/caos/zitadel/internal/notification/channels/fs" + "github.com/caos/zitadel/internal/notification/channels/log" "github.com/caos/zitadel/internal/notification/channels/smtp" "github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/query" @@ -19,7 +21,7 @@ type EmailVerificationCodeData struct { URL string } -func SendEmailVerificationCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error { +func SendEmailVerificationCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error { codeString, err := crypto.DecryptString(code.Code, alg) if err != nil { return err @@ -41,5 +43,5 @@ func SendEmailVerificationCode(ctx context.Context, mailhtml string, translator if err != nil { return err } - return generateEmail(ctx, user, emailCodeData.Subject, template, systemDefaults.Notifications, smtpConfig, true) + return generateEmail(ctx, user, emailCodeData.Subject, template, systemDefaults.Notifications, smtpConfig, getFileSystemProvider, getLogProvider, true) } diff --git a/internal/notification/types/init_code.go b/internal/notification/types/init_code.go index bf46e4c3c8..07f993354f 100644 --- a/internal/notification/types/init_code.go +++ b/internal/notification/types/init_code.go @@ -7,6 +7,8 @@ import ( "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/i18n" + "github.com/caos/zitadel/internal/notification/channels/fs" + "github.com/caos/zitadel/internal/notification/channels/log" "github.com/caos/zitadel/internal/notification/channels/smtp" "github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/query" @@ -25,7 +27,7 @@ type UrlData struct { PasswordSet bool } -func SendUserInitCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error { +func SendUserInitCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error { codeString, err := crypto.DecryptString(code.Code, alg) if err != nil { return err @@ -45,5 +47,5 @@ func SendUserInitCode(ctx context.Context, mailhtml string, translator *i18n.Tra if err != nil { return err } - return generateEmail(ctx, user, initCodeData.Subject, template, systemDefaults.Notifications, smtpConfig, true) + return generateEmail(ctx, user, initCodeData.Subject, template, systemDefaults.Notifications, smtpConfig, getFileSystemProvider, getLogProvider, true) } diff --git a/internal/notification/types/password_code.go b/internal/notification/types/password_code.go index 2c98211c1a..a539f38694 100644 --- a/internal/notification/types/password_code.go +++ b/internal/notification/types/password_code.go @@ -7,7 +7,10 @@ import ( "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/i18n" + "github.com/caos/zitadel/internal/notification/channels/fs" + "github.com/caos/zitadel/internal/notification/channels/log" "github.com/caos/zitadel/internal/notification/channels/smtp" + "github.com/caos/zitadel/internal/notification/channels/twilio" "github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/query" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" @@ -21,7 +24,7 @@ type PasswordCodeData struct { URL string } -func SendPasswordCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error { +func SendPasswordCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getTwilioConfig func(ctx context.Context) (*twilio.TwilioConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error { codeString, err := crypto.DecryptString(code.Code, alg) if err != nil { return err @@ -44,8 +47,8 @@ func SendPasswordCode(ctx context.Context, mailhtml string, translator *i18n.Tra return err } if code.NotificationType == int32(domain.NotificationTypeSms) { - return generateSms(user, passwordResetData.Text, systemDefaults.Notifications, false) + return generateSms(ctx, user, passwordResetData.Text, systemDefaults.Notifications, getTwilioConfig, getFileSystemProvider, getLogProvider, false) } - return generateEmail(ctx, user, passwordResetData.Subject, template, systemDefaults.Notifications, smtpConfig, true) + return generateEmail(ctx, user, passwordResetData.Subject, template, systemDefaults.Notifications, smtpConfig, getFileSystemProvider, getLogProvider, true) } diff --git a/internal/notification/types/passwordless_registration_link.go b/internal/notification/types/passwordless_registration_link.go index 5aa0461999..9092e444c1 100644 --- a/internal/notification/types/passwordless_registration_link.go +++ b/internal/notification/types/passwordless_registration_link.go @@ -7,6 +7,8 @@ import ( "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/i18n" + "github.com/caos/zitadel/internal/notification/channels/fs" + "github.com/caos/zitadel/internal/notification/channels/log" "github.com/caos/zitadel/internal/notification/channels/smtp" "github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/query" @@ -19,7 +21,7 @@ type PasswordlessRegistrationLinkData struct { URL string } -func SendPasswordlessRegistrationLink(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *user.HumanPasswordlessInitCodeRequestedEvent, systemDefaults systemdefaults.SystemDefaults, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error { +func SendPasswordlessRegistrationLink(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *user.HumanPasswordlessInitCodeRequestedEvent, systemDefaults systemdefaults.SystemDefaults, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string) error { codeString, err := crypto.DecryptString(code.Code, alg) if err != nil { return err @@ -36,5 +38,5 @@ func SendPasswordlessRegistrationLink(ctx context.Context, mailhtml string, tran if err != nil { return err } - return generateEmail(ctx, user, emailCodeData.Subject, template, systemDefaults.Notifications, smtpConfig, true) + return generateEmail(ctx, user, emailCodeData.Subject, template, systemDefaults.Notifications, smtpConfig, getFileSystemProvider, getLogProvider, true) } diff --git a/internal/notification/types/phone_verification_code.go b/internal/notification/types/phone_verification_code.go index 16c5fc3be7..3827cd78ec 100644 --- a/internal/notification/types/phone_verification_code.go +++ b/internal/notification/types/phone_verification_code.go @@ -1,12 +1,16 @@ package types import ( + "context" "fmt" "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/i18n" + "github.com/caos/zitadel/internal/notification/channels/fs" + "github.com/caos/zitadel/internal/notification/channels/log" + "github.com/caos/zitadel/internal/notification/channels/twilio" "github.com/caos/zitadel/internal/notification/templates" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" view_model "github.com/caos/zitadel/internal/user/repository/view/model" @@ -16,7 +20,7 @@ type PhoneVerificationCodeData struct { UserID string } -func SendPhoneVerificationCode(translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.PhoneCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm) error { +func SendPhoneVerificationCode(ctx context.Context, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.PhoneCode, systemDefaults systemdefaults.SystemDefaults, getTwilioConfig func(ctx context.Context) (*twilio.TwilioConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm) error { codeString, err := crypto.DecryptString(code.Code, alg) if err != nil { return err @@ -31,5 +35,5 @@ func SendPhoneVerificationCode(translator *i18n.Translator, user *view_model.Not if err != nil { return err } - return generateSms(user, template, systemDefaults.Notifications, true) + return generateSms(ctx, user, template, systemDefaults.Notifications, getTwilioConfig, getFileSystemProvider, getLogProvider, true) } diff --git a/internal/notification/types/user_email.go b/internal/notification/types/user_email.go index d5ea7c483c..00a0f58e48 100644 --- a/internal/notification/types/user_email.go +++ b/internal/notification/types/user_email.go @@ -4,6 +4,9 @@ import ( "context" "html" + caos_errors "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/notification/channels/fs" + "github.com/caos/zitadel/internal/notification/channels/log" "github.com/caos/zitadel/internal/notification/channels/smtp" "github.com/caos/zitadel/internal/notification/messages" "github.com/caos/zitadel/internal/notification/senders" @@ -12,7 +15,7 @@ import ( view_model "github.com/caos/zitadel/internal/user/repository/view/model" ) -func generateEmail(ctx context.Context, user *view_model.NotifyUser, subject, content string, config systemdefaults.Notifications, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), lastEmail bool) error { +func generateEmail(ctx context.Context, user *view_model.NotifyUser, subject, content string, config systemdefaults.Notifications, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), lastEmail bool) error { content = html.UnescapeString(content) message := &messages.Email{ Recipients: []string{user.VerifiedEmail}, @@ -23,12 +26,15 @@ func generateEmail(ctx context.Context, user *view_model.NotifyUser, subject, co message.Recipients = []string{user.LastEmail} } - channels, err := senders.EmailChannels(ctx, config, smtpConfig) + channelChain, err := senders.EmailChannels(ctx, config, smtpConfig, getFileSystemProvider, getLogProvider) if err != nil { return err } - return channels.HandleMessage(message) + if channelChain.Len() == 0 { + return caos_errors.ThrowPreconditionFailed(nil, "MAIL-83nof", "Errors.Notification.Channels.NotPresent") + } + return channelChain.HandleMessage(message) } func mapNotifyUserToArgs(user *view_model.NotifyUser) map[string]interface{} { diff --git a/internal/notification/types/user_phone.go b/internal/notification/types/user_phone.go index 55fdb7e5e4..5897f232d6 100644 --- a/internal/notification/types/user_phone.go +++ b/internal/notification/types/user_phone.go @@ -1,15 +1,26 @@ package types import ( + "context" + "github.com/caos/zitadel/internal/config/systemdefaults" + caos_errors "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/notification/channels/fs" + "github.com/caos/zitadel/internal/notification/channels/log" + "github.com/caos/zitadel/internal/notification/channels/twilio" "github.com/caos/zitadel/internal/notification/messages" "github.com/caos/zitadel/internal/notification/senders" view_model "github.com/caos/zitadel/internal/user/repository/view/model" ) -func generateSms(user *view_model.NotifyUser, content string, config systemdefaults.Notifications, lastPhone bool) error { +func generateSms(ctx context.Context, user *view_model.NotifyUser, content string, config systemdefaults.Notifications, getTwilioProvider func(ctx context.Context) (*twilio.TwilioConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), lastPhone bool) error { + number := "" + twilio, err := getTwilioProvider(ctx) + if err == nil { + number = twilio.SenderNumber + } message := &messages.SMS{ - SenderPhoneNumber: config.Providers.Twilio.SenderNumber, + SenderPhoneNumber: number, RecipientPhoneNumber: user.VerifiedPhone, Content: content, } @@ -17,9 +28,10 @@ func generateSms(user *view_model.NotifyUser, content string, config systemdefau message.RecipientPhoneNumber = user.LastPhone } - channels, err := senders.SMSChannels(config) - if err != nil { - return err + channelChain, err := senders.SMSChannels(ctx, config, twilio, getFileSystemProvider, getLogProvider) + + if channelChain.Len() == 0 { + return caos_errors.ThrowPreconditionFailed(nil, "PHONE-w8nfow", "Errors.Notification.Channels.NotPresent") } - return channels.HandleMessage(message) + return channelChain.HandleMessage(message) } diff --git a/internal/query/notification_provider.go b/internal/query/notification_provider.go new file mode 100644 index 0000000000..5bd605d5db --- /dev/null +++ b/internal/query/notification_provider.go @@ -0,0 +1,112 @@ +package query + +import ( + "context" + "database/sql" + errs "errors" + "time" + + sq "github.com/Masterminds/squirrel" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query/projection" +) + +type DebugNotificationProvider struct { + AggregateID string + CreationDate time.Time + ChangeDate time.Time + Sequence uint64 + ResourceOwner string + State domain.NotificationProviderState + Type domain.NotificationProviderType + Compact bool +} + +var ( + notificationProviderTable = table{ + name: projection.DebugNotificationProviderTable, + } + NotificationProviderColumnAggID = Column{ + name: projection.DebugNotificationProviderAggIDCol, + table: notificationProviderTable, + } + NotificationProviderColumnCreationDate = Column{ + name: projection.DebugNotificationProviderCreationDateCol, + table: notificationProviderTable, + } + NotificationProviderColumnChangeDate = Column{ + name: projection.DebugNotificationProviderChangeDateCol, + table: notificationProviderTable, + } + NotificationProviderColumnSequence = Column{ + name: projection.DebugNotificationProviderSequenceCol, + table: notificationProviderTable, + } + NotificationProviderColumnResourceOwner = Column{ + name: projection.DebugNotificationProviderResourceOwnerCol, + table: notificationProviderTable, + } + NotificationProviderColumnState = Column{ + name: projection.DebugNotificationProviderStateCol, + table: notificationProviderTable, + } + NotificationProviderColumnType = Column{ + name: projection.DebugNotificationProviderTypeCol, + table: notificationProviderTable, + } + NotificationProviderColumnCompact = Column{ + name: projection.DebugNotificationProviderCompactCol, + table: notificationProviderTable, + } +) + +func (q *Queries) NotificationProviderByIDAndType(ctx context.Context, aggID string, providerType domain.NotificationProviderType) (*DebugNotificationProvider, error) { + query, scan := prepareDebugNotificationProviderQuery() + stmt, args, err := query.Where( + sq.Or{ + sq.Eq{ + LoginPolicyColumnOrgID.identifier(): aggID, + }, + }). + Limit(1).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-f9jSf", "Errors.Query.SQLStatement") + } + + row := q.client.QueryRowContext(ctx, stmt, args...) + return scan(row) +} + +func prepareDebugNotificationProviderQuery() (sq.SelectBuilder, func(*sql.Row) (*DebugNotificationProvider, error)) { + return sq.Select( + NotificationProviderColumnAggID.identifier(), + NotificationProviderColumnCreationDate.identifier(), + NotificationProviderColumnChangeDate.identifier(), + NotificationProviderColumnSequence.identifier(), + NotificationProviderColumnResourceOwner.identifier(), + NotificationProviderColumnState.identifier(), + NotificationProviderColumnType.identifier(), + NotificationProviderColumnCompact.identifier(), + ).From(notificationProviderTable.identifier()).PlaceholderFormat(sq.Dollar), + func(row *sql.Row) (*DebugNotificationProvider, error) { + p := new(DebugNotificationProvider) + err := row.Scan( + &p.AggregateID, + &p.CreationDate, + &p.ChangeDate, + &p.Sequence, + &p.ResourceOwner, + &p.State, + &p.Type, + &p.Compact, + ) + if err != nil { + if errs.Is(err, sql.ErrNoRows) { + return nil, errors.ThrowNotFound(err, "QUERY-s9ujf", "Errors.NotificationProvider.NotFound") + } + return nil, errors.ThrowInternal(err, "QUERY-2liu0", "Errors.Internal") + } + return p, nil + } +} diff --git a/internal/query/notification_provider_test.go b/internal/query/notification_provider_test.go new file mode 100644 index 0000000000..82103e620b --- /dev/null +++ b/internal/query/notification_provider_test.go @@ -0,0 +1,130 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/caos/zitadel/internal/domain" + errs "github.com/caos/zitadel/internal/errors" +) + +func Test_NotificationProviderPrepares(t *testing.T) { + type want struct { + sqlExpectations sqlExpectation + err checkErr + } + tests := []struct { + name string + prepare interface{} + want want + object interface{} + }{ + { + name: "prepareNotificationProviderQuery no result", + prepare: prepareDebugNotificationProviderQuery, + want: want{ + sqlExpectations: mockQueries( + regexp.QuoteMeta(`SELECT zitadel.projections.notification_providers.aggregate_id,`+ + ` zitadel.projections.notification_providers.creation_date,`+ + ` zitadel.projections.notification_providers.change_date,`+ + ` zitadel.projections.notification_providers.sequence,`+ + ` zitadel.projections.notification_providers.resource_owner,`+ + ` zitadel.projections.notification_providers.state,`+ + ` zitadel.projections.notification_providers.provider_type,`+ + ` zitadel.projections.notification_providers.compact`+ + ` FROM zitadel.projections.notification_providers`), + nil, + nil, + ), + err: func(err error) (error, bool) { + if !errs.IsNotFound(err) { + return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false + } + return nil, true + }, + }, + object: (*DebugNotificationProvider)(nil), + }, + { + name: "prepareNotificationProviderQuery found", + prepare: prepareDebugNotificationProviderQuery, + want: want{ + sqlExpectations: mockQuery( + regexp.QuoteMeta(`SELECT zitadel.projections.notification_providers.aggregate_id,`+ + ` zitadel.projections.notification_providers.creation_date,`+ + ` zitadel.projections.notification_providers.change_date,`+ + ` zitadel.projections.notification_providers.sequence,`+ + ` zitadel.projections.notification_providers.resource_owner,`+ + ` zitadel.projections.notification_providers.state,`+ + ` zitadel.projections.notification_providers.provider_type,`+ + ` zitadel.projections.notification_providers.compact`+ + ` FROM zitadel.projections.notification_providers`), + []string{ + "aggregate_id", + "creation_date", + "change_date", + "sequence", + "resource_owner", + "state", + "provider_type", + "compact", + }, + []driver.Value{ + "agg-id", + testNow, + testNow, + uint64(20211109), + "ro-id", + domain.NotificationProviderStateActive, + domain.NotificationProviderTypeFile, + true, + }, + ), + }, + object: &DebugNotificationProvider{ + AggregateID: "agg-id", + CreationDate: testNow, + ChangeDate: testNow, + Sequence: 20211109, + ResourceOwner: "ro-id", + State: domain.NotificationProviderStateActive, + Type: domain.NotificationProviderTypeFile, + Compact: true, + }, + }, + { + name: "prepareNotificationProviderQuery sql err", + prepare: prepareDebugNotificationProviderQuery, + want: want{ + sqlExpectations: mockQueryErr( + regexp.QuoteMeta(`SELECT zitadel.projections.notification_providers.aggregate_id,`+ + ` zitadel.projections.notification_providers.creation_date,`+ + ` zitadel.projections.notification_providers.change_date,`+ + ` zitadel.projections.notification_providers.sequence,`+ + ` zitadel.projections.notification_providers.resource_owner,`+ + ` zitadel.projections.notification_providers.state,`+ + ` zitadel.projections.notification_providers.provider_type,`+ + ` zitadel.projections.notification_providers.compact`+ + ` FROM zitadel.projections.notification_providers`), + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err) + }) + } +} diff --git a/internal/query/projection/debug_notification.go b/internal/query/projection/debug_notification.go new file mode 100644 index 0000000000..1c2846df74 --- /dev/null +++ b/internal/query/projection/debug_notification.go @@ -0,0 +1,160 @@ +package projection + +import ( + "context" + + "github.com/caos/logging" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/repository/settings" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/handler/crdb" + "github.com/caos/zitadel/internal/repository/iam" +) + +type DebugNotificationProviderProjection struct { + crdb.StatementHandler +} + +const ( + DebugNotificationProviderTable = "zitadel.projections.notification_providers" +) + +func NewDebugNotificationProviderProjection(ctx context.Context, config crdb.StatementHandlerConfig) *DebugNotificationProviderProjection { + p := &DebugNotificationProviderProjection{} + config.ProjectionName = DebugNotificationProviderTable + config.Reducers = p.reducers() + p.StatementHandler = crdb.NewStatementHandler(ctx, config) + return p +} + +func (p *DebugNotificationProviderProjection) reducers() []handler.AggregateReducer { + return []handler.AggregateReducer{ + { + Aggregate: iam.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: iam.DebugNotificationProviderFileAddedEventType, + Reduce: p.reduceDebugNotificationProviderAdded, + }, + { + Event: iam.DebugNotificationProviderFileChangedEventType, + Reduce: p.reduceDebugNotificationProviderChanged, + }, + { + Event: iam.DebugNotificationProviderFileRemovedEventType, + Reduce: p.reduceDebugNotificationProviderRemoved, + }, + { + Event: iam.DebugNotificationProviderLogAddedEventType, + Reduce: p.reduceDebugNotificationProviderAdded, + }, + { + Event: iam.DebugNotificationProviderLogChangedEventType, + Reduce: p.reduceDebugNotificationProviderChanged, + }, + { + Event: iam.DebugNotificationProviderLogRemovedEventType, + Reduce: p.reduceDebugNotificationProviderRemoved, + }, + }, + }, + } +} + +const ( + DebugNotificationProviderAggIDCol = "aggregate_id" + DebugNotificationProviderCreationDateCol = "creation_date" + DebugNotificationProviderChangeDateCol = "change_date" + DebugNotificationProviderSequenceCol = "sequence" + DebugNotificationProviderResourceOwnerCol = "resource_owner" + DebugNotificationProviderStateCol = "state" + DebugNotificationProviderTypeCol = "provider_type" + DebugNotificationProviderCompactCol = "compact" +) + +func (p *DebugNotificationProviderProjection) reduceDebugNotificationProviderAdded(event eventstore.Event) (*handler.Statement, error) { + var providerEvent settings.DebugNotificationProviderAddedEvent + var providerType domain.NotificationProviderType + switch e := event.(type) { + case *iam.DebugNotificationProviderFileAddedEvent: + providerEvent = e.DebugNotificationProviderAddedEvent + providerType = domain.NotificationProviderTypeFile + case *iam.DebugNotificationProviderLogAddedEvent: + providerEvent = e.DebugNotificationProviderAddedEvent + providerType = domain.NotificationProviderTypeLog + default: + logging.WithFields("seq", event.Sequence(), "expectedTypes", []eventstore.EventType{iam.DebugNotificationProviderFileAddedEventType, iam.DebugNotificationProviderLogAddedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-pYPxS", "reduce.wrong.event.type") + } + + return crdb.NewCreateStatement(&providerEvent, []handler.Column{ + handler.NewCol(DebugNotificationProviderAggIDCol, providerEvent.Aggregate().ID), + handler.NewCol(DebugNotificationProviderCreationDateCol, providerEvent.CreationDate()), + handler.NewCol(DebugNotificationProviderChangeDateCol, providerEvent.CreationDate()), + handler.NewCol(DebugNotificationProviderSequenceCol, providerEvent.Sequence()), + handler.NewCol(DebugNotificationProviderResourceOwnerCol, providerEvent.Aggregate().ResourceOwner), + handler.NewCol(DebugNotificationProviderStateCol, domain.NotificationProviderStateActive), + handler.NewCol(DebugNotificationProviderTypeCol, providerType), + handler.NewCol(DebugNotificationProviderCompactCol, providerEvent.Compact), + }), nil +} + +func (p *DebugNotificationProviderProjection) reduceDebugNotificationProviderChanged(event eventstore.Event) (*handler.Statement, error) { + var providerEvent settings.DebugNotificationProviderChangedEvent + var providerType domain.NotificationProviderType + switch e := event.(type) { + case *iam.DebugNotificationProviderFileChangedEvent: + providerEvent = e.DebugNotificationProviderChangedEvent + providerType = domain.NotificationProviderTypeFile + case *iam.DebugNotificationProviderLogChangedEvent: + providerEvent = e.DebugNotificationProviderChangedEvent + providerType = domain.NotificationProviderTypeLog + default: + logging.WithFields("seq", event.Sequence(), "expectedTypes", []eventstore.EventType{iam.DebugNotificationProviderFileChangedEventType, iam.DebugNotificationProviderLogChangedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-pYPxS", "reduce.wrong.event.type") + } + + cols := []handler.Column{ + handler.NewCol(DebugNotificationProviderChangeDateCol, providerEvent.CreationDate()), + handler.NewCol(DebugNotificationProviderSequenceCol, providerEvent.Sequence()), + } + if providerEvent.Compact != nil { + cols = append(cols, handler.NewCol(DebugNotificationProviderCompactCol, *providerEvent.Compact)) + } + + return crdb.NewUpdateStatement( + &providerEvent, + cols, + []handler.Condition{ + handler.NewCond(DebugNotificationProviderAggIDCol, providerEvent.Aggregate().ID), + handler.NewCond(DebugNotificationProviderTypeCol, providerType), + }, + ), nil +} + +func (p *DebugNotificationProviderProjection) reduceDebugNotificationProviderRemoved(event eventstore.Event) (*handler.Statement, error) { + var providerEvent settings.DebugNotificationProviderRemovedEvent + var providerType domain.NotificationProviderType + switch e := event.(type) { + case *iam.DebugNotificationProviderFileRemovedEvent: + providerEvent = e.DebugNotificationProviderRemovedEvent + providerType = domain.NotificationProviderTypeFile + case *iam.DebugNotificationProviderLogRemovedEvent: + providerEvent = e.DebugNotificationProviderRemovedEvent + providerType = domain.NotificationProviderTypeLog + default: + logging.WithFields("seq", event.Sequence(), "expectedTypes", []eventstore.EventType{iam.DebugNotificationProviderFileRemovedEventType, iam.DebugNotificationProviderLogRemovedEventType}).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-dow9f", "reduce.wrong.event.type") + } + + return crdb.NewDeleteStatement( + &providerEvent, + []handler.Condition{ + handler.NewCond(DebugNotificationProviderAggIDCol, providerEvent.Aggregate().ID), + handler.NewCond(DebugNotificationProviderTypeCol, providerType), + }, + ), nil +} diff --git a/internal/query/projection/debug_notification_provider_test.go b/internal/query/projection/debug_notification_provider_test.go new file mode 100644 index 0000000000..b1e201a1d0 --- /dev/null +++ b/internal/query/projection/debug_notification_provider_test.go @@ -0,0 +1,232 @@ +package projection + +import ( + "testing" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/iam" +) + +func TestDebugNotificationProviderProjection_reduces(t *testing.T) { + type args struct { + event func(t *testing.T) eventstore.Event + } + tests := []struct { + name string + args args + reduce func(event eventstore.Event) (*handler.Statement, error) + want wantReduce + }{ + { + name: "iam.reduceNotificationProviderFileAdded", + reduce: (&DebugNotificationProviderProjection{}).reduceDebugNotificationProviderAdded, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.DebugNotificationProviderFileAddedEventType), + iam.AggregateType, + []byte(`{ + "compact": true + }`), + ), iam.DebugNotificationProviderFileAddedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: DebugNotificationProviderTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO zitadel.projections.notification_providers (aggregate_id, creation_date, change_date, sequence, resource_owner, state, provider_type, compact) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + "ro-id", + domain.NotificationProviderStateActive, + domain.NotificationProviderTypeFile, + true, + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceNotificationProviderFileChanged", + reduce: (&DebugNotificationProviderProjection{}).reduceDebugNotificationProviderChanged, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.DebugNotificationProviderFileChangedEventType), + iam.AggregateType, + []byte(`{ + "compact": true + }`), + ), iam.DebugNotificationProviderFileChangedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: DebugNotificationProviderTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.notification_providers SET (change_date, sequence, compact) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (provider_type = $5)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + true, + "agg-id", + domain.NotificationProviderTypeFile, + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceNotificationProviderFileRemoved", + reduce: (&DebugNotificationProviderProjection{}).reduceDebugNotificationProviderRemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.DebugNotificationProviderFileRemovedEventType), + iam.AggregateType, + nil, + ), iam.DebugNotificationProviderFileRemovedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: DebugNotificationProviderTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.notification_providers WHERE (aggregate_id = $1) AND (provider_type = $2)", + expectedArgs: []interface{}{ + "agg-id", + domain.NotificationProviderTypeFile, + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceNotificationProviderLogAdded", + reduce: (&DebugNotificationProviderProjection{}).reduceDebugNotificationProviderAdded, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.DebugNotificationProviderLogAddedEventType), + iam.AggregateType, + []byte(`{ + "compact": true + }`), + ), iam.DebugNotificationProviderLogAddedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: DebugNotificationProviderTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO zitadel.projections.notification_providers (aggregate_id, creation_date, change_date, sequence, resource_owner, state, provider_type, compact) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + uint64(15), + "ro-id", + domain.NotificationProviderStateActive, + domain.NotificationProviderTypeLog, + true, + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceNotificationProviderLogChanged", + reduce: (&DebugNotificationProviderProjection{}).reduceDebugNotificationProviderChanged, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.DebugNotificationProviderLogChangedEventType), + iam.AggregateType, + []byte(`{ + "compact": true + }`), + ), iam.DebugNotificationProviderLogChangedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: DebugNotificationProviderTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.notification_providers SET (change_date, sequence, compact) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (provider_type = $5)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + true, + "agg-id", + domain.NotificationProviderTypeLog, + }, + }, + }, + }, + }, + }, + { + name: "iam.reduceNotificationProviderLogRemoved", + reduce: (&DebugNotificationProviderProjection{}).reduceDebugNotificationProviderRemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.DebugNotificationProviderLogRemovedEventType), + iam.AggregateType, + nil, + ), iam.DebugNotificationProviderLogRemovedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + projection: DebugNotificationProviderTable, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "DELETE FROM zitadel.projections.notification_providers WHERE (aggregate_id = $1) AND (provider_type = $2)", + expectedArgs: []interface{}{ + "agg-id", + domain.NotificationProviderTypeLog, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + event := baseEvent(t) + got, err := tt.reduce(event) + if _, ok := err.(errors.InvalidArgument); !ok { + t.Errorf("no wrong event mapping: %v, got: %v", err, got) + } + + event = tt.args.event(t) + got, err = tt.reduce(event) + assertReduce(t, got, err, tt.want) + }) + } +} diff --git a/internal/query/projection/projection.go b/internal/query/projection/projection.go index af22ce0910..8c322b906a 100644 --- a/internal/query/projection/projection.go +++ b/internal/query/projection/projection.go @@ -74,6 +74,7 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co NewSMSConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["sms_config"])) NewOIDCSettingsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["oidc_settings"])) _, err := NewKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyConfig, keyChan) + NewDebugNotificationProviderProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["debug_notification_provider"])) return err } diff --git a/internal/repository/iam/debug_notification_file.go b/internal/repository/iam/debug_notification_file.go new file mode 100644 index 0000000000..7779760594 --- /dev/null +++ b/internal/repository/iam/debug_notification_file.go @@ -0,0 +1,106 @@ +package iam + +import ( + "context" + + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/settings" + + "github.com/caos/zitadel/internal/eventstore/repository" +) + +const ( + fileType = ".file" +) + +var ( + DebugNotificationProviderFileAddedEventType = iamEventTypePrefix + settings.DebugNotificationPrefix + fileType + settings.DebugNotificationProviderAdded + DebugNotificationProviderFileChangedEventType = iamEventTypePrefix + settings.DebugNotificationPrefix + fileType + settings.DebugNotificationProviderChanged + DebugNotificationProviderFileRemovedEventType = iamEventTypePrefix + settings.DebugNotificationPrefix + fileType + settings.DebugNotificationProviderRemoved +) + +type DebugNotificationProviderFileAddedEvent struct { + settings.DebugNotificationProviderAddedEvent +} + +func NewDebugNotificationProviderFileAddedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + compact bool, +) *DebugNotificationProviderFileAddedEvent { + return &DebugNotificationProviderFileAddedEvent{ + DebugNotificationProviderAddedEvent: *settings.NewDebugNotificationProviderAddedEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + DebugNotificationProviderFileAddedEventType), + compact), + } +} + +func DebugNotificationProviderFileAddedEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := settings.DebugNotificationProviderAddedEventMapper(event) + if err != nil { + return nil, err + } + + return &DebugNotificationProviderFileAddedEvent{DebugNotificationProviderAddedEvent: *e.(*settings.DebugNotificationProviderAddedEvent)}, nil +} + +type DebugNotificationProviderFileChangedEvent struct { + settings.DebugNotificationProviderChangedEvent +} + +func NewDebugNotificationProviderFileChangedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + changes []settings.DebugNotificationProviderChanges, +) (*DebugNotificationProviderFileChangedEvent, error) { + changedEvent, err := settings.NewDebugNotificationProviderChangedEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + DebugNotificationProviderFileChangedEventType), + changes, + ) + if err != nil { + return nil, err + } + return &DebugNotificationProviderFileChangedEvent{DebugNotificationProviderChangedEvent: *changedEvent}, nil +} + +func DebugNotificationProviderFileChangedEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := settings.DebugNotificationProviderChangedEventMapper(event) + if err != nil { + return nil, err + } + + return &DebugNotificationProviderFileChangedEvent{DebugNotificationProviderChangedEvent: *e.(*settings.DebugNotificationProviderChangedEvent)}, nil +} + +type DebugNotificationProviderFileRemovedEvent struct { + settings.DebugNotificationProviderRemovedEvent +} + +func NewDebugNotificationProviderFileRemovedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, +) *DebugNotificationProviderFileRemovedEvent { + return &DebugNotificationProviderFileRemovedEvent{ + DebugNotificationProviderRemovedEvent: *settings.NewDebugNotificationProviderRemovedEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + DebugNotificationProviderFileRemovedEventType), + ), + } +} + +func DebugNotificationProviderFileRemovedEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := settings.DebugNotificationProviderRemovedEventMapper(event) + if err != nil { + return nil, err + } + + return &DebugNotificationProviderFileRemovedEvent{DebugNotificationProviderRemovedEvent: *e.(*settings.DebugNotificationProviderRemovedEvent)}, nil +} diff --git a/internal/repository/iam/debug_notification_log.go b/internal/repository/iam/debug_notification_log.go new file mode 100644 index 0000000000..432a4186e5 --- /dev/null +++ b/internal/repository/iam/debug_notification_log.go @@ -0,0 +1,108 @@ +package iam + +import ( + "context" + + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/settings" + + "github.com/caos/zitadel/internal/eventstore/repository" +) + +const ( + logType = ".log" +) + +var ( + DebugNotificationProviderLogAddedEventType = iamEventTypePrefix + settings.DebugNotificationPrefix + logType + settings.DebugNotificationProviderAdded + DebugNotificationProviderLogChangedEventType = iamEventTypePrefix + settings.DebugNotificationPrefix + logType + settings.DebugNotificationProviderChanged + DebugNotificationProviderLogEnabledEventType = iamEventTypePrefix + settings.DebugNotificationPrefix + logType + settings.DebugNotificationProviderEnabled + DebugNotificationProviderLogDisabledEventType = iamEventTypePrefix + settings.DebugNotificationPrefix + logType + settings.DebugNotificationProviderDisabled + DebugNotificationProviderLogRemovedEventType = iamEventTypePrefix + settings.DebugNotificationPrefix + logType + settings.DebugNotificationProviderRemoved +) + +type DebugNotificationProviderLogAddedEvent struct { + settings.DebugNotificationProviderAddedEvent +} + +func NewDebugNotificationProviderLogAddedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + compact bool, +) *DebugNotificationProviderLogAddedEvent { + return &DebugNotificationProviderLogAddedEvent{ + DebugNotificationProviderAddedEvent: *settings.NewDebugNotificationProviderAddedEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + DebugNotificationProviderLogAddedEventType), + compact), + } +} + +func DebugNotificationProviderLogAddedEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := settings.DebugNotificationProviderAddedEventMapper(event) + if err != nil { + return nil, err + } + + return &DebugNotificationProviderLogAddedEvent{DebugNotificationProviderAddedEvent: *e.(*settings.DebugNotificationProviderAddedEvent)}, nil +} + +type DebugNotificationProviderLogChangedEvent struct { + settings.DebugNotificationProviderChangedEvent +} + +func NewDebugNotificationProviderLogChangedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + changes []settings.DebugNotificationProviderChanges, +) (*DebugNotificationProviderLogChangedEvent, error) { + changedEvent, err := settings.NewDebugNotificationProviderChangedEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + DebugNotificationProviderLogChangedEventType), + changes, + ) + if err != nil { + return nil, err + } + return &DebugNotificationProviderLogChangedEvent{DebugNotificationProviderChangedEvent: *changedEvent}, nil +} + +func DebugNotificationProviderLogChangedEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := settings.DebugNotificationProviderChangedEventMapper(event) + if err != nil { + return nil, err + } + + return &DebugNotificationProviderLogChangedEvent{DebugNotificationProviderChangedEvent: *e.(*settings.DebugNotificationProviderChangedEvent)}, nil +} + +type DebugNotificationProviderLogRemovedEvent struct { + settings.DebugNotificationProviderRemovedEvent +} + +func NewDebugNotificationProviderLogRemovedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, +) *DebugNotificationProviderLogRemovedEvent { + return &DebugNotificationProviderLogRemovedEvent{ + DebugNotificationProviderRemovedEvent: *settings.NewDebugNotificationProviderRemovedEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + DebugNotificationProviderLogRemovedEventType), + ), + } +} + +func DebugNotificationProviderLogRemovedEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := settings.DebugNotificationProviderRemovedEventMapper(event) + if err != nil { + return nil, err + } + + return &DebugNotificationProviderLogRemovedEvent{DebugNotificationProviderRemovedEvent: *e.(*settings.DebugNotificationProviderRemovedEvent)}, nil +} diff --git a/internal/repository/iam/eventstore.go b/internal/repository/iam/eventstore.go index 402dd418ad..6ac09458d7 100644 --- a/internal/repository/iam/eventstore.go +++ b/internal/repository/iam/eventstore.go @@ -23,6 +23,12 @@ func RegisterEventMappers(es *eventstore.Eventstore) { RegisterFilterEventMapper(SMSConfigActivatedEventType, SMSConfigActivatedEventMapper). RegisterFilterEventMapper(SMSConfigDeactivatedEventType, SMSConfigDeactivatedEventMapper). RegisterFilterEventMapper(SMSConfigRemovedEventType, SMSConfigRemovedEventMapper). + RegisterFilterEventMapper(DebugNotificationProviderFileAddedEventType, DebugNotificationProviderFileAddedEventMapper). + RegisterFilterEventMapper(DebugNotificationProviderFileChangedEventType, DebugNotificationProviderFileChangedEventMapper). + RegisterFilterEventMapper(DebugNotificationProviderFileRemovedEventType, DebugNotificationProviderFileRemovedEventMapper). + RegisterFilterEventMapper(DebugNotificationProviderLogAddedEventType, DebugNotificationProviderLogAddedEventMapper). + RegisterFilterEventMapper(DebugNotificationProviderLogChangedEventType, DebugNotificationProviderLogChangedEventMapper). + RegisterFilterEventMapper(DebugNotificationProviderLogRemovedEventType, DebugNotificationProviderLogRemovedEventMapper). RegisterFilterEventMapper(OIDCSettingsAddedEventType, OIDCSettingsAddedEventMapper). RegisterFilterEventMapper(OIDCSettingsChangedEventType, OIDCSettingsChangedEventMapper). RegisterFilterEventMapper(LabelPolicyAddedEventType, LabelPolicyAddedEventMapper). diff --git a/internal/repository/settings/debug_notification.go b/internal/repository/settings/debug_notification.go new file mode 100644 index 0000000000..1034abdd36 --- /dev/null +++ b/internal/repository/settings/debug_notification.go @@ -0,0 +1,130 @@ +package settings + +import ( + "encoding/json" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/repository" +) + +const ( + DebugNotificationPrefix = "notification.provider.debug" + DebugNotificationProviderAdded = "added" + DebugNotificationProviderChanged = "changed" + DebugNotificationProviderEnabled = "enabled" + DebugNotificationProviderDisabled = "disabled" + DebugNotificationProviderRemoved = "removed" +) + +type DebugNotificationProviderAddedEvent struct { + eventstore.BaseEvent `json:"-"` + + Compact bool `json:"compact,omitempty"` +} + +func (e *DebugNotificationProviderAddedEvent) Data() interface{} { + return e +} + +func (e *DebugNotificationProviderAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +func NewDebugNotificationProviderAddedEvent( + base *eventstore.BaseEvent, + compact bool, +) *DebugNotificationProviderAddedEvent { + return &DebugNotificationProviderAddedEvent{ + BaseEvent: *base, + Compact: compact, + } +} + +func DebugNotificationProviderAddedEventMapper(event *repository.Event) (eventstore.Event, error) { + e := &DebugNotificationProviderAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + + err := json.Unmarshal(event.Data, e) + if err != nil { + return nil, errors.ThrowInternal(err, "SET-f93ns", "unable to unmarshal debug notification added") + } + + return e, nil +} + +type DebugNotificationProviderChangedEvent struct { + eventstore.BaseEvent `json:"-"` + + Compact *bool `json:"compact,omitempty"` +} + +func (e *DebugNotificationProviderChangedEvent) Data() interface{} { + return e +} + +func (e *DebugNotificationProviderChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +func NewDebugNotificationProviderChangedEvent( + base *eventstore.BaseEvent, + changes []DebugNotificationProviderChanges, +) (*DebugNotificationProviderChangedEvent, error) { + if len(changes) == 0 { + return nil, errors.ThrowPreconditionFailed(nil, "SET-hj90s", "Errors.NoChangesFound") + } + changeEvent := &DebugNotificationProviderChangedEvent{ + BaseEvent: *base, + } + for _, change := range changes { + change(changeEvent) + } + return changeEvent, nil +} + +type DebugNotificationProviderChanges func(*DebugNotificationProviderChangedEvent) + +func ChangeCompact(compact bool) func(*DebugNotificationProviderChangedEvent) { + return func(e *DebugNotificationProviderChangedEvent) { + e.Compact = &compact + } +} + +func DebugNotificationProviderChangedEventMapper(event *repository.Event) (eventstore.Event, error) { + e := &DebugNotificationProviderChangedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + + err := json.Unmarshal(event.Data, e) + if err != nil { + return nil, errors.ThrowInternal(err, "POLIC-ehssl", "unable to unmarshal policy") + } + + return e, nil +} + +type DebugNotificationProviderRemovedEvent struct { + eventstore.BaseEvent `json:"-"` +} + +func (e *DebugNotificationProviderRemovedEvent) Data() interface{} { + return nil +} + +func (e *DebugNotificationProviderRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +func NewDebugNotificationProviderRemovedEvent(base *eventstore.BaseEvent) *DebugNotificationProviderRemovedEvent { + return &DebugNotificationProviderRemovedEvent{ + BaseEvent: *base, + } +} + +func DebugNotificationProviderRemovedEventMapper(event *repository.Event) (eventstore.Event, error) { + return &DebugNotificationProviderRemovedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + }, nil +} diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index bf955bbbd5..2090101353 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -2,11 +2,11 @@ syntax = "proto3"; import "zitadel/idp.proto"; import "zitadel/user.proto"; -import "zitadel/settings.proto"; import "zitadel/object.proto"; import "zitadel/options.proto"; import "zitadel/org.proto"; import "zitadel/policy.proto"; +import "zitadel/settings.proto"; import "zitadel/text.proto"; import "zitadel/member.proto"; import "zitadel/features.proto"; @@ -336,6 +336,28 @@ service AdminService { }; } + // Get file system notification provider + rpc GetFileSystemNotificationProvider(GetFileSystemNotificationProviderRequest) returns (GetFileSystemNotificationProviderResponse) { + option (google.api.http) = { + get: "/notification/provider/file"; + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.read"; + }; + } + + // Get log notification provider + rpc GetLogNotificationProvider(GetLogNotificationProviderRequest) returns (GetLogNotificationProviderResponse) { + option (google.api.http) = { + get: "/notification/provider/log"; + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.read"; + }; + } + // Returns an organisation by id rpc GetOrgByID(GetOrgByIDRequest) returns (GetOrgByIDResponse) { option (google.api.http) = { @@ -2561,6 +2583,20 @@ message UpdateSMSProviderTwilioTokenResponse { zitadel.v1.ObjectDetails details = 1; } +//This is an empty request +message GetFileSystemNotificationProviderRequest {} + +message GetFileSystemNotificationProviderResponse { + zitadel.settings.v1.DebugNotificationProvider provider = 1; +} + +//This is an empty request +message GetLogNotificationProviderRequest {} + +message GetLogNotificationProviderResponse { + zitadel.settings.v1.DebugNotificationProvider provider = 1; +} + // This is an empty request message GetOIDCSettingsRequest {} diff --git a/proto/zitadel/settings.proto b/proto/zitadel/settings.proto index 8af7610166..8d4f8abeeb 100644 --- a/proto/zitadel/settings.proto +++ b/proto/zitadel/settings.proto @@ -72,6 +72,11 @@ enum SMSProviderConfigState { SMS_PROVIDER_CONFIG_INACTIVE = 2; } +message DebugNotificationProvider { + zitadel.v1.ObjectDetails details = 1; + bool compact = 2; +} + message OIDCSettings { zitadel.v1.ObjectDetails details = 1; google.protobuf.Duration access_token_lifetime = 2;