mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-13 14:55:28 +00:00
feat: add notification policy and password change message (#5065)
Implementation of new notification policy with functionality to send email when a password is changed
This commit is contained in:
@@ -252,6 +252,54 @@ func (s *Server) ResetCustomDomainClaimedMessageTextToDefault(ctx context.Contex
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetDefaultPasswordChangeMessageText(ctx context.Context, req *admin_pb.GetDefaultPasswordChangeMessageTextRequest) (*admin_pb.GetDefaultPasswordChangeMessageTextResponse, error) {
|
||||
msg, err := s.query.DefaultMessageTextByTypeAndLanguageFromFileSystem(ctx, domain.PasswordChangeMessageType, req.Language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetDefaultPasswordChangeMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMessageTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetCustomPasswordChangeMessageText(ctx context.Context, req *admin_pb.GetCustomPasswordChangeMessageTextRequest) (*admin_pb.GetCustomPasswordChangeMessageTextResponse, error) {
|
||||
msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, authz.GetInstance(ctx).InstanceID(), domain.PasswordChangeMessageType, req.Language, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetCustomPasswordChangeMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMessageTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) SetDefaultPasswordChangeMessageText(ctx context.Context, req *admin_pb.SetDefaultPasswordChangeMessageTextRequest) (*admin_pb.SetDefaultPasswordChangeMessageTextResponse, error) {
|
||||
result, err := s.command.SetDefaultMessageText(ctx, authz.GetInstance(ctx).InstanceID(), SetPasswordChangeCustomTextToDomain(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.SetDefaultPasswordChangeMessageTextResponse{
|
||||
Details: object.ChangeToDetailsPb(
|
||||
result.Sequence,
|
||||
result.EventDate,
|
||||
result.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ResetCustomPasswordChangeMessageTextToDefault(ctx context.Context, req *admin_pb.ResetCustomPasswordChangeMessageTextToDefaultRequest) (*admin_pb.ResetCustomPasswordChangeMessageTextToDefaultResponse, error) {
|
||||
result, err := s.command.RemoveInstanceMessageTexts(ctx, domain.PasswordChangeMessageType, language.Make(req.Language))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.ResetCustomPasswordChangeMessageTextToDefaultResponse{
|
||||
Details: object.ChangeToDetailsPb(
|
||||
result.Sequence,
|
||||
result.EventDate,
|
||||
result.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetDefaultPasswordlessRegistrationMessageText(ctx context.Context, req *admin_pb.GetDefaultPasswordlessRegistrationMessageTextRequest) (*admin_pb.GetDefaultPasswordlessRegistrationMessageTextResponse, error) {
|
||||
msg, err := s.query.DefaultMessageTextByTypeAndLanguageFromFileSystem(ctx, domain.PasswordlessRegistrationMessageType, req.Language)
|
||||
if err != nil {
|
||||
|
@@ -83,6 +83,21 @@ func SetDomainClaimedCustomTextToDomain(msg *admin_pb.SetDefaultDomainClaimedMes
|
||||
}
|
||||
}
|
||||
|
||||
func SetPasswordChangeCustomTextToDomain(msg *admin_pb.SetDefaultPasswordChangeMessageTextRequest) *domain.CustomMessageText {
|
||||
langTag := language.Make(msg.Language)
|
||||
return &domain.CustomMessageText{
|
||||
MessageTextType: domain.PasswordChangeMessageType,
|
||||
Language: langTag,
|
||||
Title: msg.Title,
|
||||
PreHeader: msg.PreHeader,
|
||||
Subject: msg.Subject,
|
||||
Greeting: msg.Greeting,
|
||||
Text: msg.Text,
|
||||
ButtonText: msg.ButtonText,
|
||||
FooterText: msg.FooterText,
|
||||
}
|
||||
}
|
||||
|
||||
func SetPasswordlessRegistrationCustomTextToDomain(msg *admin_pb.SetDefaultPasswordlessRegistrationMessageTextRequest) *domain.CustomMessageText {
|
||||
langTag := language.Make(msg.Language)
|
||||
return &domain.CustomMessageText{
|
||||
|
46
internal/api/grpc/admin/notification_policy.go
Normal file
46
internal/api/grpc/admin/notification_policy.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||
policy_grpc "github.com/zitadel/zitadel/internal/api/grpc/policy"
|
||||
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||
)
|
||||
|
||||
func (s *Server) AddNotificationPolicy(ctx context.Context, req *admin_pb.AddNotificationPolicyRequest) (*admin_pb.AddNotificationPolicyResponse, error) {
|
||||
result, err := s.command.AddDefaultNotificationPolicy(ctx, authz.GetInstance(ctx).InstanceID(), req.GetPasswordChange())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.AddNotificationPolicyResponse{
|
||||
Details: object.AddToDetailsPb(
|
||||
result.Sequence,
|
||||
result.EventDate,
|
||||
result.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetNotificationPolicy(ctx context.Context, _ *admin_pb.GetNotificationPolicyRequest) (*admin_pb.GetNotificationPolicyResponse, error) {
|
||||
policy, err := s.query.DefaultNotificationPolicy(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.GetNotificationPolicyResponse{Policy: policy_grpc.ModelNotificationPolicyToPb(policy)}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateNotificationPolicy(ctx context.Context, req *admin_pb.UpdateNotificationPolicyRequest) (*admin_pb.UpdateNotificationPolicyResponse, error) {
|
||||
result, err := s.command.ChangeDefaultNotificationPolicy(ctx, authz.GetInstance(ctx).InstanceID(), req.GetPasswordChange())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.UpdateNotificationPolicyResponse{
|
||||
Details: object.ChangeToDetailsPb(
|
||||
result.Sequence,
|
||||
result.EventDate,
|
||||
result.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
@@ -252,6 +252,54 @@ func (s *Server) ResetCustomDomainClaimedMessageTextToDefault(ctx context.Contex
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetCustomPasswordChangeMessageText(ctx context.Context, req *mgmt_pb.GetCustomPasswordChangeMessageTextRequest) (*mgmt_pb.GetCustomPasswordChangeMessageTextResponse, error) {
|
||||
msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, authz.GetCtxData(ctx).OrgID, domain.PasswordChangeMessageType, req.Language, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetCustomPasswordChangeMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMessageTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetDefaultPasswordChangeMessageText(ctx context.Context, req *mgmt_pb.GetDefaultPasswordChangeMessageTextRequest) (*mgmt_pb.GetDefaultPasswordChangeMessageTextResponse, error) {
|
||||
msg, err := s.query.IAMMessageTextByTypeAndLanguage(ctx, domain.PasswordChangeMessageType, req.Language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetDefaultPasswordChangeMessageTextResponse{
|
||||
CustomText: text_grpc.ModelCustomMessageTextToPb(msg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) SetCustomPasswordChangeMessageCustomText(ctx context.Context, req *mgmt_pb.SetCustomPasswordChangeMessageTextRequest) (*mgmt_pb.SetCustomPasswordChangeMessageTextResponse, error) {
|
||||
result, err := s.command.SetOrgMessageText(ctx, authz.GetCtxData(ctx).OrgID, SetPasswordChangeCustomTextToDomain(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.SetCustomPasswordChangeMessageTextResponse{
|
||||
Details: object.ChangeToDetailsPb(
|
||||
result.Sequence,
|
||||
result.EventDate,
|
||||
result.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ResetCustomPasswordChangeMessageTextToDefault(ctx context.Context, req *mgmt_pb.ResetCustomPasswordChangeMessageTextToDefaultRequest) (*mgmt_pb.ResetCustomPasswordChangeMessageTextToDefaultResponse, error) {
|
||||
result, err := s.command.RemoveOrgMessageTexts(ctx, authz.GetCtxData(ctx).OrgID, domain.PasswordChangeMessageType, language.Make(req.Language))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.ResetCustomPasswordChangeMessageTextToDefaultResponse{
|
||||
Details: object.ChangeToDetailsPb(
|
||||
result.Sequence,
|
||||
result.EventDate,
|
||||
result.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetCustomPasswordlessRegistrationMessageText(ctx context.Context, req *mgmt_pb.GetCustomPasswordlessRegistrationMessageTextRequest) (*mgmt_pb.GetCustomPasswordlessRegistrationMessageTextResponse, error) {
|
||||
msg, err := s.query.CustomMessageTextByTypeAndLanguage(ctx, authz.GetCtxData(ctx).OrgID, domain.PasswordlessRegistrationMessageType, req.Language, false)
|
||||
if err != nil {
|
||||
|
@@ -83,6 +83,21 @@ func SetDomainClaimedCustomTextToDomain(msg *mgmt_pb.SetCustomDomainClaimedMessa
|
||||
}
|
||||
}
|
||||
|
||||
func SetPasswordChangeCustomTextToDomain(msg *mgmt_pb.SetCustomPasswordChangeMessageTextRequest) *domain.CustomMessageText {
|
||||
langTag := language.Make(msg.Language)
|
||||
return &domain.CustomMessageText{
|
||||
MessageTextType: domain.PasswordChangeMessageType,
|
||||
Language: langTag,
|
||||
Title: msg.Title,
|
||||
PreHeader: msg.PreHeader,
|
||||
Subject: msg.Subject,
|
||||
Greeting: msg.Greeting,
|
||||
Text: msg.Text,
|
||||
ButtonText: msg.ButtonText,
|
||||
FooterText: msg.FooterText,
|
||||
}
|
||||
}
|
||||
|
||||
func SetPasswordlessRegistrationCustomTextToDomain(msg *mgmt_pb.SetCustomPasswordlessRegistrationMessageTextRequest) *domain.CustomMessageText {
|
||||
langTag := language.Make(msg.Language)
|
||||
return &domain.CustomMessageText{
|
||||
|
64
internal/api/grpc/management/policy_notification.go
Normal file
64
internal/api/grpc/management/policy_notification.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package management
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||
policy_grpc "github.com/zitadel/zitadel/internal/api/grpc/policy"
|
||||
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
)
|
||||
|
||||
func (s *Server) GetNotificationPolicy(ctx context.Context, _ *mgmt_pb.GetNotificationPolicyRequest) (*mgmt_pb.GetNotificationPolicyResponse, error) {
|
||||
policy, err := s.query.NotificationPolicyByOrg(ctx, true, authz.GetCtxData(ctx).OrgID, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetNotificationPolicyResponse{Policy: policy_grpc.ModelNotificationPolicyToPb(policy)}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetDefaultNotificationPolicy(ctx context.Context, _ *mgmt_pb.GetDefaultNotificationPolicyRequest) (*mgmt_pb.GetDefaultNotificationPolicyResponse, error) {
|
||||
policy, err := s.query.DefaultNotificationPolicy(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.GetDefaultNotificationPolicyResponse{Policy: policy_grpc.ModelNotificationPolicyToPb(policy)}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddCustomNotificationPolicy(ctx context.Context, req *mgmt_pb.AddCustomNotificationPolicyRequest) (*mgmt_pb.AddCustomNotificationPolicyResponse, error) {
|
||||
result, err := s.command.AddNotificationPolicy(ctx, authz.GetCtxData(ctx).OrgID, req.GetPasswordChange())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.AddCustomNotificationPolicyResponse{
|
||||
Details: object.AddToDetailsPb(
|
||||
result.Sequence,
|
||||
result.EventDate,
|
||||
result.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateCustomNotificationPolicy(ctx context.Context, req *mgmt_pb.UpdateCustomNotificationPolicyRequest) (*mgmt_pb.UpdateCustomNotificationPolicyResponse, error) {
|
||||
result, err := s.command.ChangeNotificationPolicy(ctx, authz.GetCtxData(ctx).OrgID, req.GetPasswordChange())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.UpdateCustomNotificationPolicyResponse{
|
||||
Details: object.ChangeToDetailsPb(
|
||||
result.Sequence,
|
||||
result.EventDate,
|
||||
result.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ResetNotificationPolicyToDefault(ctx context.Context, _ *mgmt_pb.ResetNotificationPolicyToDefaultRequest) (*mgmt_pb.ResetNotificationPolicyToDefaultResponse, error) {
|
||||
objectDetails, err := s.command.RemoveNotificationPolicy(ctx, authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.ResetNotificationPolicyToDefaultResponse{
|
||||
Details: object.DomainToChangeDetailsPb(objectDetails),
|
||||
}, nil
|
||||
}
|
20
internal/api/grpc/policy/notification_policy.go
Normal file
20
internal/api/grpc/policy/notification_policy.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
policy_pb "github.com/zitadel/zitadel/pkg/grpc/policy"
|
||||
)
|
||||
|
||||
func ModelNotificationPolicyToPb(policy *query.NotificationPolicy) *policy_pb.NotificationPolicy {
|
||||
return &policy_pb.NotificationPolicy{
|
||||
IsDefault: policy.IsDefault,
|
||||
PasswordChange: policy.PasswordChange,
|
||||
Details: object.ToViewDetailsPb(
|
||||
policy.Sequence,
|
||||
policy.CreationDate,
|
||||
policy.ChangeDate,
|
||||
policy.ResourceOwner,
|
||||
),
|
||||
}
|
||||
}
|
@@ -52,6 +52,10 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func LoginHintLink(origin, username string) string {
|
||||
return origin + HandlerPrefix + "?login_hint=" + username
|
||||
}
|
||||
|
||||
func (i *spaHandler) Open(name string) (http.File, error) {
|
||||
ret, err := i.fileSystem.Open(name)
|
||||
if !os.IsNotExist(err) || path.Ext(name) != "" {
|
||||
|
@@ -82,6 +82,9 @@ type InstanceSetup struct {
|
||||
SecondFactorCheckLifetime time.Duration
|
||||
MultiFactorCheckLifetime time.Duration
|
||||
}
|
||||
NotificationPolicy struct {
|
||||
PasswordChange bool
|
||||
}
|
||||
PrivacyPolicy struct {
|
||||
TOSLink string
|
||||
PrivacyLink string
|
||||
@@ -236,6 +239,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
|
||||
prepareAddMultiFactorToDefaultLoginPolicy(instanceAgg, domain.MultiFactorTypeU2FWithPIN),
|
||||
|
||||
prepareAddDefaultPrivacyPolicy(instanceAgg, setup.PrivacyPolicy.TOSLink, setup.PrivacyPolicy.PrivacyLink, setup.PrivacyPolicy.HelpLink),
|
||||
prepareAddDefaultNotificationPolicy(instanceAgg, setup.NotificationPolicy.PasswordChange),
|
||||
prepareAddDefaultLockoutPolicy(instanceAgg, setup.LockoutPolicy.MaxAttempts, setup.LockoutPolicy.ShouldShowLockoutFailure),
|
||||
|
||||
prepareAddDefaultLabelPolicy(
|
||||
|
92
internal/command/instance_policy_notification.go
Normal file
92
internal/command/instance_policy_notification.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
)
|
||||
|
||||
func (c *Commands) AddDefaultNotificationPolicy(ctx context.Context, resourceOwner string, passwordChange bool) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareAddDefaultNotificationPolicy(instanceAgg, passwordChange))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeDefaultNotificationPolicy(ctx context.Context, resourceOwner string, passwordChange bool) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareChangeDefaultNotificationPolicy(instanceAgg, passwordChange))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func prepareAddDefaultNotificationPolicy(
|
||||
a *instance.Aggregate,
|
||||
passwordChange bool,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel := NewInstanceNotificationPolicyWriteModel(ctx)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
if err = writeModel.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if writeModel.State == domain.PolicyStateActive {
|
||||
return nil, caos_errs.ThrowAlreadyExists(nil, "INSTANCE-xpo1bj", "Errors.Instance.NotificationPolicy.AlreadyExists")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
instance.NewNotificationPolicyAddedEvent(ctx, &a.Aggregate, passwordChange),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func prepareChangeDefaultNotificationPolicy(
|
||||
a *instance.Aggregate,
|
||||
passwordChange bool,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel := NewInstanceNotificationPolicyWriteModel(ctx)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
if err = writeModel.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if writeModel.State == domain.PolicyStateUnspecified || writeModel.State == domain.PolicyStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "INSTANCE-x891na", "Errors.IAM.NotificationPolicy.NotFound")
|
||||
}
|
||||
change, hasChanged := writeModel.NewChangedEvent(ctx, &a.Aggregate, passwordChange)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "INSTANCE-29x02n", "Errors.IAM.NotificationPolicy.NotChanged")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
change,
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
72
internal/command/instance_policy_notification_model.go
Normal file
72
internal/command/instance_policy_notification_model.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
type InstanceNotificationPolicyWriteModel struct {
|
||||
NotificationPolicyWriteModel
|
||||
}
|
||||
|
||||
func NewInstanceNotificationPolicyWriteModel(ctx context.Context) *InstanceNotificationPolicyWriteModel {
|
||||
return &InstanceNotificationPolicyWriteModel{
|
||||
NotificationPolicyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: authz.GetInstance(ctx).InstanceID(),
|
||||
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *InstanceNotificationPolicyWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *instance.NotificationPolicyAddedEvent:
|
||||
wm.NotificationPolicyWriteModel.AppendEvents(&e.NotificationPolicyAddedEvent)
|
||||
case *instance.NotificationPolicyChangedEvent:
|
||||
wm.NotificationPolicyWriteModel.AppendEvents(&e.NotificationPolicyChangedEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *InstanceNotificationPolicyWriteModel) Reduce() error {
|
||||
return wm.NotificationPolicyWriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *InstanceNotificationPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(instance.AggregateType).
|
||||
AggregateIDs(wm.NotificationPolicyWriteModel.AggregateID).
|
||||
EventTypes(
|
||||
instance.NotificationPolicyAddedEventType,
|
||||
instance.NotificationPolicyChangedEventType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *InstanceNotificationPolicyWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
passwordChange bool,
|
||||
) (*instance.NotificationPolicyChangedEvent, bool) {
|
||||
|
||||
changes := make([]policy.NotificationPolicyChanges, 0)
|
||||
if wm.PasswordChange != passwordChange {
|
||||
changes = append(changes, policy.ChangePasswordChange(passwordChange))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
changedEvent, err := instance.NewNotificationPolicyChangedEvent(ctx, aggregate, changes)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return changedEvent, true
|
||||
}
|
260
internal/command/instance_policy_notification_test.go
Normal file
260
internal/command/instance_policy_notification_test.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
func TestCommandSide_AddDefaultNotificationPolicy(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
resourceOwner string
|
||||
passwordChange bool
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "notification policy already existing, already exists error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewNotificationPolicyAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "INSTANCE",
|
||||
passwordChange: true,
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add policy,ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
instance.NewNotificationPolicyAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
true,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "INSTANCE",
|
||||
passwordChange: true,
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add empty policy,ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
instance.NewNotificationPolicyAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
true,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "INSTANCE",
|
||||
passwordChange: true,
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.AddDefaultNotificationPolicy(tt.args.ctx, tt.args.resourceOwner, tt.args.passwordChange)
|
||||
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_ChangeDefaultNotificationPolicy(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
resourceOwner string
|
||||
passwordChange bool
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "privacy policy not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "INSTANCE",
|
||||
passwordChange: true,
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewNotificationPolicyAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "INSTANCE",
|
||||
passwordChange: true,
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewNotificationPolicyAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
false,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
newDefaultNotificationPolicyChangedEvent(context.Background(),
|
||||
true,
|
||||
)),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "INSTANCE",
|
||||
passwordChange: true,
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.ChangeDefaultNotificationPolicy(tt.args.ctx, tt.args.resourceOwner, tt.args.passwordChange)
|
||||
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 newDefaultNotificationPolicyChangedEvent(ctx context.Context, passwordChange bool) *instance.NotificationPolicyChangedEvent {
|
||||
event, _ := instance.NewNotificationPolicyChangedEvent(ctx,
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
[]policy.NotificationPolicyChanges{
|
||||
policy.ChangePasswordChange(passwordChange),
|
||||
},
|
||||
)
|
||||
return event
|
||||
}
|
@@ -49,5 +49,6 @@ func orgWriteModelToPrivacyPolicy(wm *OrgPrivacyPolicyWriteModel) *domain.Privac
|
||||
ObjectRoot: writeModelToObjectRoot(wm.PrivacyPolicyWriteModel.WriteModel),
|
||||
TOSLink: wm.TOSLink,
|
||||
PrivacyLink: wm.PrivacyLink,
|
||||
HelpLink: wm.HelpLink,
|
||||
}
|
||||
}
|
||||
|
139
internal/command/org_policy_notification.go
Normal file
139
internal/command/org_policy_notification.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func (c *Commands) AddNotificationPolicy(ctx context.Context, resourceOwner string, passwordChange bool) (*domain.ObjectDetails, error) {
|
||||
if resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-x801sk2i", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareAddNotificationPolicy(orgAgg, passwordChange))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func prepareAddNotificationPolicy(
|
||||
a *org.Aggregate,
|
||||
passwordChange bool,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel := NewOrgNotificationPolicyWriteModel(a.Aggregate.ID)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
if err = writeModel.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if writeModel.State == domain.PolicyStateActive {
|
||||
return nil, caos_errs.ThrowAlreadyExists(nil, "Org-xa08n2", "Errors.Org.NotificationPolicy.AlreadyExists")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
org.NewNotificationPolicyAddedEvent(ctx, &a.Aggregate, passwordChange),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) ChangeNotificationPolicy(ctx context.Context, resourceOwner string, passwordChange bool) (*domain.ObjectDetails, error) {
|
||||
if resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-x091n1g", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareChangeNotificationPolicy(orgAgg, passwordChange))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func prepareChangeNotificationPolicy(
|
||||
a *org.Aggregate,
|
||||
passwordChange bool,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel := NewOrgNotificationPolicyWriteModel(a.Aggregate.ID)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
if err = writeModel.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if writeModel.State == domain.PolicyStateUnspecified || writeModel.State == domain.PolicyStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "ORG-x029n3", "Errors.Org.NotificationPolicy.NotFound")
|
||||
}
|
||||
change, hasChanged := writeModel.NewChangedEvent(ctx, &a.Aggregate, passwordChange)
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-ioqnxz", "Errors.Org.NotificationPolicy.NotChanged")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
change,
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) RemoveNotificationPolicy(ctx context.Context, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||
if resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-x89ns2", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, prepareRemoveNotificationPolicy(orgAgg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func prepareRemoveNotificationPolicy(
|
||||
a *org.Aggregate,
|
||||
) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
writeModel := NewOrgNotificationPolicyWriteModel(a.Aggregate.ID)
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
if err = writeModel.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if writeModel.State == domain.PolicyStateUnspecified || writeModel.State == domain.PolicyStateRemoved {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "ORG-x029n1s", "Errors.Org.NotificationPolicy.NotFound")
|
||||
}
|
||||
return []eventstore.Command{
|
||||
org.NewNotificationPolicyRemovedEvent(ctx, &a.Aggregate),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
73
internal/command/org_policy_notification_model.go
Normal file
73
internal/command/org_policy_notification_model.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
type OrgNotificationPolicyWriteModel struct {
|
||||
NotificationPolicyWriteModel
|
||||
}
|
||||
|
||||
func NewOrgNotificationPolicyWriteModel(orgID string) *OrgNotificationPolicyWriteModel {
|
||||
return &OrgNotificationPolicyWriteModel{
|
||||
NotificationPolicyWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: orgID,
|
||||
ResourceOwner: orgID,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgNotificationPolicyWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *org.NotificationPolicyAddedEvent:
|
||||
wm.NotificationPolicyWriteModel.AppendEvents(&e.NotificationPolicyAddedEvent)
|
||||
case *org.NotificationPolicyChangedEvent:
|
||||
wm.NotificationPolicyWriteModel.AppendEvents(&e.NotificationPolicyChangedEvent)
|
||||
case *org.NotificationPolicyRemovedEvent:
|
||||
wm.NotificationPolicyWriteModel.AppendEvents(&e.NotificationPolicyRemovedEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgNotificationPolicyWriteModel) Reduce() error {
|
||||
return wm.NotificationPolicyWriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *OrgNotificationPolicyWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateIDs(wm.NotificationPolicyWriteModel.AggregateID).
|
||||
AggregateTypes(org.AggregateType).
|
||||
EventTypes(org.NotificationPolicyAddedEventType,
|
||||
org.NotificationPolicyChangedEventType,
|
||||
org.NotificationPolicyRemovedEventType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *OrgNotificationPolicyWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
passwordChange bool,
|
||||
) (*org.NotificationPolicyChangedEvent, bool) {
|
||||
|
||||
changes := make([]policy.NotificationPolicyChanges, 0)
|
||||
if wm.PasswordChange != passwordChange {
|
||||
changes = append(changes, policy.ChangePasswordChange(passwordChange))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
changedEvent, err := org.NewNotificationPolicyChangedEvent(ctx, aggregate, changes)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return changedEvent, true
|
||||
}
|
391
internal/command/org_policy_notification_test.go
Normal file
391
internal/command/org_policy_notification_test.go
Normal file
@@ -0,0 +1,391 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
func TestCommandSide_AddNotificationPolicy(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
passwordChange bool
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "org id missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "",
|
||||
passwordChange: true,
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "policy already existing, already exists error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewNotificationPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
passwordChange: true,
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorAlreadyExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add policy,ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
org.NewNotificationPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
true,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
passwordChange: true,
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add policy empty, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
org.NewNotificationPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
false,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
passwordChange: false,
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.AddNotificationPolicy(tt.args.ctx, tt.args.orgID, tt.args.passwordChange)
|
||||
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_ChangeNotificationPolicy(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
passwordChange bool
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "org id missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
passwordChange: true,
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "policy not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
passwordChange: true,
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewNotificationPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
passwordChange: true,
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewNotificationPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
newNotificationPolicyChangedEvent(context.Background(), "org1", false),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
passwordChange: false,
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.ChangeNotificationPolicy(tt.args.ctx, tt.args.orgID, tt.args.passwordChange)
|
||||
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_RemoveNotificationPolicy(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
orgID string
|
||||
}
|
||||
type res struct {
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "org id missing, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "policy not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewNotificationPolicyAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate,
|
||||
true,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
org.NewNotificationPolicyRemovedEvent(context.Background(),
|
||||
&org.NewAggregate("org1").Aggregate),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
orgID: "org1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
}
|
||||
got, err := r.RemoveNotificationPolicy(tt.args.ctx, tt.args.orgID)
|
||||
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 newNotificationPolicyChangedEvent(ctx context.Context, orgID string, passwordChange bool) *org.NotificationPolicyChangedEvent {
|
||||
event, _ := org.NewNotificationPolicyChangedEvent(ctx,
|
||||
&org.NewAggregate(orgID).Aggregate,
|
||||
[]policy.NotificationPolicyChanges{
|
||||
policy.ChangePasswordChange(passwordChange),
|
||||
},
|
||||
)
|
||||
return event
|
||||
}
|
31
internal/command/policy_notification_model.go
Normal file
31
internal/command/policy_notification_model.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
type NotificationPolicyWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
PasswordChange bool
|
||||
State domain.PolicyState
|
||||
}
|
||||
|
||||
func (wm *NotificationPolicyWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *policy.NotificationPolicyAddedEvent:
|
||||
wm.PasswordChange = e.PasswordChange
|
||||
wm.State = domain.PolicyStateActive
|
||||
case *policy.NotificationPolicyChangedEvent:
|
||||
if e.PasswordChange != nil {
|
||||
wm.PasswordChange = *e.PasswordChange
|
||||
}
|
||||
case *policy.NotificationPolicyRemovedEvent:
|
||||
wm.State = domain.PolicyStateRemoved
|
||||
}
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
@@ -196,6 +196,23 @@ func (c *Commands) PasswordCodeSent(ctx context.Context, orgID, userID string) (
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Commands) PasswordChangeSent(ctx context.Context, orgID, userID string) (err error) {
|
||||
if userID == "" {
|
||||
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-pqlm2n", "Errors.User.UserIDMissing")
|
||||
}
|
||||
|
||||
existingPassword, err := c.passwordWriteModel(ctx, userID, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existingPassword.UserState == domain.UserStateUnspecified || existingPassword.UserState == domain.UserStateDeleted {
|
||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-x902b2v", "Errors.User.NotFound")
|
||||
}
|
||||
userAgg := UserAggregateFromWriteModel(&existingPassword.WriteModel)
|
||||
_, err = c.eventstore.Push(ctx, user.NewHumanPasswordChangeSentEvent(ctx, userAgg))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Commands) HumanCheckPassword(ctx context.Context, orgID, userID, password string, authRequest *domain.AuthRequest, lockoutPolicy *domain.LockoutPolicy) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
@@ -13,6 +13,7 @@ const (
|
||||
VerifyPhoneMessageType = "VerifyPhone"
|
||||
DomainClaimedMessageType = "DomainClaimed"
|
||||
PasswordlessRegistrationMessageType = "PasswordlessRegistration"
|
||||
PasswordChangeMessageType = "PasswordChange"
|
||||
MessageTitle = "Title"
|
||||
MessagePreHeader = "PreHeader"
|
||||
MessageSubject = "Subject"
|
||||
@@ -29,6 +30,7 @@ type MessageTexts struct {
|
||||
VerifyPhone CustomMessageText
|
||||
DomainClaimed CustomMessageText
|
||||
PasswordlessRegistration CustomMessageText
|
||||
PasswordChange CustomMessageText
|
||||
}
|
||||
|
||||
type CustomMessageText struct {
|
||||
@@ -65,6 +67,8 @@ func (m *MessageTexts) GetMessageTextByType(msgType string) *CustomMessageText {
|
||||
return &m.DomainClaimed
|
||||
case PasswordlessRegistrationMessageType:
|
||||
return &m.PasswordlessRegistration
|
||||
case PasswordChangeMessageType:
|
||||
return &m.PasswordChange
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -75,5 +79,6 @@ func IsMessageTextType(textType string) bool {
|
||||
textType == VerifyEmailMessageType ||
|
||||
textType == VerifyPhoneMessageType ||
|
||||
textType == DomainClaimedMessageType ||
|
||||
textType == PasswordlessRegistrationMessageType
|
||||
textType == PasswordlessRegistrationMessageType ||
|
||||
textType == PasswordChangeMessageType
|
||||
}
|
||||
|
@@ -137,6 +137,10 @@ func (p *notificationsProjection) reducers() []handler.AggregateReducer {
|
||||
Event: user.HumanPhoneCodeAddedType,
|
||||
Reduce: p.reducePhoneCodeAdded,
|
||||
},
|
||||
{
|
||||
Event: user.HumanPasswordChangedType,
|
||||
Reduce: p.reducePasswordChanged,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -463,6 +467,74 @@ func (p *notificationsProjection) reducePasswordlessCodeRequested(event eventsto
|
||||
return crdb.NewNoOpStatement(e), nil
|
||||
}
|
||||
|
||||
func (p *notificationsProjection) reducePasswordChanged(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*user.HumanPasswordChangedEvent)
|
||||
if !ok {
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-Yko2z8", "reduce.wrong.event.type %s", user.HumanPasswordChangedType)
|
||||
}
|
||||
ctx := setNotificationContext(event.Aggregate())
|
||||
alreadyHandled, err := p.checkIfAlreadyHandled(ctx, event, nil, user.HumanPasswordChangeSentType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if alreadyHandled {
|
||||
return crdb.NewNoOpStatement(e), nil
|
||||
}
|
||||
|
||||
notificationPolicy, err := p.queries.NotificationPolicyByOrg(ctx, true, e.Aggregate().ResourceOwner, false)
|
||||
if errors.IsNotFound(err) {
|
||||
return crdb.NewNoOpStatement(e), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if notificationPolicy.PasswordChange {
|
||||
colors, err := p.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
template, err := p.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
notifyUser, err := p.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
translator, err := p.getTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.PasswordChangeMessageType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, origin, err := p.origin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = types.SendEmail(
|
||||
ctx,
|
||||
string(template.Template),
|
||||
translator,
|
||||
notifyUser,
|
||||
p.getSMTPConfig,
|
||||
p.getFileSystemProvider,
|
||||
p.getLogProvider,
|
||||
colors,
|
||||
p.assetsPrefix(ctx),
|
||||
).SendPasswordChange(notifyUser, origin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = p.commands.PasswordChangeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return crdb.NewNoOpStatement(e), nil
|
||||
}
|
||||
|
||||
func (p *notificationsProjection) reducePhoneCodeAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*user.HumanPhoneCodeAddedEvent)
|
||||
if !ok {
|
||||
|
@@ -40,3 +40,10 @@ PasswordlessRegistration:
|
||||
Greeting: Hallo {{.FirstName}} {{.LastName}},
|
||||
Text: Wir haben eine Anfrage für das Hinzufügen eines Token für den passwortlosen Login erhalten. Du kannst den untenstehenden Button verwenden, um dein Token oder Gerät hinzuzufügen.
|
||||
ButtonText: Passwortlosen Login hinzufügen
|
||||
PasswordChange:
|
||||
Title: ZITADEL - Passwort von Benutzer wurde geändert
|
||||
PreHeader: Passwort Änderung
|
||||
Subject: Passwort von Benutzer wurde geändert
|
||||
Greeting: Hallo {{.FirstName}} {{.LastName}},
|
||||
Text: Das Password vom Benutzer wurde geändert, wenn diese Änderung von jemand anderem gemacht wurde, empfehlen wir die sofortige Zurücksetzung ihres Passworts.
|
||||
ButtonText: Login
|
@@ -40,3 +40,10 @@ PasswordlessRegistration:
|
||||
Greeting: Hello {{.FirstName}} {{.LastName}},
|
||||
Text: We received a request to add a token for passwordless login. Please use the button below to add your token or device for passwordless login.
|
||||
ButtonText: Add Passwordless Login
|
||||
PasswordChange:
|
||||
Title: ZITADEL - Password of user has changed
|
||||
PreHeader: Change password
|
||||
Subject: Password of user has changed
|
||||
Greeting: Hello {{.FirstName}} {{.LastName}},
|
||||
Text: The password of your user has changed, if this change was not done by you, please be advised to immediately reset your password.
|
||||
ButtonText: Login
|
@@ -40,3 +40,10 @@ PasswordlessRegistration:
|
||||
Greeting: Bonjour {{.FirstName}} {{.LastName}},
|
||||
Text: Nous avons reçu une demande d'ajout d'un jeton pour la connexion sans mot de passe. Veuillez utiliser le bouton ci-dessous pour ajouter votre jeton ou dispositif pour la connexion sans mot de passe.
|
||||
ButtonText: Ajouter une connexion sans mot de passe
|
||||
PasswordChange:
|
||||
Title: ZITADEL - Le mot de passe de l'utilisateur a changé
|
||||
PreHeader: Modifier le mot de passe
|
||||
Subject: Le mot de passe de l'utilisateur a changé
|
||||
Greeting: Bonjour {{.FirstName}} {{.LastName}},
|
||||
Text: Le mot de passe de votre utilisateur a changé, si ce changement n'a pas été fait par vous, nous vous conseillons de réinitialiser immédiatement votre mot de passe.
|
||||
ButtonText: Login
|
||||
|
@@ -40,3 +40,10 @@ PasswordlessRegistration:
|
||||
Greeting: 'Ciao {{.FirstName}} {{.LastName}},'
|
||||
Text: Abbiamo ricevuto una richiesta per aggiungere l'autenticazione passwordless. Usa il pulsante qui sotto per aggiungere il tuo token o dispositivo per il login senza password.
|
||||
ButtonText: Attiva passwordless
|
||||
PasswordChange:
|
||||
Title: ZITADEL - La password dell'utente è stata modificata
|
||||
PreHeader: Modifica della password
|
||||
Subject: La password dell'utente è stata modificata
|
||||
Greeting: Ciao {{.FirstName}} {{.LastName}},
|
||||
Text: La password del vostro utente è cambiata; se questa modifica non è stata fatta da voi, vi consigliamo di reimpostare immediatamente la vostra password.
|
||||
ButtonText: Login
|
@@ -40,3 +40,10 @@ PasswordlessRegistration:
|
||||
Greeting: 你好 {{.FirstName}} {{.LastName}},
|
||||
Text: 我们收到了为无密码登录添加令牌的请求。请使用下面的按钮添加您的令牌或设备以进行无密码登录。
|
||||
ButtonText: 添加无密码登录
|
||||
PasswordChange:
|
||||
Title: ZITADEL - 用户的密码已经改变
|
||||
PreHeader: 更改密码
|
||||
Subject: 用户的密码已经改变
|
||||
Greeting: 你好 {{.FirstName}} {{.LastName}},
|
||||
Text: 您的用户的密码已经改变,如果这个改变不是由您做的,请注意立即重新设置您的密码。
|
||||
ButtonText: 登录
|
||||
|
13
internal/notification/types/password_change.go
Normal file
13
internal/notification/types/password_change.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/internal/api/ui/console"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
)
|
||||
|
||||
func (notify Notify) SendPasswordChange(user *query.NotifyUser, origin string) error {
|
||||
url := console.LoginHintLink(origin, user.PreferredLoginName)
|
||||
args := make(map[string]interface{})
|
||||
return notify(url, args, domain.PasswordChangeMessageType, true)
|
||||
}
|
@@ -29,6 +29,7 @@ type MessageTexts struct {
|
||||
VerifyPhone MessageText
|
||||
DomainClaimed MessageText
|
||||
PasswordlessRegistration MessageText
|
||||
PasswordChange MessageText
|
||||
}
|
||||
|
||||
type MessageText struct {
|
||||
@@ -330,6 +331,8 @@ func (m *MessageTexts) GetMessageTextByType(msgType string) *MessageText {
|
||||
return &m.DomainClaimed
|
||||
case domain.PasswordlessRegistrationMessageType:
|
||||
return &m.PasswordlessRegistration
|
||||
case domain.PasswordChangeMessageType:
|
||||
return &m.PasswordChange
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
166
internal/query/notification_policy.go
Normal file
166
internal/query/notification_policy.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
errs "errors"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
type NotificationPolicy struct {
|
||||
ID string
|
||||
Sequence uint64
|
||||
CreationDate time.Time
|
||||
ChangeDate time.Time
|
||||
ResourceOwner string
|
||||
State domain.PolicyState
|
||||
|
||||
PasswordChange bool
|
||||
|
||||
IsDefault bool
|
||||
}
|
||||
|
||||
var (
|
||||
notificationPolicyTable = table{
|
||||
name: projection.NotificationPolicyProjectionTable,
|
||||
instanceIDCol: projection.NotificationPolicyColumnInstanceID,
|
||||
}
|
||||
NotificationPolicyColID = Column{
|
||||
name: projection.NotificationPolicyColumnID,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColSequence = Column{
|
||||
name: projection.NotificationPolicyColumnSequence,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColCreationDate = Column{
|
||||
name: projection.NotificationPolicyColumnCreationDate,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColChangeDate = Column{
|
||||
name: projection.NotificationPolicyColumnChangeDate,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColResourceOwner = Column{
|
||||
name: projection.NotificationPolicyColumnResourceOwner,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColInstanceID = Column{
|
||||
name: projection.NotificationPolicyColumnInstanceID,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColPasswordChange = Column{
|
||||
name: projection.NotificationPolicyColumnPasswordChange,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColIsDefault = Column{
|
||||
name: projection.NotificationPolicyColumnIsDefault,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColState = Column{
|
||||
name: projection.NotificationPolicyColumnStateCol,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
NotificationPolicyColOwnerRemoved = Column{
|
||||
name: projection.NotificationPolicyColumnOwnerRemoved,
|
||||
table: notificationPolicyTable,
|
||||
}
|
||||
)
|
||||
|
||||
func (q *Queries) NotificationPolicyByOrg(ctx context.Context, shouldTriggerBulk bool, orgID string, withOwnerRemoved bool) (_ *NotificationPolicy, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if shouldTriggerBulk {
|
||||
if err := projection.NotificationPolicyProjection.Trigger(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
eq := sq.Eq{NotificationPolicyColInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||
if !withOwnerRemoved {
|
||||
eq[NotificationPolicyColOwnerRemoved.identifier()] = false
|
||||
}
|
||||
stmt, scan := prepareNotificationPolicyQuery()
|
||||
query, args, err := stmt.Where(
|
||||
sq.And{
|
||||
eq,
|
||||
sq.Or{
|
||||
sq.Eq{NotificationPolicyColID.identifier(): orgID},
|
||||
sq.Eq{NotificationPolicyColID.identifier(): authz.GetInstance(ctx).InstanceID()},
|
||||
},
|
||||
}).
|
||||
OrderBy(NotificationPolicyColIsDefault.identifier()).Limit(1).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Xuoapqm", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func (q *Queries) DefaultNotificationPolicy(ctx context.Context, shouldTriggerBulk bool) (_ *NotificationPolicy, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if shouldTriggerBulk {
|
||||
if err := projection.NotificationPolicyProjection.Trigger(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
stmt, scan := prepareNotificationPolicyQuery()
|
||||
query, args, err := stmt.Where(sq.Eq{
|
||||
NotificationPolicyColID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
NotificationPolicyColInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
}).
|
||||
OrderBy(NotificationPolicyColIsDefault.identifier()).
|
||||
Limit(1).ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "QUERY-xlqp209", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
row := q.client.QueryRowContext(ctx, query, args...)
|
||||
return scan(row)
|
||||
}
|
||||
|
||||
func prepareNotificationPolicyQuery() (sq.SelectBuilder, func(*sql.Row) (*NotificationPolicy, error)) {
|
||||
return sq.Select(
|
||||
NotificationPolicyColID.identifier(),
|
||||
NotificationPolicyColSequence.identifier(),
|
||||
NotificationPolicyColCreationDate.identifier(),
|
||||
NotificationPolicyColChangeDate.identifier(),
|
||||
NotificationPolicyColResourceOwner.identifier(),
|
||||
NotificationPolicyColPasswordChange.identifier(),
|
||||
NotificationPolicyColIsDefault.identifier(),
|
||||
NotificationPolicyColState.identifier(),
|
||||
).
|
||||
From(notificationPolicyTable.identifier()).PlaceholderFormat(sq.Dollar),
|
||||
func(row *sql.Row) (*NotificationPolicy, error) {
|
||||
policy := new(NotificationPolicy)
|
||||
err := row.Scan(
|
||||
&policy.ID,
|
||||
&policy.Sequence,
|
||||
&policy.CreationDate,
|
||||
&policy.ChangeDate,
|
||||
&policy.ResourceOwner,
|
||||
&policy.PasswordChange,
|
||||
&policy.IsDefault,
|
||||
&policy.State,
|
||||
)
|
||||
if err != nil {
|
||||
if errs.Is(err, sql.ErrNoRows) {
|
||||
return nil, errors.ThrowNotFound(err, "QUERY-x0so2p", "Errors.NotificationPolicy.NotFound")
|
||||
}
|
||||
return nil, errors.ThrowInternal(err, "QUERY-Zixoooq", "Errors.Internal")
|
||||
}
|
||||
return policy, nil
|
||||
}
|
||||
}
|
116
internal/query/notification_policy_test.go
Normal file
116
internal/query/notification_policy_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
errs "github.com/zitadel/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
var notificationPolicyStmt = regexp.QuoteMeta(`SELECT projections.notification_policies.id,` +
|
||||
` projections.notification_policies.sequence,` +
|
||||
` projections.notification_policies.creation_date,` +
|
||||
` projections.notification_policies.change_date,` +
|
||||
` projections.notification_policies.resource_owner,` +
|
||||
` projections.notification_policies.password_change,` +
|
||||
` projections.notification_policies.is_default,` +
|
||||
` projections.notification_policies.state` +
|
||||
` FROM projections.notification_policies`)
|
||||
|
||||
func Test_NotificationPolicyPrepares(t *testing.T) {
|
||||
type want struct {
|
||||
sqlExpectations sqlExpectation
|
||||
err checkErr
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare interface{}
|
||||
want want
|
||||
object interface{}
|
||||
}{
|
||||
{
|
||||
name: "prepareNotificationPolicyQuery no result",
|
||||
prepare: prepareNotificationPolicyQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueries(
|
||||
notificationPolicyStmt,
|
||||
nil,
|
||||
nil,
|
||||
),
|
||||
err: func(err error) (error, bool) {
|
||||
if !errs.IsNotFound(err) {
|
||||
return fmt.Errorf("err should be NotFoundError got: %w", err), false
|
||||
}
|
||||
return nil, true
|
||||
},
|
||||
},
|
||||
object: (*NotificationPolicy)(nil),
|
||||
},
|
||||
{
|
||||
name: "prepareNotificationPolicyQuery found",
|
||||
prepare: prepareNotificationPolicyQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQuery(
|
||||
notificationPolicyStmt,
|
||||
[]string{
|
||||
"id",
|
||||
"sequence",
|
||||
"creation_date",
|
||||
"change_date",
|
||||
"resource_owner",
|
||||
"password_change",
|
||||
"is_default",
|
||||
"state",
|
||||
},
|
||||
[]driver.Value{
|
||||
"pol-id",
|
||||
uint64(20211109),
|
||||
testNow,
|
||||
testNow,
|
||||
"ro",
|
||||
true,
|
||||
true,
|
||||
domain.PolicyStateActive,
|
||||
},
|
||||
),
|
||||
},
|
||||
object: &NotificationPolicy{
|
||||
ID: "pol-id",
|
||||
CreationDate: testNow,
|
||||
ChangeDate: testNow,
|
||||
Sequence: 20211109,
|
||||
ResourceOwner: "ro",
|
||||
State: domain.PolicyStateActive,
|
||||
PasswordChange: true,
|
||||
IsDefault: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prepareNotificationPolicyQuery sql err",
|
||||
prepare: prepareNotificationPolicyQuery,
|
||||
want: want{
|
||||
sqlExpectations: mockQueryErr(
|
||||
notificationPolicyStmt,
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
@@ -273,7 +273,8 @@ func isMessageTemplate(template string) bool {
|
||||
template == domain.VerifyEmailMessageType ||
|
||||
template == domain.VerifyPhoneMessageType ||
|
||||
template == domain.DomainClaimedMessageType ||
|
||||
template == domain.PasswordlessRegistrationMessageType
|
||||
template == domain.PasswordlessRegistrationMessageType ||
|
||||
template == domain.PasswordChangeMessageType
|
||||
}
|
||||
func isTitle(key string) bool {
|
||||
return key == domain.MessageTitle
|
||||
|
187
internal/query/projection/notification_policy.go
Normal file
187
internal/query/projection/notification_policy.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package projection
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/crdb"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
"github.com/zitadel/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
const (
|
||||
NotificationPolicyProjectionTable = "projections.notification_policies"
|
||||
|
||||
NotificationPolicyColumnID = "id"
|
||||
NotificationPolicyColumnCreationDate = "creation_date"
|
||||
NotificationPolicyColumnChangeDate = "change_date"
|
||||
NotificationPolicyColumnResourceOwner = "resource_owner"
|
||||
NotificationPolicyColumnInstanceID = "instance_id"
|
||||
NotificationPolicyColumnSequence = "sequence"
|
||||
NotificationPolicyColumnStateCol = "state"
|
||||
NotificationPolicyColumnIsDefault = "is_default"
|
||||
NotificationPolicyColumnPasswordChange = "password_change"
|
||||
NotificationPolicyColumnOwnerRemoved = "owner_removed"
|
||||
)
|
||||
|
||||
type notificationPolicyProjection struct {
|
||||
crdb.StatementHandler
|
||||
}
|
||||
|
||||
func newNotificationPolicyProjection(ctx context.Context, config crdb.StatementHandlerConfig) *notificationPolicyProjection {
|
||||
p := new(notificationPolicyProjection)
|
||||
config.ProjectionName = NotificationPolicyProjectionTable
|
||||
config.Reducers = p.reducers()
|
||||
config.InitCheck = crdb.NewTableCheck(
|
||||
crdb.NewTable([]*crdb.Column{
|
||||
crdb.NewColumn(NotificationPolicyColumnID, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(NotificationPolicyColumnCreationDate, crdb.ColumnTypeTimestamp),
|
||||
crdb.NewColumn(NotificationPolicyColumnChangeDate, crdb.ColumnTypeTimestamp),
|
||||
crdb.NewColumn(NotificationPolicyColumnResourceOwner, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(NotificationPolicyColumnInstanceID, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(NotificationPolicyColumnSequence, crdb.ColumnTypeInt64),
|
||||
crdb.NewColumn(NotificationPolicyColumnStateCol, crdb.ColumnTypeEnum),
|
||||
crdb.NewColumn(NotificationPolicyColumnIsDefault, crdb.ColumnTypeBool),
|
||||
crdb.NewColumn(NotificationPolicyColumnPasswordChange, crdb.ColumnTypeBool),
|
||||
crdb.NewColumn(NotificationPolicyColumnOwnerRemoved, crdb.ColumnTypeBool, crdb.Default(false)),
|
||||
},
|
||||
crdb.NewPrimaryKey(NotificationPolicyColumnInstanceID, NotificationPolicyColumnID),
|
||||
),
|
||||
)
|
||||
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *notificationPolicyProjection) reducers() []handler.AggregateReducer {
|
||||
return []handler.AggregateReducer{
|
||||
{
|
||||
Aggregate: org.AggregateType,
|
||||
EventRedusers: []handler.EventReducer{
|
||||
{
|
||||
Event: org.NotificationPolicyAddedEventType,
|
||||
Reduce: p.reduceAdded,
|
||||
},
|
||||
{
|
||||
Event: org.NotificationPolicyChangedEventType,
|
||||
Reduce: p.reduceChanged,
|
||||
},
|
||||
{
|
||||
Event: org.NotificationPolicyRemovedEventType,
|
||||
Reduce: p.reduceRemoved,
|
||||
},
|
||||
{
|
||||
Event: org.OrgRemovedEventType,
|
||||
Reduce: p.reduceOwnerRemoved,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Aggregate: instance.AggregateType,
|
||||
EventRedusers: []handler.EventReducer{
|
||||
{
|
||||
Event: instance.InstanceRemovedEventType,
|
||||
Reduce: reduceInstanceRemovedHelper(NotificationPolicyColumnInstanceID),
|
||||
},
|
||||
{
|
||||
Event: instance.NotificationPolicyAddedEventType,
|
||||
Reduce: p.reduceAdded,
|
||||
},
|
||||
{
|
||||
Event: instance.NotificationPolicyChangedEventType,
|
||||
Reduce: p.reduceChanged,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *notificationPolicyProjection) reduceAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||
var policyEvent policy.NotificationPolicyAddedEvent
|
||||
var isDefault bool
|
||||
switch e := event.(type) {
|
||||
case *org.NotificationPolicyAddedEvent:
|
||||
policyEvent = e.NotificationPolicyAddedEvent
|
||||
isDefault = false
|
||||
case *instance.NotificationPolicyAddedEvent:
|
||||
policyEvent = e.NotificationPolicyAddedEvent
|
||||
isDefault = true
|
||||
default:
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-x02s1m", "reduce.wrong.event.type %v", []eventstore.EventType{org.NotificationPolicyAddedEventType, instance.NotificationPolicyAddedEventType})
|
||||
}
|
||||
return crdb.NewCreateStatement(
|
||||
&policyEvent,
|
||||
[]handler.Column{
|
||||
handler.NewCol(NotificationPolicyColumnCreationDate, policyEvent.CreationDate()),
|
||||
handler.NewCol(NotificationPolicyColumnChangeDate, policyEvent.CreationDate()),
|
||||
handler.NewCol(NotificationPolicyColumnSequence, policyEvent.Sequence()),
|
||||
handler.NewCol(NotificationPolicyColumnID, policyEvent.Aggregate().ID),
|
||||
handler.NewCol(NotificationPolicyColumnStateCol, domain.PolicyStateActive),
|
||||
handler.NewCol(NotificationPolicyColumnPasswordChange, policyEvent.PasswordChange),
|
||||
handler.NewCol(NotificationPolicyColumnIsDefault, isDefault),
|
||||
handler.NewCol(NotificationPolicyColumnResourceOwner, policyEvent.Aggregate().ResourceOwner),
|
||||
handler.NewCol(NotificationPolicyColumnInstanceID, policyEvent.Aggregate().InstanceID),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *notificationPolicyProjection) reduceChanged(event eventstore.Event) (*handler.Statement, error) {
|
||||
var policyEvent policy.NotificationPolicyChangedEvent
|
||||
switch e := event.(type) {
|
||||
case *org.NotificationPolicyChangedEvent:
|
||||
policyEvent = e.NotificationPolicyChangedEvent
|
||||
case *instance.NotificationPolicyChangedEvent:
|
||||
policyEvent = e.NotificationPolicyChangedEvent
|
||||
default:
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-psom2h19", "reduce.wrong.event.type %v", []eventstore.EventType{org.NotificationPolicyChangedEventType, instance.NotificationPolicyChangedEventType})
|
||||
}
|
||||
cols := []handler.Column{
|
||||
handler.NewCol(NotificationPolicyColumnChangeDate, policyEvent.CreationDate()),
|
||||
handler.NewCol(NotificationPolicyColumnSequence, policyEvent.Sequence()),
|
||||
}
|
||||
if policyEvent.PasswordChange != nil {
|
||||
cols = append(cols, handler.NewCol(NotificationPolicyColumnPasswordChange, *policyEvent.PasswordChange))
|
||||
}
|
||||
return crdb.NewUpdateStatement(
|
||||
&policyEvent,
|
||||
cols,
|
||||
[]handler.Condition{
|
||||
handler.NewCond(NotificationPolicyColumnID, policyEvent.Aggregate().ID),
|
||||
handler.NewCond(NotificationPolicyColumnInstanceID, policyEvent.Aggregate().InstanceID),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *notificationPolicyProjection) reduceRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||
policyEvent, ok := event.(*org.NotificationPolicyRemovedEvent)
|
||||
if !ok {
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-Po2iso2", "reduce.wrong.event.type %s", org.NotificationPolicyRemovedEventType)
|
||||
}
|
||||
return crdb.NewDeleteStatement(
|
||||
policyEvent,
|
||||
[]handler.Condition{
|
||||
handler.NewCond(NotificationPolicyColumnID, policyEvent.Aggregate().ID),
|
||||
handler.NewCond(NotificationPolicyColumnInstanceID, policyEvent.Aggregate().InstanceID),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (p *notificationPolicyProjection) reduceOwnerRemoved(event eventstore.Event) (*handler.Statement, error) {
|
||||
e, ok := event.(*org.OrgRemovedEvent)
|
||||
if !ok {
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-poxi9a", "reduce.wrong.event.type %s", org.OrgRemovedEventType)
|
||||
}
|
||||
|
||||
return crdb.NewUpdateStatement(
|
||||
e,
|
||||
[]handler.Column{
|
||||
handler.NewCol(DomainPolicyChangeDateCol, e.CreationDate()),
|
||||
handler.NewCol(DomainPolicySequenceCol, e.Sequence()),
|
||||
handler.NewCol(DomainPolicyOwnerRemovedCol, true),
|
||||
},
|
||||
[]handler.Condition{
|
||||
handler.NewCond(DomainPolicyInstanceIDCol, e.Aggregate().InstanceID),
|
||||
handler.NewCond(DomainPolicyResourceOwnerCol, e.Aggregate().ID),
|
||||
},
|
||||
), nil
|
||||
}
|
258
internal/query/projection/notification_policy_test.go
Normal file
258
internal/query/projection/notification_policy_test.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package projection
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func TestNotificationPolicyProjection_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: "org reduceAdded",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(org.NotificationPolicyAddedEventType),
|
||||
org.AggregateType,
|
||||
[]byte(`{
|
||||
"passwordChange": true
|
||||
}`),
|
||||
), org.NotificationPolicyAddedEventMapper),
|
||||
},
|
||||
reduce: (¬ificationPolicyProjection{}).reduceAdded,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("org"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.notification_policies (creation_date, change_date, sequence, id, state, password_change, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"agg-id",
|
||||
domain.PolicyStateActive,
|
||||
true,
|
||||
false,
|
||||
"ro-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org reduceChanged",
|
||||
reduce: (¬ificationPolicyProjection{}).reduceChanged,
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(org.NotificationPolicyChangedEventType),
|
||||
org.AggregateType,
|
||||
[]byte(`{
|
||||
"passwordChange": true
|
||||
}`),
|
||||
), org.NotificationPolicyChangedEventMapper),
|
||||
},
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("org"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.notification_policies SET (change_date, sequence, password_change) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
true,
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org reduceRemoved",
|
||||
reduce: (¬ificationPolicyProjection{}).reduceRemoved,
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(org.NotificationPolicyRemovedEventType),
|
||||
org.AggregateType,
|
||||
nil,
|
||||
), org.NotificationPolicyRemovedEventMapper),
|
||||
},
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("org"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.notification_policies WHERE (id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "instance reduceInstanceRemoved",
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(instance.InstanceRemovedEventType),
|
||||
instance.AggregateType,
|
||||
nil,
|
||||
), instance.InstanceRemovedEventMapper),
|
||||
},
|
||||
reduce: reduceInstanceRemovedHelper(NotificationPolicyColumnInstanceID),
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("instance"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.notification_policies WHERE (instance_id = $1)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "instance reduceAdded",
|
||||
reduce: (¬ificationPolicyProjection{}).reduceAdded,
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(instance.NotificationPolicyAddedEventType),
|
||||
instance.AggregateType,
|
||||
[]byte(`{
|
||||
"passwordChange": true
|
||||
}`),
|
||||
), instance.NotificationPolicyAddedEventMapper),
|
||||
},
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("instance"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.notification_policies (creation_date, change_date, sequence, id, state, password_change, is_default, resource_owner, instance_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"agg-id",
|
||||
domain.PolicyStateActive,
|
||||
true,
|
||||
true,
|
||||
"ro-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "instance reduceChanged",
|
||||
reduce: (¬ificationPolicyProjection{}).reduceChanged,
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(instance.NotificationPolicyChangedEventType),
|
||||
instance.AggregateType,
|
||||
[]byte(`{
|
||||
"passwordChange": true
|
||||
}`),
|
||||
), instance.NotificationPolicyChangedEventMapper),
|
||||
},
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("instance"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.notification_policies SET (change_date, sequence, password_change) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
true,
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org.reduceOwnerRemoved",
|
||||
reduce: (¬ificationPolicyProjection{}).reduceOwnerRemoved,
|
||||
args: args{
|
||||
event: getEvent(testEvent(
|
||||
repository.EventType(org.OrgRemovedEventType),
|
||||
org.AggregateType,
|
||||
nil,
|
||||
), org.OrgRemovedEventMapper),
|
||||
},
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("org"),
|
||||
sequence: 15,
|
||||
previousSequence: 10,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.notification_policies SET (change_date, sequence, owner_removed) = ($1, $2, $3) WHERE (instance_id = $4) AND (resource_owner = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
true,
|
||||
"instance-id",
|
||||
"agg-id",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
event := baseEvent(t)
|
||||
got, err := tt.reduce(event)
|
||||
|
||||
if ok := errors.IsErrorInvalidArgument(err); !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, NotificationPolicyProjectionTable, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
@@ -60,6 +60,7 @@ var (
|
||||
DebugNotificationProviderProjection *debugNotificationProviderProjection
|
||||
KeyProjection *keyProjection
|
||||
SecurityPolicyProjection *securityPolicyProjection
|
||||
NotificationPolicyProjection *notificationPolicyProjection
|
||||
NotificationsProjection interface{}
|
||||
)
|
||||
|
||||
@@ -133,6 +134,7 @@ func Create(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, c
|
||||
DebugNotificationProviderProjection = newDebugNotificationProviderProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["debug_notification_provider"]))
|
||||
KeyProjection = newKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyEncryptionAlgorithm, certEncryptionAlgorithm)
|
||||
SecurityPolicyProjection = newSecurityPolicyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["security_policies"]))
|
||||
NotificationPolicyProjection = newNotificationPolicyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["notification_policies"]))
|
||||
newProjectionsList()
|
||||
return nil
|
||||
}
|
||||
@@ -224,5 +226,6 @@ func newProjectionsList() {
|
||||
DebugNotificationProviderProjection,
|
||||
KeyProjection,
|
||||
SecurityPolicyProjection,
|
||||
NotificationPolicyProjection,
|
||||
}
|
||||
}
|
||||
|
@@ -89,5 +89,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(AggregateType, InstanceDomainRemovedEventType, DomainRemovedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, InstanceAddedEventType, InstanceAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, InstanceChangedEventType, InstanceChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, InstanceRemovedEventType, InstanceRemovedEventMapper)
|
||||
RegisterFilterEventMapper(AggregateType, InstanceRemovedEventType, InstanceRemovedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, NotificationPolicyAddedEventType, NotificationPolicyAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, NotificationPolicyChangedEventType, NotificationPolicyChangedEventMapper)
|
||||
}
|
||||
|
73
internal/repository/instance/policy_notification.go
Normal file
73
internal/repository/instance/policy_notification.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package instance
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||
"github.com/zitadel/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
const (
|
||||
NotificationPolicyAddedEventType = instanceEventTypePrefix + policy.NotificationPolicyAddedEventType
|
||||
NotificationPolicyChangedEventType = instanceEventTypePrefix + policy.NotificationPolicyChangedEventType
|
||||
)
|
||||
|
||||
type NotificationPolicyAddedEvent struct {
|
||||
policy.NotificationPolicyAddedEvent
|
||||
}
|
||||
|
||||
func NewNotificationPolicyAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
passwordChange bool,
|
||||
) *NotificationPolicyAddedEvent {
|
||||
return &NotificationPolicyAddedEvent{
|
||||
NotificationPolicyAddedEvent: *policy.NewNotificationPolicyAddedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
NotificationPolicyAddedEventType),
|
||||
passwordChange),
|
||||
}
|
||||
}
|
||||
|
||||
func NotificationPolicyAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e, err := policy.NotificationPolicyAddedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &NotificationPolicyAddedEvent{NotificationPolicyAddedEvent: *e.(*policy.NotificationPolicyAddedEvent)}, nil
|
||||
}
|
||||
|
||||
type NotificationPolicyChangedEvent struct {
|
||||
policy.NotificationPolicyChangedEvent
|
||||
}
|
||||
|
||||
func NewNotificationPolicyChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
changes []policy.NotificationPolicyChanges,
|
||||
) (*NotificationPolicyChangedEvent, error) {
|
||||
changedEvent, err := policy.NewNotificationPolicyChangedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
NotificationPolicyChangedEventType),
|
||||
changes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &NotificationPolicyChangedEvent{NotificationPolicyChangedEvent: *changedEvent}, nil
|
||||
}
|
||||
|
||||
func NotificationPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e, err := policy.NotificationPolicyChangedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &NotificationPolicyChangedEvent{NotificationPolicyChangedEvent: *e.(*policy.NotificationPolicyChangedEvent)}, nil
|
||||
}
|
@@ -83,5 +83,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(AggregateType, FlowClearedEventType, FlowClearedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, MetadataSetType, MetadataSetEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, MetadataRemovedType, MetadataRemovedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, MetadataRemovedAllType, MetadataRemovedAllEventMapper)
|
||||
RegisterFilterEventMapper(AggregateType, MetadataRemovedAllType, MetadataRemovedAllEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, NotificationPolicyAddedEventType, NotificationPolicyAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, NotificationPolicyChangedEventType, NotificationPolicyChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, NotificationPolicyRemovedEventType, NotificationPolicyRemovedEventMapper)
|
||||
}
|
||||
|
102
internal/repository/org/policy_notification.go
Normal file
102
internal/repository/org/policy_notification.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package org
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||
"github.com/zitadel/zitadel/internal/repository/policy"
|
||||
)
|
||||
|
||||
var (
|
||||
NotificationPolicyAddedEventType = orgEventTypePrefix + policy.NotificationPolicyAddedEventType
|
||||
NotificationPolicyChangedEventType = orgEventTypePrefix + policy.NotificationPolicyChangedEventType
|
||||
NotificationPolicyRemovedEventType = orgEventTypePrefix + policy.NotificationPolicyRemovedEventType
|
||||
)
|
||||
|
||||
type NotificationPolicyAddedEvent struct {
|
||||
policy.NotificationPolicyAddedEvent
|
||||
}
|
||||
|
||||
func NewNotificationPolicyAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
passwordChange bool,
|
||||
) *NotificationPolicyAddedEvent {
|
||||
return &NotificationPolicyAddedEvent{
|
||||
NotificationPolicyAddedEvent: *policy.NewNotificationPolicyAddedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
NotificationPolicyAddedEventType),
|
||||
passwordChange,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func NotificationPolicyAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e, err := policy.NotificationPolicyAddedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &NotificationPolicyAddedEvent{NotificationPolicyAddedEvent: *e.(*policy.NotificationPolicyAddedEvent)}, nil
|
||||
}
|
||||
|
||||
type NotificationPolicyChangedEvent struct {
|
||||
policy.NotificationPolicyChangedEvent
|
||||
}
|
||||
|
||||
func NewNotificationPolicyChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
changes []policy.NotificationPolicyChanges,
|
||||
) (*NotificationPolicyChangedEvent, error) {
|
||||
changedEvent, err := policy.NewNotificationPolicyChangedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
NotificationPolicyChangedEventType),
|
||||
changes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &NotificationPolicyChangedEvent{NotificationPolicyChangedEvent: *changedEvent}, nil
|
||||
}
|
||||
|
||||
func NotificationPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e, err := policy.NotificationPolicyChangedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &NotificationPolicyChangedEvent{NotificationPolicyChangedEvent: *e.(*policy.NotificationPolicyChangedEvent)}, nil
|
||||
}
|
||||
|
||||
type NotificationPolicyRemovedEvent struct {
|
||||
policy.NotificationPolicyRemovedEvent
|
||||
}
|
||||
|
||||
func NewNotificationPolicyRemovedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
) *NotificationPolicyRemovedEvent {
|
||||
return &NotificationPolicyRemovedEvent{
|
||||
NotificationPolicyRemovedEvent: *policy.NewNotificationPolicyRemovedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
NotificationPolicyRemovedEventType),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func NotificationPolicyRemovedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e, err := policy.NotificationPolicyRemovedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &NotificationPolicyRemovedEvent{NotificationPolicyRemovedEvent: *e.(*policy.NotificationPolicyRemovedEvent)}, nil
|
||||
}
|
127
internal/repository/policy/policy_notification.go
Normal file
127
internal/repository/policy/policy_notification.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/errors"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
NotificationPolicyAddedEventType = "policy.notification.added"
|
||||
NotificationPolicyChangedEventType = "policy.notification.changed"
|
||||
NotificationPolicyRemovedEventType = "policy.notification.removed"
|
||||
)
|
||||
|
||||
type NotificationPolicyAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
PasswordChange bool `json:"passwordChange,omitempty"`
|
||||
}
|
||||
|
||||
func (e *NotificationPolicyAddedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *NotificationPolicyAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewNotificationPolicyAddedEvent(
|
||||
base *eventstore.BaseEvent,
|
||||
passwordChange bool,
|
||||
) *NotificationPolicyAddedEvent {
|
||||
return &NotificationPolicyAddedEvent{
|
||||
BaseEvent: *base,
|
||||
PasswordChange: passwordChange,
|
||||
}
|
||||
}
|
||||
|
||||
func NotificationPolicyAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e := &NotificationPolicyAddedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "POLIC-0sp2nios", "unable to unmarshal policy")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type NotificationPolicyChangedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
PasswordChange *bool `json:"passwordChange,omitempty"`
|
||||
}
|
||||
|
||||
func (e *NotificationPolicyChangedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *NotificationPolicyChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewNotificationPolicyChangedEvent(
|
||||
base *eventstore.BaseEvent,
|
||||
changes []NotificationPolicyChanges,
|
||||
) (*NotificationPolicyChangedEvent, error) {
|
||||
if len(changes) == 0 {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "POLICY-09sp2m", "Errors.NoChangesFound")
|
||||
}
|
||||
changeEvent := &NotificationPolicyChangedEvent{
|
||||
BaseEvent: *base,
|
||||
}
|
||||
for _, change := range changes {
|
||||
change(changeEvent)
|
||||
}
|
||||
return changeEvent, nil
|
||||
}
|
||||
|
||||
type NotificationPolicyChanges func(*NotificationPolicyChangedEvent)
|
||||
|
||||
func ChangePasswordChange(passwordChange bool) func(*NotificationPolicyChangedEvent) {
|
||||
return func(e *NotificationPolicyChangedEvent) {
|
||||
e.PasswordChange = &passwordChange
|
||||
}
|
||||
}
|
||||
|
||||
func NotificationPolicyChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
e := &NotificationPolicyChangedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "POLIC-09s2oss", "unable to unmarshal policy")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type NotificationPolicyRemovedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *NotificationPolicyRemovedEvent) Data() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *NotificationPolicyRemovedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewNotificationPolicyRemovedEvent(base *eventstore.BaseEvent) *NotificationPolicyRemovedEvent {
|
||||
return &NotificationPolicyRemovedEvent{
|
||||
BaseEvent: *base,
|
||||
}
|
||||
}
|
||||
|
||||
func NotificationPolicyRemovedEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
return &NotificationPolicyRemovedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}, nil
|
||||
}
|
@@ -59,6 +59,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(AggregateType, HumanPasswordChangedType, HumanPasswordChangedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, HumanPasswordCodeAddedType, HumanPasswordCodeAddedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, HumanPasswordCodeSentType, HumanPasswordCodeSentEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, HumanPasswordChangeSentType, HumanPasswordChangeSentEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, HumanPasswordCheckSucceededType, HumanPasswordCheckSucceededEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, HumanPasswordCheckFailedType, HumanPasswordCheckFailedEventMapper).
|
||||
RegisterFilterEventMapper(AggregateType, UserIDPLinkAddedType, UserIDPLinkAddedEventMapper).
|
||||
|
@@ -16,6 +16,7 @@ import (
|
||||
const (
|
||||
passwordEventPrefix = humanEventPrefix + "password."
|
||||
HumanPasswordChangedType = passwordEventPrefix + "changed"
|
||||
HumanPasswordChangeSentType = passwordEventPrefix + "change.sent"
|
||||
HumanPasswordCodeAddedType = passwordEventPrefix + "code.added"
|
||||
HumanPasswordCodeSentType = passwordEventPrefix + "code.sent"
|
||||
HumanPasswordCheckSucceededType = passwordEventPrefix + "check.succeeded"
|
||||
@@ -144,6 +145,34 @@ func HumanPasswordCodeSentEventMapper(event *repository.Event) (eventstore.Event
|
||||
}, nil
|
||||
}
|
||||
|
||||
type HumanPasswordChangeSentEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
}
|
||||
|
||||
func (e *HumanPasswordChangeSentEvent) Data() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *HumanPasswordChangeSentEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewHumanPasswordChangeSentEvent(ctx context.Context, aggregate *eventstore.Aggregate) *HumanPasswordChangeSentEvent {
|
||||
return &HumanPasswordChangeSentEvent{
|
||||
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
HumanPasswordChangeSentType,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func HumanPasswordChangeSentEventMapper(event *repository.Event) (eventstore.Event, error) {
|
||||
return &HumanPasswordChangeSentEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type HumanPasswordCheckSucceededEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
*AuthRequestInfo
|
||||
|
@@ -225,6 +225,10 @@ Errors:
|
||||
Empty: Org IAM Policy ist leer
|
||||
NotExisting: Org IAM Policy existiert nicht
|
||||
AlreadyExists: Org IAM Policy existiert bereits
|
||||
NotificationPolicy:
|
||||
NotFound: Notification Policy konnte nicht gefunden werden
|
||||
NotChanged: Notification Policy wurde nicht verändert
|
||||
AlreadyExists: Notification Policy existiert bereits
|
||||
Project:
|
||||
ProjectIDMissing: Project ID fehlt
|
||||
AlreadyExists: Project existiert bereits auf der Organisation
|
||||
@@ -351,6 +355,10 @@ Errors:
|
||||
AlreadyExists: Default Org IAM Policy existiert bereits
|
||||
Empty: Default Org IAM Policy leer
|
||||
NotChanged: Default Org IAM Policy wurde nicht verändert
|
||||
NotificationPolicy:
|
||||
NotFound: Default Notification Policy konnte nicht gefunden werden
|
||||
NotChanged: Default Notification Policy wurde nicht verändert
|
||||
AlreadyExists: Default Notification Policy existiert bereits
|
||||
Policy:
|
||||
AlreadyExists: Policy existiert bereits
|
||||
Label:
|
||||
@@ -750,6 +758,10 @@ EventTypes:
|
||||
added: Passwortaussperrrichtlinie hinzugefügt
|
||||
changed: Passwortaussperrrichtlinie geändert
|
||||
removed: Passwortaussperrrichtlinie gelöscht
|
||||
notification:
|
||||
added: Notifikation Richtlinie hinzugefügt
|
||||
changed: Notifikation Richtlinie geändert
|
||||
removed: Notifikation Richtlinie entfernt
|
||||
flow:
|
||||
trigger_actions:
|
||||
set: Aktionen festgelegt
|
||||
@@ -980,7 +992,7 @@ EventTypes:
|
||||
removed: Instanzmitglied gelöscht
|
||||
cascade:
|
||||
removed: Instanzmitglied kaskadierend gelöscht
|
||||
notification:
|
||||
notification:
|
||||
provider:
|
||||
debug:
|
||||
fileadded: Datei zu Debug Notification Provider hinzugefügt
|
||||
@@ -1047,7 +1059,7 @@ EventTypes:
|
||||
changed: Datenschutzrichtlinie geändert
|
||||
security:
|
||||
set: Sicherheitsrichtlinie gesetzt
|
||||
|
||||
|
||||
removed: Instanz gelöscht
|
||||
secret:
|
||||
generator:
|
||||
|
@@ -225,6 +225,10 @@ Errors:
|
||||
Empty: Org IAM Policy is empty
|
||||
NotExisting: Org IAM Policy doesn't exist
|
||||
AlreadyExists: Org IAM Policy already exists
|
||||
NotificationPolicy:
|
||||
NotFound: Notification Policy not found
|
||||
NotChanged: Notification Policy not changed
|
||||
AlreadyExists: Notification Policy already exists
|
||||
Project:
|
||||
ProjectIDMissing: Project Id missing
|
||||
AlreadyExists: Project already exists on organization
|
||||
@@ -351,6 +355,10 @@ Errors:
|
||||
NotExisting: Org IAM Policy not existing
|
||||
AlreadyExists: Org IAM Policy already exists
|
||||
NotChanged: Org IAM Policy has not been changed
|
||||
NotificationPolicy:
|
||||
NotFound: Default Notification Policy not found
|
||||
NotChanged: Default Notification Policy not changed
|
||||
AlreadyExists: Default Notification Policy already exists
|
||||
Policy:
|
||||
AlreadyExists: Policy already exists
|
||||
Label:
|
||||
@@ -750,6 +758,10 @@ EventTypes:
|
||||
added: Lockout policy added
|
||||
changed: Lockout policy changed
|
||||
removed: Lockout policy removed
|
||||
notification:
|
||||
added: Notification policy added
|
||||
changed: Notification policy changed
|
||||
removed: Notification policy removed
|
||||
flow:
|
||||
trigger_actions:
|
||||
set: Action set
|
||||
@@ -1047,7 +1059,7 @@ EventTypes:
|
||||
changed: Privacy policy changed
|
||||
security:
|
||||
set: Security policy set
|
||||
|
||||
|
||||
removed: Instance removed
|
||||
secret:
|
||||
generator:
|
||||
|
@@ -225,6 +225,10 @@ Errors:
|
||||
Empty: La politique IAM d'Org est vide
|
||||
NotExisting: La politique Org IAM n'existe pas
|
||||
AlreadyExists: La politique IAM d'Org existe déjà
|
||||
NotificationPolicy:
|
||||
NotFound: La politique notification n'a pas été trouvée
|
||||
NotChanged: La politique notification n'a pas été modifiée
|
||||
AlreadyExists: La politique notification existe déjà
|
||||
Project:
|
||||
ProjectIDMissing: Id de projet manquant
|
||||
AlreadyExists: Le projet existe déjà dans l'organisation
|
||||
@@ -351,6 +355,10 @@ Errors:
|
||||
NotExisting: La politique IAM d'Org n'existe pas
|
||||
AlreadyExists: La politique IAM d'Org existe déjà
|
||||
NotChanged: La politique IAM d'Org n'a pas été modifiée
|
||||
NotificationPolicy:
|
||||
NotFound: La politique de notification par défaut n'a pas été trouvée
|
||||
NotChanged: La politique de notification par défaut n'a pas été modifiée
|
||||
AlreadyExists: La ppolitique de notification par défaut existe déjà
|
||||
Policy:
|
||||
AlreadyExists: La politique existe déjà
|
||||
Label:
|
||||
@@ -725,6 +733,10 @@ EventTypes:
|
||||
added: Politique de confidentialité et CGU ajoutés
|
||||
changed: Politique de confidentialité et CGU modifiées
|
||||
removed: Politique de confidentialité et conditions d'utilisation supprimées
|
||||
notification:
|
||||
added: Politique de notification ajoutée
|
||||
changed: Politique de notification modifiée
|
||||
removed: Politique de notification supprimée
|
||||
flow:
|
||||
trigger_actions:
|
||||
set: Action set
|
||||
|
@@ -225,6 +225,10 @@ Errors:
|
||||
Empty: Mancano le impostazioni Org IAM
|
||||
NotExisting: Impostazioni Org IAM non esistenti
|
||||
AlreadyExists: Impostazioni Org IAM già esistenti
|
||||
NotificationPolicy:
|
||||
NotFound: Impostazioni di notifica non trovate
|
||||
NotChanged: Impostazioni di notifica non è stato cambiato
|
||||
AlreadyExists: Impostazioni di notifica già esistente
|
||||
Project:
|
||||
ProjectIDMissing: ID del progetto mancante
|
||||
AlreadyExists: Il progetto è già stato creato nell'organizzazione
|
||||
@@ -351,6 +355,10 @@ Errors:
|
||||
NotExisting: Impostazioni Org IAM non esistenti
|
||||
AlreadyExists: Impostazioni Org IAM già esistenti
|
||||
NotChanged: Impostazioni Org IAM non sono state cambiate
|
||||
NotificationPolicy:
|
||||
NotFound: Impostazioni di notifica predefinite non trovate
|
||||
NotChanged: Impostazioni di notifica predefinite non è stato cambiato
|
||||
AlreadyExists: Impostazioni di notifica predefinite già esistente
|
||||
Policy:
|
||||
AlreadyExists: Impostazioni già esistenti
|
||||
Label:
|
||||
@@ -725,6 +733,10 @@ EventTypes:
|
||||
added: Informativa sulla privacy e termini e condizioni aggiunti
|
||||
changed: Informativa sulla privacy e termini e condizioni cambiati
|
||||
removed: Informativa sulla privacy e termini e condizioni rimossi
|
||||
notification:
|
||||
added: Impostazione di notifica creata
|
||||
changed: Impostazione di notifica cambiata
|
||||
removed: Impostazione di notifica rimossa
|
||||
flow:
|
||||
trigger_actions:
|
||||
set: azioni salvate
|
||||
|
@@ -225,6 +225,10 @@ Errors:
|
||||
Empty: 组织 IAM 策略为空
|
||||
NotExisting: 组织 IAM 策略不存在
|
||||
AlreadyExists: 组织 IAM 策略已存在
|
||||
NotificationPolicy:
|
||||
NotFound: 未找到通知政策
|
||||
NotChanged: 通知政策没有改变
|
||||
AlreadyExists: 已经存在的通知政策
|
||||
Project:
|
||||
ProjectIDMissing: P缺少项目 ID
|
||||
AlreadyExists: 项目以存在于组织中
|
||||
@@ -351,6 +355,10 @@ Errors:
|
||||
NotExisting: 组织 IAM 策略不存在
|
||||
AlreadyExists: 组织 IAM 策略已存在
|
||||
NotChanged: 组织 IAM 策略未更改
|
||||
NotificationPolicy:
|
||||
NotFound: 没有找到默认的通知政策
|
||||
NotChanged: 默认的通知政策没有改变
|
||||
AlreadyExists: 默认的通知政策已经存在
|
||||
Policy:
|
||||
AlreadyExists: 策略已存在
|
||||
Label:
|
||||
@@ -715,6 +723,10 @@ EventTypes:
|
||||
added: 添加隐私政策和服务条款
|
||||
changed: 更改隐私政策和服务条款
|
||||
removed: 删除隐私政策和服务条款
|
||||
notification:
|
||||
added: 增加了通知政策
|
||||
changed: 通知政策改变
|
||||
removed: 删除了通知政策
|
||||
flow:
|
||||
trigger_actions:
|
||||
set: 设置动作
|
||||
|
Reference in New Issue
Block a user