feat: e-mail templates (#1158)

* View definition added

* Get templates and texts from the database.

* Fill in texts in templates

* Fill in texts in templates

* Client API added

* Weekly backup

* Weekly backup

* Daily backup

* Weekly backup

* Tests added

* Corrections from merge branch

* Fixes from pull request review
This commit is contained in:
Michael Waeger 2021-01-18 14:17:22 +01:00 committed by GitHub
parent e7540e5e05
commit f2a32871a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 5325 additions and 155 deletions

File diff suppressed because one or more lines are too long

1
go.mod
View File

@ -30,6 +30,7 @@ require (
github.com/gorilla/schema v1.2.0 github.com/gorilla/schema v1.2.0
github.com/gorilla/securecookie v1.1.1 github.com/gorilla/securecookie v1.1.1
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 github.com/grpc-ecosystem/go-grpc-middleware v1.2.2
github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1
github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/huandu/xstrings v1.3.2 // indirect github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.11 // indirect github.com/imdario/mergo v0.3.11 // indirect

3
go.sum
View File

@ -333,6 +333,7 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1/go.mod h1:oVMjMN64nzEcepv1kdZKgx1qNYt4Ro0Gqefiq2JWdis=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -1065,6 +1066,7 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201103154000-415bd0cd5df6 h1:rMoZiLTOobSD3eg30lPMcFkBFNSyKUQQIQlw/hsAXME= google.golang.org/genproto v0.0.0-20201103154000-415bd0cd5df6 h1:rMoZiLTOobSD3eg30lPMcFkBFNSyKUQQIQlw/hsAXME=
google.golang.org/genproto v0.0.0-20201103154000-415bd0cd5df6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201103154000-415bd0cd5df6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@ -1096,6 +1098,7 @@ google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View File

@ -176,66 +176,6 @@ func (repo *IAMRepository) SearchIDPConfigs(ctx context.Context, request *iam_mo
return result, nil return result, nil
} }
func (repo *IAMRepository) GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) {
policy, viewErr := repo.View.LabelPolicyByAggregateID(repo.SystemDefaults.IamID)
if viewErr != nil && !caos_errs.IsNotFound(viewErr) {
return nil, viewErr
}
if caos_errs.IsNotFound(viewErr) {
policy = new(iam_es_model.LabelPolicyView)
}
events, esErr := repo.IAMEventstore.IAMEventsByID(ctx, repo.SystemDefaults.IamID, policy.Sequence)
if caos_errs.IsNotFound(viewErr) && len(events) == 0 {
return nil, caos_errs.ThrowNotFound(nil, "EVENT-4bM0s", "Errors.IAM.LabelPolicy.NotFound")
}
if esErr != nil {
logging.Log("EVENT-3M0xs").WithError(esErr).Debug("error retrieving new events")
return iam_es_model.LabelPolicyViewToModel(policy), nil
}
policyCopy := *policy
for _, event := range events {
if err := policyCopy.AppendEvent(event); err != nil {
return iam_es_model.LabelPolicyViewToModel(policy), nil
}
}
return iam_es_model.LabelPolicyViewToModel(policy), nil
}
func (repo *IAMRepository) AddDefaultLabelPolicy(ctx context.Context, policy *iam_model.LabelPolicy) (*iam_model.LabelPolicy, error) {
policy.AggregateID = repo.SystemDefaults.IamID
return repo.IAMEventstore.AddLabelPolicy(ctx, policy)
}
func (repo *IAMRepository) ChangeDefaultLabelPolicy(ctx context.Context, policy *iam_model.LabelPolicy) (*iam_model.LabelPolicy, error) {
policy.AggregateID = repo.SystemDefaults.IamID
return repo.IAMEventstore.ChangeLabelPolicy(ctx, policy)
}
func (repo *IAMRepository) GetDefaultLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) {
policy, viewErr := repo.View.LoginPolicyByAggregateID(repo.SystemDefaults.IamID)
if viewErr != nil && !caos_errs.IsNotFound(viewErr) {
return nil, viewErr
}
if caos_errs.IsNotFound(viewErr) {
policy = new(iam_es_model.LoginPolicyView)
}
events, esErr := repo.IAMEventstore.IAMEventsByID(ctx, repo.SystemDefaults.IamID, policy.Sequence)
if caos_errs.IsNotFound(viewErr) && len(events) == 0 {
return nil, caos_errs.ThrowNotFound(nil, "EVENT-cmO9s", "Errors.IAM.LoginPolicy.NotFound")
}
if esErr != nil {
logging.Log("EVENT-2Mi8s").WithError(esErr).Debug("error retrieving new events")
return iam_es_model.LoginPolicyViewToModel(policy), nil
}
policyCopy := *policy
for _, event := range events {
if err := policyCopy.AppendEvent(event); err != nil {
return iam_es_model.LoginPolicyViewToModel(policy), nil
}
}
return iam_es_model.LoginPolicyViewToModel(policy), nil
}
func (repo *IAMRepository) AddDefaultLoginPolicy(ctx context.Context, policy *iam_model.LoginPolicy) (*iam_model.LoginPolicy, error) { func (repo *IAMRepository) AddDefaultLoginPolicy(ctx context.Context, policy *iam_model.LoginPolicy) (*iam_model.LoginPolicy, error) {
policy.AggregateID = repo.SystemDefaults.IamID policy.AggregateID = repo.SystemDefaults.IamID
return repo.IAMEventstore.AddLoginPolicy(ctx, policy) return repo.IAMEventstore.AddLoginPolicy(ctx, policy)
@ -475,3 +415,95 @@ func (repo *IAMRepository) ChangeDefaultOrgIAMPolicy(ctx context.Context, policy
policy.AggregateID = repo.SystemDefaults.IamID policy.AggregateID = repo.SystemDefaults.IamID
return repo.IAMEventstore.ChangeOrgIAMPolicy(ctx, policy) return repo.IAMEventstore.ChangeOrgIAMPolicy(ctx, policy)
} }
func (repo *IAMRepository) GetDefaultLabelPolicy(ctx context.Context) (*iam_model.LabelPolicyView, error) {
policy, err := repo.View.LabelPolicyByAggregateID(repo.SystemDefaults.IamID)
if err != nil {
return nil, err
}
return iam_es_model.LabelPolicyViewToModel(policy), err
}
func (repo *IAMRepository) AddDefaultLabelPolicy(ctx context.Context, policy *iam_model.LabelPolicy) (*iam_model.LabelPolicy, error) {
policy.AggregateID = repo.SystemDefaults.IamID
return repo.IAMEventstore.AddLabelPolicy(ctx, policy)
}
func (repo *IAMRepository) ChangeDefaultLabelPolicy(ctx context.Context, policy *iam_model.LabelPolicy) (*iam_model.LabelPolicy, error) {
policy.AggregateID = repo.SystemDefaults.IamID
return repo.IAMEventstore.ChangeLabelPolicy(ctx, policy)
}
func (repo *IAMRepository) GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) {
template, err := repo.View.MailTemplateByAggregateID(repo.SystemDefaults.IamID)
if err != nil {
return nil, err
}
return iam_es_model.MailTemplateViewToModel(template), err
}
func (repo *IAMRepository) AddDefaultMailTemplate(ctx context.Context, template *iam_model.MailTemplate) (*iam_model.MailTemplate, error) {
template.AggregateID = repo.SystemDefaults.IamID
return repo.IAMEventstore.AddMailTemplate(ctx, template)
}
func (repo *IAMRepository) ChangeDefaultMailTemplate(ctx context.Context, template *iam_model.MailTemplate) (*iam_model.MailTemplate, error) {
template.AggregateID = repo.SystemDefaults.IamID
return repo.IAMEventstore.ChangeMailTemplate(ctx, template)
}
func (repo *IAMRepository) SearchIAMMembersx(ctx context.Context, request *iam_model.IAMMemberSearchRequest) (*iam_model.IAMMemberSearchResponse, error) {
request.EnsureLimit(repo.SearchLimit)
sequence, err := repo.View.GetLatestIAMMemberSequence("")
logging.Log("EVENT-Slkci").OnError(err).Warn("could not read latest iam sequence")
members, count, err := repo.View.SearchIAMMembers(request)
if err != nil {
return nil, err
}
result := &iam_model.IAMMemberSearchResponse{
Offset: request.Offset,
Limit: request.Limit,
TotalResult: count,
Result: iam_es_model.IAMMembersToModel(members),
}
if err == nil {
result.Sequence = sequence.CurrentSequence
result.Timestamp = result.Timestamp
}
return result, nil
}
func (repo *IAMRepository) GetDefaultMailTexts(ctx context.Context) (*iam_model.MailTextsView, error) {
text, err := repo.View.MailTexts(repo.SystemDefaults.IamID)
if err != nil {
return nil, err
}
return iam_es_model.MailTextsViewToModel(text, true), err
}
func (repo *IAMRepository) GetDefaultMailText(ctx context.Context, textType string, language string) (*iam_model.MailTextView, error) {
text, err := repo.View.MailTextByIDs(repo.SystemDefaults.IamID, textType, language)
if err != nil {
return nil, err
}
text.Default = true
return iam_es_model.MailTextViewToModel(text), err
}
func (repo *IAMRepository) AddDefaultMailText(ctx context.Context, text *iam_model.MailText) (*iam_model.MailText, error) {
text.AggregateID = repo.SystemDefaults.IamID
return repo.IAMEventstore.AddMailText(ctx, text)
}
func (repo *IAMRepository) ChangeDefaultMailText(ctx context.Context, text *iam_model.MailText) (*iam_model.MailText, error) {
text.AggregateID = repo.SystemDefaults.IamID
return repo.IAMEventstore.ChangeMailText(ctx, text)
}
func (repo *IAMRepository) GetDefaultLoginPolicy(ctx context.Context) (*iam_model.LoginPolicyView, error) {
policy, err := repo.View.LoginPolicyByAggregateID(repo.SystemDefaults.IamID)
if err != nil {
return nil, err
}
return iam_es_model.LoginPolicyViewToModel(policy), err
}

View File

@ -74,6 +74,10 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
defaults, defaults,
repos.IamEvents, repos.IamEvents,
repos.OrgEvents), repos.OrgEvents),
newMailTemplate(
handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}),
newMailText(
handler{view, bulkLimit, configs.cycleDuration("MailText"), errorCount, es}),
} }
} }

View File

@ -0,0 +1,105 @@
package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
)
type MailTemplate struct {
handler
subscription *eventstore.Subscription
}
func newMailTemplate(handler handler) *MailTemplate {
h := &MailTemplate{
handler: handler,
}
h.subscribe()
return h
}
func (m *MailTemplate) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
const (
mailTemplateTable = "adminapi.mail_templates"
)
func (m *MailTemplate) ViewModel() string {
return mailTemplateTable
}
func (_ *MailTemplate) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{iam_es_model.IAMAggregate}
}
func (p *MailTemplate) CurrentSequence(event *models.Event) (uint64, error) {
sequence, err := p.view.GetLatestMailTemplateSequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *MailTemplate) EventQuery() (*models.SearchQuery, error) {
sequence, err := m.view.GetLatestMailTemplateSequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *MailTemplate) Reduce(event *models.Event) (err error) {
switch event.AggregateType {
case model.IAMAggregate:
err = m.processMailTemplate(event)
}
return err
}
func (m *MailTemplate) processMailTemplate(event *models.Event) (err error) {
template := new(iam_model.MailTemplateView)
switch event.Type {
case model.MailTemplateAdded:
err = template.AppendEvent(event)
case model.MailTemplateChanged:
template, err = m.view.MailTemplateByAggregateID(event.AggregateID)
if err != nil {
return err
}
err = template.AppendEvent(event)
default:
return m.view.ProcessedMailTemplateSequence(event)
}
if err != nil {
return err
}
return m.view.PutMailTemplate(template, event)
}
func (m *MailTemplate) OnError(event *models.Event, err error) error {
logging.LogWithFields("SPOOL-Wj8sf", "id", event.AggregateID).WithError(err).Warn("something went wrong in label template handler")
return spooler.HandleError(event, err, m.view.GetLatestMailTemplateFailedEvent, m.view.ProcessedMailTemplateFailedEvent, m.view.ProcessedMailTemplateSequence, m.errorCountUntilSkip)
}
func (o *MailTemplate) OnSuccess() error {
return spooler.HandleSuccess(o.view.UpdateMailTemplateSpoolerRunTimestamp)
}

View File

@ -0,0 +1,109 @@
package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
)
type MailText struct {
handler
subscription *eventstore.Subscription
}
func newMailText(handler handler) *MailText {
h := &MailText{
handler: handler,
}
h.subscribe()
return h
}
func (m *MailText) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
const (
mailTextTable = "adminapi.mail_texts"
)
func (m *MailText) ViewModel() string {
return mailTextTable
}
func (_ *MailText) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{iam_es_model.IAMAggregate}
}
func (p *MailText) CurrentSequence(event *models.Event) (uint64, error) {
sequence, err := p.view.GetLatestMailTextSequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *MailText) EventQuery() (*models.SearchQuery, error) {
sequence, err := m.view.GetLatestMailTextSequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *MailText) Reduce(event *models.Event) (err error) {
switch event.AggregateType {
case model.IAMAggregate:
err = m.processMailText(event)
}
return err
}
func (m *MailText) processMailText(event *models.Event) (err error) {
mailText := new(iam_model.MailTextView)
switch event.Type {
case model.MailTextAdded:
err = mailText.AppendEvent(event)
case model.MailTextChanged:
err = mailText.SetData(event)
if err != nil {
return err
}
mailText, err = m.view.MailTextByIDs(event.AggregateID, mailText.MailTextType, mailText.Language)
if err != nil {
return err
}
err = mailText.AppendEvent(event)
default:
return m.view.ProcessedMailTextSequence(event)
}
if err != nil {
return err
}
return m.view.PutMailText(mailText, event)
}
func (m *MailText) OnError(event *models.Event, err error) error {
logging.LogWithFields("HANDL-5jk84", "id", event.AggregateID).WithError(err).Warn("something went wrong in label mailText handler")
return spooler.HandleError(event, err, m.view.GetLatestMailTextFailedEvent, m.view.ProcessedMailTextFailedEvent, m.view.ProcessedMailTextSequence, m.errorCountUntilSkip)
}
func (o *MailText) OnSuccess() error {
return spooler.HandleSuccess(o.view.UpdateMailTextSpoolerRunTimestamp)
}

View File

@ -0,0 +1,44 @@
package view
import (
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
mailTemplateTable = "adminapi.mail_templates"
)
func (v *View) MailTemplateByAggregateID(aggregateID string) (*model.MailTemplateView, error) {
return view.GetMailTemplateByAggregateID(v.Db, mailTemplateTable, aggregateID)
}
func (v *View) PutMailTemplate(template *model.MailTemplateView, event *models.Event) error {
err := view.PutMailTemplate(v.Db, mailTemplateTable, template)
if err != nil {
return err
}
return v.ProcessedMailTemplateSequence(event)
}
func (v *View) GetLatestMailTemplateSequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(mailTemplateTable, aggregateType)
}
func (v *View) ProcessedMailTemplateSequence(event *models.Event) error {
return v.saveCurrentSequence(mailTemplateTable, event)
}
func (v *View) UpdateMailTemplateSpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(mailTemplateTable)
}
func (v *View) GetLatestMailTemplateFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(mailTemplateTable, sequence)
}
func (v *View) ProcessedMailTemplateFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@ -0,0 +1,48 @@
package view
import (
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
mailTextTable = "adminapi.mail_texts"
)
func (v *View) MailTexts(aggregateID string) ([]*model.MailTextView, error) {
return view.GetMailTexts(v.Db, mailTextTable, aggregateID)
}
func (v *View) MailTextByIDs(aggregateID string, textType string, language string) (*model.MailTextView, error) {
return view.GetMailTextByIDs(v.Db, mailTextTable, aggregateID, textType, language)
}
func (v *View) PutMailText(template *model.MailTextView, event *models.Event) error {
err := view.PutMailText(v.Db, mailTextTable, template)
if err != nil {
return err
}
return v.ProcessedMailTextSequence(event)
}
func (v *View) GetLatestMailTextSequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(mailTextTable, aggregateType)
}
func (v *View) ProcessedMailTextSequence(event *models.Event) error {
return v.saveCurrentSequence(mailTextTable, event)
}
func (v *View) UpdateMailTextSpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(mailTextTable)
}
func (v *View) GetLatestMailTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(mailTextTable, sequence)
}
func (v *View) ProcessedMailTextFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@ -40,6 +40,15 @@ type IAMRepository interface {
AddDefaultLabelPolicy(ctx context.Context, policy *iam_model.LabelPolicy) (*iam_model.LabelPolicy, error) AddDefaultLabelPolicy(ctx context.Context, policy *iam_model.LabelPolicy) (*iam_model.LabelPolicy, error)
ChangeDefaultLabelPolicy(ctx context.Context, policy *iam_model.LabelPolicy) (*iam_model.LabelPolicy, error) ChangeDefaultLabelPolicy(ctx context.Context, policy *iam_model.LabelPolicy) (*iam_model.LabelPolicy, error)
GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
AddDefaultMailTemplate(ctx context.Context, template *iam_model.MailTemplate) (*iam_model.MailTemplate, error)
ChangeDefaultMailTemplate(ctx context.Context, template *iam_model.MailTemplate) (*iam_model.MailTemplate, error)
GetDefaultMailTexts(ctx context.Context) (*iam_model.MailTextsView, error)
GetDefaultMailText(ctx context.Context, textType string, language string) (*iam_model.MailTextView, error)
AddDefaultMailText(ctx context.Context, mailText *iam_model.MailText) (*iam_model.MailText, error)
ChangeDefaultMailText(ctx context.Context, policy *iam_model.MailText) (*iam_model.MailText, error)
GetDefaultPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error) GetDefaultPasswordComplexityPolicy(ctx context.Context) (*iam_model.PasswordComplexityPolicyView, error)
AddDefaultPasswordComplexityPolicy(ctx context.Context, policy *iam_model.PasswordComplexityPolicy) (*iam_model.PasswordComplexityPolicy, error) AddDefaultPasswordComplexityPolicy(ctx context.Context, policy *iam_model.PasswordComplexityPolicy) (*iam_model.PasswordComplexityPolicy, error)
ChangeDefaultPasswordComplexityPolicy(ctx context.Context, policy *iam_model.PasswordComplexityPolicy) (*iam_model.PasswordComplexityPolicy, error) ChangeDefaultPasswordComplexityPolicy(ctx context.Context, policy *iam_model.PasswordComplexityPolicy) (*iam_model.PasswordComplexityPolicy, error)

View File

@ -0,0 +1,24 @@
package admin
import (
"context"
"github.com/caos/zitadel/pkg/grpc/admin"
"github.com/golang/protobuf/ptypes/empty"
)
func (s *Server) GetDefaultMailTemplate(ctx context.Context, _ *empty.Empty) (*admin.DefaultMailTemplateView, error) {
result, err := s.iam.GetDefaultMailTemplate(ctx)
if err != nil {
return nil, err
}
return templateViewFromModel(result), nil
}
func (s *Server) UpdateDefaultMailTemplate(ctx context.Context, policy *admin.DefaultMailTemplateUpdate) (*admin.DefaultMailTemplate, error) {
result, err := s.iam.ChangeDefaultMailTemplate(ctx, templateToModel(policy))
if err != nil {
return nil, err
}
return templateFromModel(result), nil
}

View File

@ -0,0 +1,42 @@
package admin
import (
"github.com/caos/logging"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/pkg/grpc/admin"
"github.com/golang/protobuf/ptypes"
)
func templateToModel(policy *admin.DefaultMailTemplateUpdate) *iam_model.MailTemplate {
return &iam_model.MailTemplate{
Template: policy.Template,
}
}
func templateFromModel(policy *iam_model.MailTemplate) *admin.DefaultMailTemplate {
creationDate, err := ptypes.TimestampProto(policy.CreationDate)
logging.Log("ADMIN-CAA7T").OnError(err).Debug("date parse failed")
changeDate, err := ptypes.TimestampProto(policy.ChangeDate)
logging.Log("ADMIN-H52Zx").OnError(err).Debug("date parse failed")
return &admin.DefaultMailTemplate{
Template: policy.Template,
CreationDate: creationDate,
ChangeDate: changeDate,
}
}
func templateViewFromModel(policy *iam_model.MailTemplateView) *admin.DefaultMailTemplateView {
creationDate, err := ptypes.TimestampProto(policy.CreationDate)
logging.Log("ADMIN-yWFs5").OnError(err).Debug("date parse failed")
changeDate, err := ptypes.TimestampProto(policy.ChangeDate)
logging.Log("ADMIN-JRpIO").OnError(err).Debug("date parse failed")
return &admin.DefaultMailTemplateView{
Template: policy.Template,
CreationDate: creationDate,
ChangeDate: changeDate,
}
}

View File

@ -0,0 +1,32 @@
package admin
import (
"context"
"github.com/caos/zitadel/pkg/grpc/admin"
"github.com/golang/protobuf/ptypes/empty"
)
func (s *Server) GetDefaultMailTexts(ctx context.Context, _ *empty.Empty) (*admin.DefaultMailTextsView, error) {
result, err := s.iam.GetDefaultMailTexts(ctx)
if err != nil {
return nil, err
}
return textsViewFromModel(result), nil
}
func (s *Server) GetDefaultMailText(ctx context.Context, textType string, language string) (*admin.DefaultMailTextView, error) {
result, err := s.iam.GetDefaultMailText(ctx, textType, language)
if err != nil {
return nil, err
}
return textViewFromModel(result), nil
}
func (s *Server) UpdateDefaultMailText(ctx context.Context, text *admin.DefaultMailTextUpdate) (*admin.DefaultMailText, error) {
result, err := s.iam.ChangeDefaultMailText(ctx, textToModel(text))
if err != nil {
return nil, err
}
return textFromModel(result), nil
}

View File

@ -0,0 +1,78 @@
package admin
import (
"github.com/caos/logging"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/pkg/grpc/admin"
"github.com/golang/protobuf/ptypes"
)
func textToModel(text *admin.DefaultMailTextUpdate) *iam_model.MailText {
return &iam_model.MailText{
MailTextType: text.MailTextType,
Language: text.Language,
Title: text.Title,
PreHeader: text.PreHeader,
Subject: text.Subject,
Greeting: text.Greeting,
Text: text.Text,
ButtonText: text.ButtonText,
}
}
func textFromModel(text *iam_model.MailText) *admin.DefaultMailText {
creationDate, err := ptypes.TimestampProto(text.CreationDate)
logging.Log("ADMIN-Jlzsj").OnError(err).Debug("date parse failed")
changeDate, err := ptypes.TimestampProto(text.ChangeDate)
logging.Log("ADMIN-mw5b8").OnError(err).Debug("date parse failed")
return &admin.DefaultMailText{
MailTextType: text.MailTextType,
Language: text.Language,
Title: text.Title,
PreHeader: text.PreHeader,
Subject: text.Subject,
Greeting: text.Greeting,
Text: text.Text,
ButtonText: text.ButtonText,
CreationDate: creationDate,
ChangeDate: changeDate,
}
}
func textsViewFromModel(textsin *iam_model.MailTextsView) *admin.DefaultMailTextsView {
return &admin.DefaultMailTextsView{
Texts: textsViewToModel(textsin.Texts),
}
}
func textsViewToModel(queries []*iam_model.MailTextView) []*admin.DefaultMailTextView {
modelQueries := make([]*admin.DefaultMailTextView, len(queries))
for i, query := range queries {
modelQueries[i] = textViewFromModel(query)
}
return modelQueries
}
func textViewFromModel(text *iam_model.MailTextView) *admin.DefaultMailTextView {
creationDate, err := ptypes.TimestampProto(text.CreationDate)
logging.Log("ADMIN-7RyJc").OnError(err).Debug("date parse failed")
changeDate, err := ptypes.TimestampProto(text.ChangeDate)
logging.Log("ADMIN-fTFgY").OnError(err).Debug("date parse failed")
return &admin.DefaultMailTextView{
MailTextType: text.MailTextType,
Language: text.Language,
Title: text.Title,
PreHeader: text.PreHeader,
Subject: text.Subject,
Greeting: text.Greeting,
Text: text.Text,
ButtonText: text.ButtonText,
CreationDate: creationDate,
ChangeDate: changeDate,
}
}

View File

@ -0,0 +1,45 @@
package management
import (
"context"
"github.com/caos/zitadel/pkg/grpc/management"
"github.com/golang/protobuf/ptypes/empty"
)
func (s *Server) GetMailTemplate(ctx context.Context, _ *empty.Empty) (*management.MailTemplateView, error) {
result, err := s.org.GetMailTemplate(ctx)
if err != nil {
return nil, err
}
return mailTemplateViewFromModel(result), nil
}
func (s *Server) GetDefaultMailTemplate(ctx context.Context, _ *empty.Empty) (*management.MailTemplateView, error) {
result, err := s.org.GetDefaultMailTemplate(ctx)
if err != nil {
return nil, err
}
return mailTemplateViewFromModel(result), nil
}
func (s *Server) CreateMailTemplate(ctx context.Context, template *management.MailTemplateUpdate) (*management.MailTemplate, error) {
result, err := s.org.AddMailTemplate(ctx, mailTemplateRequestToModel(template))
if err != nil {
return nil, err
}
return mailTemplateFromModel(result), nil
}
func (s *Server) UpdateMailTemplate(ctx context.Context, template *management.MailTemplateUpdate) (*management.MailTemplate, error) {
result, err := s.org.ChangeMailTemplate(ctx, mailTemplateRequestToModel(template))
if err != nil {
return nil, err
}
return mailTemplateFromModel(result), nil
}
func (s *Server) RemoveMailTemplate(ctx context.Context, _ *empty.Empty) (*empty.Empty, error) {
err := s.org.RemoveMailTemplate(ctx)
return &empty.Empty{}, err
}

View File

@ -0,0 +1,42 @@
package management
import (
"github.com/caos/logging"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/pkg/grpc/management"
"github.com/golang/protobuf/ptypes"
)
func mailTemplateRequestToModel(mailTemplate *management.MailTemplateUpdate) *iam_model.MailTemplate {
return &iam_model.MailTemplate{
Template: mailTemplate.Template,
}
}
func mailTemplateFromModel(mailTemplate *iam_model.MailTemplate) *management.MailTemplate {
creationDate, err := ptypes.TimestampProto(mailTemplate.CreationDate)
logging.Log("MANAG-ULKZ6").OnError(err).Debug("date parse failed")
changeDate, err := ptypes.TimestampProto(mailTemplate.ChangeDate)
logging.Log("MANAG-451rI").OnError(err).Debug("date parse failed")
return &management.MailTemplate{
Template: mailTemplate.Template,
CreationDate: creationDate,
ChangeDate: changeDate,
}
}
func mailTemplateViewFromModel(mailTemplate *iam_model.MailTemplateView) *management.MailTemplateView {
creationDate, err := ptypes.TimestampProto(mailTemplate.CreationDate)
logging.Log("MANAG-koQnB").OnError(err).Debug("date parse failed")
changeDate, err := ptypes.TimestampProto(mailTemplate.ChangeDate)
logging.Log("MANAG-ToDhD").OnError(err).Debug("date parse failed")
return &management.MailTemplateView{
Default: mailTemplate.Default,
Template: mailTemplate.Template,
CreationDate: creationDate,
ChangeDate: changeDate,
}
}

View File

@ -0,0 +1,45 @@
package management
import (
"context"
"github.com/caos/zitadel/pkg/grpc/management"
"github.com/golang/protobuf/ptypes/empty"
)
func (s *Server) GetMailTexts(ctx context.Context, _ *empty.Empty) (*management.MailTextsView, error) {
result, err := s.org.GetMailTexts(ctx)
if err != nil {
return nil, err
}
return mailTextsViewFromModel(result.Texts), nil
}
func (s *Server) GetDefaultMailTexts(ctx context.Context, _ *empty.Empty) (*management.MailTextsView, error) {
result, err := s.org.GetDefaultMailTexts(ctx)
if err != nil {
return nil, err
}
return mailTextsViewFromModel(result.Texts), nil
}
func (s *Server) CreateMailText(ctx context.Context, mailText *management.MailTextUpdate) (*management.MailText, error) {
result, err := s.org.AddMailText(ctx, mailTextRequestToModel(mailText))
if err != nil {
return nil, err
}
return mailTextFromModel(result), nil
}
func (s *Server) UpdateMailText(ctx context.Context, mailText *management.MailTextUpdate) (*management.MailText, error) {
result, err := s.org.ChangeMailText(ctx, mailTextRequestToModel(mailText))
if err != nil {
return nil, err
}
return mailTextFromModel(result), nil
}
func (s *Server) RemoveMailText(ctx context.Context, mailText *management.MailTextRemove) (*empty.Empty, error) {
err := s.org.RemoveMailText(ctx, mailTextRemoveToModel(mailText))
return &empty.Empty{}, err
}

View File

@ -0,0 +1,82 @@
package management
import (
"github.com/caos/logging"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/pkg/grpc/management"
"github.com/golang/protobuf/ptypes"
)
func mailTextRequestToModel(mailText *management.MailTextUpdate) *iam_model.MailText {
return &iam_model.MailText{
MailTextType: mailText.MailTextType,
Language: mailText.Language,
Title: mailText.Title,
PreHeader: mailText.PreHeader,
Subject: mailText.Subject,
Greeting: mailText.Greeting,
Text: mailText.Text,
ButtonText: mailText.ButtonText,
}
}
func mailTextRemoveToModel(mailText *management.MailTextRemove) *iam_model.MailText {
return &iam_model.MailText{
MailTextType: mailText.MailTextType,
Language: mailText.Language,
}
}
func mailTextFromModel(mailText *iam_model.MailText) *management.MailText {
creationDate, err := ptypes.TimestampProto(mailText.CreationDate)
logging.Log("MANAG-ULKZ6").OnError(err).Debug("date parse failed")
changeDate, err := ptypes.TimestampProto(mailText.ChangeDate)
logging.Log("MANAG-451rI").OnError(err).Debug("date parse failed")
return &management.MailText{
MailTextType: mailText.MailTextType,
Language: mailText.Language,
Title: mailText.Title,
PreHeader: mailText.PreHeader,
Subject: mailText.Subject,
Greeting: mailText.Greeting,
Text: mailText.Text,
ButtonText: mailText.ButtonText,
CreationDate: creationDate,
ChangeDate: changeDate,
}
}
func mailTextsViewFromModel(queries []*iam_model.MailTextView) *management.MailTextsView {
modelQueries := make([]*management.MailTextView, len(queries))
for i, query := range queries {
modelQueries[i] = mailTextViewFromModel(query)
}
return &management.MailTextsView{
Texts: modelQueries,
}
}
func mailTextViewFromModel(mailText *iam_model.MailTextView) *management.MailTextView {
creationDate, err := ptypes.TimestampProto(mailText.CreationDate)
logging.Log("MANAG-koQnB").OnError(err).Debug("date parse failed")
changeDate, err := ptypes.TimestampProto(mailText.ChangeDate)
logging.Log("MANAG-ToDhD").OnError(err).Debug("date parse failed")
return &management.MailTextView{
Default: mailText.Default,
MailTextType: mailText.MailTextType,
Language: mailText.Language,
Title: mailText.Title,
PreHeader: mailText.PreHeader,
Subject: mailText.Subject,
Greeting: mailText.Greeting,
Text: mailText.Text,
ButtonText: mailText.ButtonText,
CreationDate: creationDate,
ChangeDate: changeDate,
}
}

View File

@ -99,10 +99,13 @@ func projectRoleViewsFromModel(roles []*proj_model.ProjectRoleView) []*managemen
func projectRoleViewFromModel(role *proj_model.ProjectRoleView) *management.ProjectRoleView { func projectRoleViewFromModel(role *proj_model.ProjectRoleView) *management.ProjectRoleView {
creationDate, err := ptypes.TimestampProto(role.CreationDate) creationDate, err := ptypes.TimestampProto(role.CreationDate)
logging.Log("GRPC-dlso3").OnError(err).Debug("unable to parse timestamp") logging.Log("GRPC-dlso3").OnError(err).Debug("unable to parse timestamp")
changeDate, err := ptypes.TimestampProto(role.ChangeDate)
logging.Log("MANAG-BRr8Y").OnError(err).Debug("unable to parse timestamp")
return &management.ProjectRoleView{ return &management.ProjectRoleView{
ProjectId: role.ProjectID, ProjectId: role.ProjectID,
CreationDate: creationDate, CreationDate: creationDate,
ChangeDate: changeDate,
Key: role.Key, Key: role.Key,
Group: role.Group, Group: role.Group,
DisplayName: role.DisplayName, DisplayName: role.DisplayName,

View File

@ -84,6 +84,7 @@ func (p *ProjectRole) Reduce(event *es_models.Event) (err error) {
if err != nil { if err != nil {
return err return err
} }
role.ChangeDate = event.CreationDate
err = role.AppendEvent(event) err = role.AppendEvent(event)
case model.ProjectRoleRemoved: case model.ProjectRoleRemoved:
err = role.SetData(event) err = role.SetData(event)

View File

@ -16,6 +16,7 @@ const (
Step7 Step7
Step8 Step8
Step9 Step9
Step10
//StepCount marks the the length of possible steps (StepCount-1 == last possible step) //StepCount marks the the length of possible steps (StepCount-1 == last possible step)
StepCount StepCount
) )
@ -34,6 +35,8 @@ type IAM struct {
DefaultPasswordComplexityPolicy *PasswordComplexityPolicy DefaultPasswordComplexityPolicy *PasswordComplexityPolicy
DefaultPasswordAgePolicy *PasswordAgePolicy DefaultPasswordAgePolicy *PasswordAgePolicy
DefaultPasswordLockoutPolicy *PasswordLockoutPolicy DefaultPasswordLockoutPolicy *PasswordLockoutPolicy
DefaultMailTemplate *MailTemplate
DefaultMailTexts []*MailText
} }
func (iam *IAM) GetMember(userID string) (int, *IAMMember) { func (iam *IAM) GetMember(userID string) (int, *IAMMember) {
@ -53,3 +56,12 @@ func (iam *IAM) GetIDP(idpID string) (int, *IDPConfig) {
} }
return -1, nil return -1, nil
} }
func (iam *IAM) GetDefaultMailText(mailTextType string, language string) (int, *MailText) {
for i, m := range iam.DefaultMailTexts {
if m.MailTextType == mailTextType && m.Language == language {
return i, m
}
}
return -1, nil
}

View File

@ -0,0 +1,17 @@
package model
import (
"github.com/caos/zitadel/internal/eventstore/models"
)
type MailTemplate struct {
models.ObjectRoot
State PolicyState
Default bool
Template []byte
}
func (p *MailTemplate) IsValid() bool {
return p.ObjectRoot.AggregateID != ""
}

View File

@ -0,0 +1,47 @@
package model
import (
"time"
"github.com/caos/zitadel/internal/model"
)
type MailTemplateView struct {
AggregateID string
Template []byte
Default bool
CreationDate time.Time
ChangeDate time.Time
Sequence uint64
}
type MailTemplateSearchRequest struct {
Offset uint64
Limit uint64
SortingColumn MailTemplateSearchKey
Asc bool
Queries []*MailTemplateSearchQuery
}
type MailTemplateSearchKey int32
const (
MailTemplateSearchKeyUnspecified MailTemplateSearchKey = iota
MailTemplateSearchKeyAggregateID
)
type MailTemplateSearchQuery struct {
Key MailTemplateSearchKey
Method model.SearchMethod
Value interface{}
}
type MailTemplateSearchResponse struct {
Offset uint64
Limit uint64
TotalResult uint64
Result []*MailTemplateView
Sequence uint64
Timestamp time.Time
}

View File

@ -0,0 +1,28 @@
package model
import (
"github.com/caos/zitadel/internal/eventstore/models"
)
type MailTexts struct {
Texts []*MailText
Default bool
}
type MailText struct {
models.ObjectRoot
State PolicyState
Default bool
MailTextType string
Language string
Title string
PreHeader string
Subject string
Greeting string
Text string
ButtonText string
}
func (p *MailText) IsValid() bool {
return p.ObjectRoot.AggregateID != ""
}

View File

@ -0,0 +1,60 @@
package model
import (
"time"
"github.com/caos/zitadel/internal/model"
)
type MailTextsView struct {
Texts []*MailTextView
Default bool
}
type MailTextView struct {
AggregateID string
MailTextType string
Language string
Title string
PreHeader string
Subject string
Greeting string
Text string
ButtonText string
Default bool
CreationDate time.Time
ChangeDate time.Time
Sequence uint64
}
type MailTextSearchRequest struct {
Offset uint64
Limit uint64
SortingColumn MailTextSearchKey
Asc bool
Queries []*MailTextSearchQuery
}
type MailTextSearchKey int32
const (
MailTextSearchKeyUnspecified MailTextSearchKey = iota
MailTextSearchKeyAggregateID
MailTextSearchKeyMailTextType
MailTextSearchKeyLanguage
)
type MailTextSearchQuery struct {
Key MailTextSearchKey
Method model.SearchMethod
Value interface{}
}
type MailTextSearchResponse struct {
Offset uint64
Limit uint64
TotalResult uint64
Result []*MailTextView
Sequence uint64
Timestamp time.Time
}

View File

@ -964,3 +964,118 @@ func (es *IAMEventstore) ChangeOrgIAMPolicy(ctx context.Context, policy *iam_mod
es.iamCache.cacheIAM(repoIam) es.iamCache.cacheIAM(repoIam)
return model.OrgIAMPolicyToModel(repoIam.DefaultOrgIAMPolicy), nil return model.OrgIAMPolicyToModel(repoIam.DefaultOrgIAMPolicy), nil
} }
func (es *IAMEventstore) PrepareAddMailTemplate(ctx context.Context, template *iam_model.MailTemplate) (*model.IAM, *models.Aggregate, error) {
if template == nil || !template.IsValid() {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-j9l18", "Errors.IAM.MailTemplate.Empty")
}
iam, err := es.IAMByID(ctx, template.AggregateID)
if err != nil {
return nil, nil, err
}
repoIam := model.IAMFromModel(iam)
mailTemplate := model.MailTemplateFromModel(template)
addAggregate := MailTemplateAddedAggregate(es.Eventstore.AggregateCreator(), repoIam, mailTemplate)
aggregate, err := addAggregate(ctx)
if err != nil {
return nil, nil, err
}
return repoIam, aggregate, nil
}
func (es *IAMEventstore) AddMailTemplate(ctx context.Context, template *iam_model.MailTemplate) (*iam_model.MailTemplate, error) {
repoIam, addAggregate, err := es.PrepareAddMailTemplate(ctx, template)
if err != nil {
return nil, err
}
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoIam.AppendEvents, addAggregate)
if err != nil {
return nil, err
}
es.iamCache.cacheIAM(repoIam)
return model.MailTemplateToModel(repoIam.DefaultMailTemplate), nil
}
func (es *IAMEventstore) ChangeMailTemplate(ctx context.Context, template *iam_model.MailTemplate) (*iam_model.MailTemplate, error) {
if template == nil || !template.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-gCnCs", "Errors.IAM.MailTemplateInvalid")
}
iam, err := es.IAMByID(ctx, template.AggregateID)
if err != nil {
return nil, err
}
repoIam := model.IAMFromModel(iam)
repoMailTemplate := model.MailTemplateFromModel(template)
addAggregate := MailTemplateChangedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoMailTemplate)
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, addAggregate)
if err != nil {
return nil, err
}
es.iamCache.cacheIAM(repoIam)
return model.MailTemplateToModel(repoIam.DefaultMailTemplate), nil
}
func (es *IAMEventstore) PrepareAddMailText(ctx context.Context, text *iam_model.MailText) (*model.IAM, *models.Aggregate, error) {
if text == nil || !text.IsValid() {
return nil, nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-j9l18", "Errors.IAM.MailText.Empty")
}
iam, err := es.IAMByID(ctx, text.AggregateID)
if err != nil {
return nil, nil, err
}
repoIam := model.IAMFromModel(iam)
mailText := model.MailTextFromModel(text)
addAggregate := MailTextAddedAggregate(es.Eventstore.AggregateCreator(), repoIam, mailText)
aggregate, err := addAggregate(ctx)
if err != nil {
return nil, nil, err
}
return repoIam, aggregate, nil
}
func (es *IAMEventstore) AddMailText(ctx context.Context, text *iam_model.MailText) (*iam_model.MailText, error) {
repoIam, addAggregate, err := es.PrepareAddMailText(ctx, text)
if err != nil {
return nil, err
}
err = es_sdk.PushAggregates(ctx, es.PushAggregates, repoIam.AppendEvents, addAggregate)
if err != nil {
return nil, err
}
es.iamCache.cacheIAM(repoIam)
if _, m := model.GetMailText(repoIam.DefaultMailTexts, text.MailTextType, text.Language); m != nil {
return model.MailTextToModel(m), nil
}
return nil, caos_errs.ThrowInternal(nil, "EVENT-9AwUm", "Errors.Internal")
}
func (es *IAMEventstore) ChangeMailText(ctx context.Context, text *iam_model.MailText) (*iam_model.MailText, error) {
if !text.IsValid() {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-J5xbB", "Errors.IAM.MailTextInvalid")
}
existing, err := es.IAMByID(ctx, text.AggregateID)
if err != nil {
return nil, err
}
if _, m := existing.GetDefaultMailText(text.MailTextType, text.Language); m == nil {
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-0CTV3", "Errors.IAM.MailTextNotExisting")
}
repoIam := model.IAMFromModel(existing)
repoMember := model.MailTextFromModel(text)
projectAggregate := MailTextChangedAggregate(es.Eventstore.AggregateCreator(), repoIam, repoMember)
err = es_sdk.Push(ctx, es.PushAggregates, repoIam.AppendEvents, projectAggregate)
es.iamCache.cacheIAM(repoIam)
if _, m := model.GetMailText(repoIam.DefaultMailTexts, text.MailTextType, text.Language); m != nil {
return model.MailTextToModel(m), nil
}
return nil, caos_errs.ThrowInternal(nil, "EVENT-HawVx", "Errors.Internal")
}

View File

@ -216,3 +216,29 @@ func GetMockManipulateIAMWithLabelPolicy(ctrl *gomock.Controller) *IAMEventstore
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil) mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
return GetMockedEventstore(ctrl, mockEs) return GetMockedEventstore(ctrl, mockEs)
} }
func GetMockManipulateIAMWithMailTemplate(ctrl *gomock.Controller) *IAMEventstore {
mailTemplate, _ := json.Marshal(model.MailTemplate{Template: []byte("<!doctype htm>")})
events := []*es_models.Event{
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.IAMSetupStarted},
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.MailTemplateAdded, Data: mailTemplate},
}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
return GetMockedEventstore(ctrl, mockEs)
}
func GetMockManipulateIAMWithMailText(ctrl *gomock.Controller) *IAMEventstore {
mailText, _ := json.Marshal(model.MailText{MailTextType: "Type", Language: "DE"})
events := []*es_models.Event{
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.IAMSetupStarted},
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.MailTextAdded, Data: mailText},
}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
return GetMockedEventstore(ctrl, mockEs)
}

View File

@ -2815,3 +2815,321 @@ func TestChangeOrgIAMPolicy(t *testing.T) {
}) })
} }
} }
func TestAddMailTemplate(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *IAMEventstore
ctx context.Context
policy *iam_model.MailTemplate
}
type res struct {
result *iam_model.MailTemplate
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "add mailtemplate, ok",
args: args{
es: GetMockManipulateIAM(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
policy: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
Template: []byte("<!doctype html>"),
},
},
res: res{
result: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
Template: []byte("<!doctype html>"),
},
},
},
{
name: "invalid policy",
args: args{
es: GetMockManipulateIAM(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
policy: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing iam not found",
args: args{
es: GetMockManipulateIAMNotExisting(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
policy: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.AddMailTemplate(tt.args.ctx, tt.args.policy)
if (tt.res.wantErr && !tt.res.errFunc(err)) || (err != nil && !tt.res.wantErr) {
t.Errorf("got wrong err: %v ", err)
return
}
if tt.res.wantErr && tt.res.errFunc(err) {
return
}
if string(result.Template) != string(tt.res.result.Template) {
t.Errorf("got wrong result Template: expected: %v, actual: %v ", tt.res.result.Template, result.Template)
}
})
}
}
func TestChangeMailTemplate(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *IAMEventstore
ctx context.Context
template *iam_model.MailTemplate
}
type res struct {
result *iam_model.MailTemplate
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "add mail template, ok",
args: args{
es: GetMockManipulateIAMWithMailTemplate(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
template: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
Template: []byte("<!doctype html>"),
},
},
res: res{
result: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
Template: []byte("<!doctype html>"),
},
},
},
{
name: "invalid mail template",
args: args{
es: GetMockManipulateIAM(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
template: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing iam not found",
args: args{
es: GetMockManipulateIAMNotExisting(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
template: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.ChangeMailTemplate(tt.args.ctx, tt.args.template)
if (tt.res.wantErr && !tt.res.errFunc(err)) || (err != nil && !tt.res.wantErr) {
t.Errorf("got wrong err: %v ", err)
return
}
if tt.res.wantErr && tt.res.errFunc(err) {
return
}
if string(result.Template) != string(tt.res.result.Template) {
t.Errorf("got wrong result Template: expected: %v, actual: %v ", tt.res.result.Template, result.Template)
}
})
}
}
func TestAddMailText(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *IAMEventstore
ctx context.Context
policy *iam_model.MailText
}
type res struct {
result *iam_model.MailText
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "add mailtemplate, ok",
args: args{
es: GetMockManipulateIAM(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
policy: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
MailTextType: "Type", Language: "DE",
},
},
res: res{
result: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
MailTextType: "Type", Language: "DE",
},
},
},
{
name: "invalid policy",
args: args{
es: GetMockManipulateIAM(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
policy: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing iam not found",
args: args{
es: GetMockManipulateIAMNotExisting(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
policy: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.AddMailText(tt.args.ctx, tt.args.policy)
if (tt.res.wantErr && !tt.res.errFunc(err)) || (err != nil && !tt.res.wantErr) {
t.Errorf("got wrong err: %v ", err)
return
}
if tt.res.wantErr && tt.res.errFunc(err) {
return
}
if string(result.MailTextType) != string(tt.res.result.MailTextType) {
t.Errorf("got wrong result MailTextType: expected: %v, actual: %v ", tt.res.result.MailTextType, result.MailTextType)
}
})
}
}
func TestChangeMailText(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *IAMEventstore
ctx context.Context
policy *iam_model.MailText
}
type res struct {
result *iam_model.MailText
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "change mailtemplate, ok",
args: args{
es: GetMockManipulateIAMWithMailText(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
policy: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
MailTextType: "Type", Language: "DE",
},
},
res: res{
result: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
MailTextType: "Type", Language: "DE",
},
},
},
{
name: "invalid policy",
args: args{
es: GetMockManipulateIAM(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
policy: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing iam not found",
args: args{
es: GetMockManipulateIAMNotExisting(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
policy: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.ChangeMailText(tt.args.ctx, tt.args.policy)
if (tt.res.wantErr && !tt.res.errFunc(err)) || (err != nil && !tt.res.wantErr) {
t.Errorf("got wrong err: %v ", err)
return
}
if tt.res.wantErr && tt.res.errFunc(err) {
return
}
if string(result.MailTextType) != string(tt.res.result.MailTextType) {
t.Errorf("got wrong result MailTextType: expected: %v, actual: %v ", tt.res.result.MailTextType, result.MailTextType)
}
})
}
}

View File

@ -232,6 +232,7 @@ func OIDCIDPConfigChangedAggregate(aggCreator *es_models.AggregateCreator, exist
return agg.AppendEvent(model.OIDCIDPConfigChanged, changes) return agg.AppendEvent(model.OIDCIDPConfigChanged, changes)
} }
} }
func LabelPolicyAddedAggregate(aggCreator *es_models.AggregateCreator, existing *model.IAM, policy *model.LabelPolicy) func(ctx context.Context) (*es_models.Aggregate, error) { func LabelPolicyAddedAggregate(aggCreator *es_models.AggregateCreator, existing *model.IAM, policy *model.LabelPolicy) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) { return func(ctx context.Context) (*es_models.Aggregate, error) {
if policy == nil { if policy == nil {
@ -678,6 +679,101 @@ func checkExistingLoginPolicyIDPProviderValidation(idpConfigID string) func(...*
} }
} }
func MailTemplateAddedAggregate(aggCreator *es_models.AggregateCreator, existing *model.IAM, template *model.MailTemplate) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if template == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-ZCfDS", "Errors.Internal")
}
agg, err := IAMAggregate(ctx, aggCreator, existing)
if err != nil {
return nil, err
}
validationQuery := es_models.NewSearchQuery().
AggregateTypeFilter(model.IAMAggregate).
EventTypesFilter(model.MailTemplateAdded).
AggregateIDFilter(existing.AggregateID)
validation := checkExistingMailTemplateValidation()
agg.SetPrecondition(validationQuery, validation)
return agg.AppendEvent(model.MailTemplateAdded, template)
}
}
func checkExistingMailTemplateValidation() func(...*es_models.Event) error {
return func(events ...*es_models.Event) error {
for _, event := range events {
switch event.Type {
case model.MailTemplateAdded:
return errors.ThrowPreconditionFailed(nil, "EVENT-uKPiJ", "Errors.IAM.MailTemplate.AlreadyExists")
}
}
return nil
}
}
func MailTemplateChangedAggregate(aggCreator *es_models.AggregateCreator, existing *model.IAM, template *model.MailTemplate) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if template == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-s4PVD", "Errors.Internal")
}
agg, err := IAMAggregate(ctx, aggCreator, existing)
if err != nil {
return nil, err
}
changes := existing.DefaultMailTemplate.Changes(template)
if len(changes) == 0 {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-hxxSm", "Errors.NoChangesFound")
}
return agg.AppendEvent(model.MailTemplateChanged, changes)
}
}
func MailTextAddedAggregate(aggCreator *es_models.AggregateCreator, existing *model.IAM, text *model.MailText) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if text == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-ZCfDS", "Errors.Internal")
}
agg, err := IAMAggregate(ctx, aggCreator, existing)
if err != nil {
return nil, err
}
validationQuery := es_models.NewSearchQuery().
AggregateTypeFilter(model.IAMAggregate).
EventTypesFilter(model.MailTextAdded).
AggregateIDFilter(existing.AggregateID)
validation := checkExistingMailTextValidation()
agg.SetPrecondition(validationQuery, validation)
return agg.AppendEvent(model.MailTextAdded, text)
}
}
func checkExistingMailTextValidation() func(...*es_models.Event) error {
return func(events ...*es_models.Event) error {
for _, event := range events {
switch event.Type {
case model.MailTextAdded:
return errors.ThrowPreconditionFailed(nil, "EVENT-ijzeq", "Errors.IAM.MailText.AlreadyExists")
}
}
return nil
}
}
func MailTextChangedAggregate(aggCreator *es_models.AggregateCreator, existingIAM *model.IAM, text *model.MailText) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if text == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-mgYpV", "Errors.Internal")
}
agg, err := IAMAggregate(ctx, aggCreator, existingIAM)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.MailTextChanged, text)
}
}
func checkExistingLoginPolicySecondFactorValidation(mfaType int32) func(...*es_models.Event) error { func checkExistingLoginPolicySecondFactorValidation(mfaType int32) func(...*es_models.Event) error {
return func(events ...*es_models.Event) error { return func(events ...*es_models.Event) error {
mfas := make([]int32, 0) mfas := make([]int32, 0)

View File

@ -31,6 +31,8 @@ type IAM struct {
IDPs []*IDPConfig `json:"-"` IDPs []*IDPConfig `json:"-"`
DefaultLoginPolicy *LoginPolicy `json:"-"` DefaultLoginPolicy *LoginPolicy `json:"-"`
DefaultLabelPolicy *LabelPolicy `json:"-"` DefaultLabelPolicy *LabelPolicy `json:"-"`
DefaultMailTemplate *MailTemplate `json:"-"`
DefaultMailTexts []*MailText `json:"-"`
DefaultOrgIAMPolicy *OrgIAMPolicy `json:"-"` DefaultOrgIAMPolicy *OrgIAMPolicy `json:"-"`
DefaultPasswordComplexityPolicy *PasswordComplexityPolicy `json:"-"` DefaultPasswordComplexityPolicy *PasswordComplexityPolicy `json:"-"`
DefaultPasswordAgePolicy *PasswordAgePolicy `json:"-"` DefaultPasswordAgePolicy *PasswordAgePolicy `json:"-"`
@ -40,14 +42,16 @@ type IAM struct {
func IAMFromModel(iam *model.IAM) *IAM { func IAMFromModel(iam *model.IAM) *IAM {
members := IAMMembersFromModel(iam.Members) members := IAMMembersFromModel(iam.Members)
idps := IDPConfigsFromModel(iam.IDPs) idps := IDPConfigsFromModel(iam.IDPs)
mailTexts := MailTextsFromModel(iam.DefaultMailTexts)
converted := &IAM{ converted := &IAM{
ObjectRoot: iam.ObjectRoot, ObjectRoot: iam.ObjectRoot,
SetUpStarted: Step(iam.SetUpStarted), SetUpStarted: Step(iam.SetUpStarted),
SetUpDone: Step(iam.SetUpDone), SetUpDone: Step(iam.SetUpDone),
GlobalOrgID: iam.GlobalOrgID, GlobalOrgID: iam.GlobalOrgID,
IAMProjectID: iam.IAMProjectID, IAMProjectID: iam.IAMProjectID,
Members: members, Members: members,
IDPs: idps, IDPs: idps,
DefaultMailTexts: mailTexts,
} }
if iam.DefaultLoginPolicy != nil { if iam.DefaultLoginPolicy != nil {
converted.DefaultLoginPolicy = LoginPolicyFromModel(iam.DefaultLoginPolicy) converted.DefaultLoginPolicy = LoginPolicyFromModel(iam.DefaultLoginPolicy)
@ -55,6 +59,9 @@ func IAMFromModel(iam *model.IAM) *IAM {
if iam.DefaultLabelPolicy != nil { if iam.DefaultLabelPolicy != nil {
converted.DefaultLabelPolicy = LabelPolicyFromModel(iam.DefaultLabelPolicy) converted.DefaultLabelPolicy = LabelPolicyFromModel(iam.DefaultLabelPolicy)
} }
if iam.DefaultMailTemplate != nil {
converted.DefaultMailTemplate = MailTemplateFromModel(iam.DefaultMailTemplate)
}
if iam.DefaultPasswordComplexityPolicy != nil { if iam.DefaultPasswordComplexityPolicy != nil {
converted.DefaultPasswordComplexityPolicy = PasswordComplexityPolicyFromModel(iam.DefaultPasswordComplexityPolicy) converted.DefaultPasswordComplexityPolicy = PasswordComplexityPolicyFromModel(iam.DefaultPasswordComplexityPolicy)
} }
@ -73,14 +80,16 @@ func IAMFromModel(iam *model.IAM) *IAM {
func IAMToModel(iam *IAM) *model.IAM { func IAMToModel(iam *IAM) *model.IAM {
members := IAMMembersToModel(iam.Members) members := IAMMembersToModel(iam.Members)
idps := IDPConfigsToModel(iam.IDPs) idps := IDPConfigsToModel(iam.IDPs)
mailTexts := MailTextsToModel(iam.DefaultMailTexts)
converted := &model.IAM{ converted := &model.IAM{
ObjectRoot: iam.ObjectRoot, ObjectRoot: iam.ObjectRoot,
SetUpStarted: model.Step(iam.SetUpStarted), SetUpStarted: model.Step(iam.SetUpStarted),
SetUpDone: model.Step(iam.SetUpDone), SetUpDone: model.Step(iam.SetUpDone),
GlobalOrgID: iam.GlobalOrgID, GlobalOrgID: iam.GlobalOrgID,
IAMProjectID: iam.IAMProjectID, IAMProjectID: iam.IAMProjectID,
Members: members, Members: members,
IDPs: idps, IDPs: idps,
DefaultMailTexts: mailTexts,
} }
if iam.DefaultLoginPolicy != nil { if iam.DefaultLoginPolicy != nil {
converted.DefaultLoginPolicy = LoginPolicyToModel(iam.DefaultLoginPolicy) converted.DefaultLoginPolicy = LoginPolicyToModel(iam.DefaultLoginPolicy)
@ -88,6 +97,9 @@ func IAMToModel(iam *IAM) *model.IAM {
if iam.DefaultLabelPolicy != nil { if iam.DefaultLabelPolicy != nil {
converted.DefaultLabelPolicy = LabelPolicyToModel(iam.DefaultLabelPolicy) converted.DefaultLabelPolicy = LabelPolicyToModel(iam.DefaultLabelPolicy)
} }
if iam.DefaultMailTemplate != nil {
converted.DefaultMailTemplate = MailTemplateToModel(iam.DefaultMailTemplate)
}
if iam.DefaultPasswordComplexityPolicy != nil { if iam.DefaultPasswordComplexityPolicy != nil {
converted.DefaultPasswordComplexityPolicy = PasswordComplexityPolicyToModel(iam.DefaultPasswordComplexityPolicy) converted.DefaultPasswordComplexityPolicy = PasswordComplexityPolicyToModel(iam.DefaultPasswordComplexityPolicy)
} }
@ -180,6 +192,14 @@ func (i *IAM) AppendEvent(event *es_models.Event) (err error) {
return i.appendAddLabelPolicyEvent(event) return i.appendAddLabelPolicyEvent(event)
case LabelPolicyChanged: case LabelPolicyChanged:
return i.appendChangeLabelPolicyEvent(event) return i.appendChangeLabelPolicyEvent(event)
case MailTemplateAdded:
return i.appendAddMailTemplateEvent(event)
case MailTemplateChanged:
return i.appendChangeMailTemplateEvent(event)
case MailTextAdded:
return i.appendAddMailTextEvent(event)
case MailTextChanged:
return i.appendChangeMailTextEvent(event)
case PasswordComplexityPolicyAdded: case PasswordComplexityPolicyAdded:
return i.appendAddPasswordComplexityPolicyEvent(event) return i.appendAddPasswordComplexityPolicyEvent(event)
case PasswordComplexityPolicyChanged: case PasswordComplexityPolicyChanged:

View File

@ -0,0 +1,64 @@
package model
import (
b64 "encoding/base64"
"encoding/json"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
iam_model "github.com/caos/zitadel/internal/iam/model"
)
type MailTemplate struct {
models.ObjectRoot
State int32 `json:"-"`
Template []byte
}
func MailTemplateToModel(template *MailTemplate) *iam_model.MailTemplate {
return &iam_model.MailTemplate{
ObjectRoot: template.ObjectRoot,
State: iam_model.PolicyState(template.State),
Template: template.Template,
}
}
func MailTemplateFromModel(template *iam_model.MailTemplate) *MailTemplate {
return &MailTemplate{
ObjectRoot: template.ObjectRoot,
State: int32(template.State),
Template: template.Template,
}
}
func (p *MailTemplate) Changes(changed *MailTemplate) map[string]interface{} {
changes := make(map[string]interface{}, 1)
if b64.StdEncoding.EncodeToString(changed.Template) != b64.StdEncoding.EncodeToString(p.Template) {
changes["template"] = b64.StdEncoding.EncodeToString(changed.Template)
}
return changes
}
func (i *IAM) appendAddMailTemplateEvent(event *es_models.Event) error {
i.DefaultMailTemplate = new(MailTemplate)
err := i.DefaultMailTemplate.SetDataLabel(event)
if err != nil {
return err
}
i.DefaultMailTemplate.ObjectRoot.CreationDate = event.CreationDate
return nil
}
func (i *IAM) appendChangeMailTemplateEvent(event *es_models.Event) error {
return i.DefaultMailTemplate.SetDataLabel(event)
}
func (p *MailTemplate) SetDataLabel(event *es_models.Event) error {
err := json.Unmarshal(event.Data, p)
if err != nil {
return errors.ThrowInternal(err, "MODEL-ikjhf", "unable to unmarshal data")
}
return nil
}

View File

@ -0,0 +1,126 @@
package model
import (
"encoding/json"
"testing"
es_models "github.com/caos/zitadel/internal/eventstore/models"
)
func TestMailTemplateChanges(t *testing.T) {
type args struct {
existing *MailTemplate
new *MailTemplate
}
type res struct {
changesLen int
}
tests := []struct {
name string
args args
res res
}{
{
name: "mailtemplate all attributes change",
args: args{
existing: &MailTemplate{Template: []byte("<doctype html>")},
new: &MailTemplate{Template: []byte("<!doctype html>")},
},
res: res{
changesLen: 1,
},
},
{
name: "no changes",
args: args{
existing: &MailTemplate{Template: []byte("<!doctype html>")},
new: &MailTemplate{Template: []byte("<!doctype html>")},
},
res: res{
changesLen: 0,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
changes := tt.args.existing.Changes(tt.args.new)
if len(changes) != tt.res.changesLen {
t.Errorf("got wrong changes len: expected: %v, actual: %v ", tt.res.changesLen, len(changes))
}
})
}
}
func TestAppendAddMailTemplateEvent(t *testing.T) {
type args struct {
iam *IAM
policy *MailTemplate
event *es_models.Event
}
tests := []struct {
name string
args args
result *IAM
}{
{
name: "append add label policy event",
args: args{
iam: new(IAM),
policy: &MailTemplate{Template: []byte("<!doctype html>")},
event: new(es_models.Event),
},
result: &IAM{DefaultMailTemplate: &MailTemplate{Template: []byte("<!doctype html>")}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.policy != nil {
data, _ := json.Marshal(tt.args.policy)
tt.args.event.Data = data
}
tt.args.iam.appendAddMailTemplateEvent(tt.args.event)
if string(tt.result.DefaultMailTemplate.Template) != string(tt.args.iam.DefaultMailTemplate.Template) {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultMailTemplate.Template, tt.args.iam.DefaultMailTemplate.Template)
}
})
}
}
func TestAppendChangeMailTemplateEvent(t *testing.T) {
type args struct {
iam *IAM
policy *MailTemplate
event *es_models.Event
}
tests := []struct {
name string
args args
result *IAM
}{
{
name: "append change label policy event",
args: args{
iam: &IAM{DefaultMailTemplate: &MailTemplate{
Template: []byte("<doctype html>"),
}},
policy: &MailTemplate{Template: []byte("<!doctype html>")},
event: &es_models.Event{},
},
result: &IAM{DefaultMailTemplate: &MailTemplate{
Template: []byte("<!doctype html>"),
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.policy != nil {
data, _ := json.Marshal(tt.args.policy)
tt.args.event.Data = data
}
tt.args.iam.appendChangeMailTemplateEvent(tt.args.event)
if string(tt.result.DefaultMailTemplate.Template) != string(tt.args.iam.DefaultMailTemplate.Template) {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultMailTemplate.Template, tt.args.iam.DefaultMailTemplate.Template)
}
})
}
}

View File

@ -0,0 +1,157 @@
package model
import (
"encoding/json"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
iam_model "github.com/caos/zitadel/internal/iam/model"
)
type MailText struct {
models.ObjectRoot
State int32 `json:"-"`
MailTextType string
Language string
Title string
PreHeader string
Subject string
Greeting string
Text string
ButtonText string
}
func GetMailText(mailTexts []*MailText, mailTextType string, language string) (int, *MailText) {
for i, m := range mailTexts {
if m.MailTextType == mailTextType && m.Language == language {
return i, m
}
}
return -1, nil
}
func MailTextsToModel(mailTexts []*MailText) []*iam_model.MailText {
convertedMailTexts := make([]*iam_model.MailText, len(mailTexts))
for i, m := range mailTexts {
convertedMailTexts[i] = MailTextToModel(m)
}
return convertedMailTexts
}
func MailTextToModel(mailText *MailText) *iam_model.MailText {
return &iam_model.MailText{
ObjectRoot: mailText.ObjectRoot,
State: iam_model.PolicyState(mailText.State),
MailTextType: mailText.MailTextType,
Language: mailText.Language,
Title: mailText.Title,
PreHeader: mailText.PreHeader,
Subject: mailText.Subject,
Greeting: mailText.Greeting,
Text: mailText.Text,
ButtonText: mailText.ButtonText,
}
}
func MailTextsFromModel(mailTexts []*iam_model.MailText) []*MailText {
convertedMailTexts := make([]*MailText, len(mailTexts))
for i, m := range mailTexts {
convertedMailTexts[i] = MailTextFromModel(m)
}
return convertedMailTexts
}
func MailTextFromModel(mailText *iam_model.MailText) *MailText {
return &MailText{
ObjectRoot: mailText.ObjectRoot,
State: int32(mailText.State),
MailTextType: mailText.MailTextType,
Language: mailText.Language,
Title: mailText.Title,
PreHeader: mailText.PreHeader,
Subject: mailText.Subject,
Greeting: mailText.Greeting,
Text: mailText.Text,
ButtonText: mailText.ButtonText,
}
}
func (p *MailText) Changes(changed *MailText) map[string]interface{} {
changes := make(map[string]interface{}, 8)
changes["mailTextType"] = changed.MailTextType
changes["language"] = changed.Language
if changed.Title != p.Title {
changes["title"] = changed.Title
}
if changed.PreHeader != p.PreHeader {
changes["preHeader"] = changed.PreHeader
}
if changed.Subject != p.Subject {
changes["subject"] = changed.Subject
}
if changed.Greeting != p.Greeting {
changes["greeting"] = changed.Greeting
}
if changed.Text != p.Text {
changes["text"] = changed.Text
}
if changed.ButtonText != p.ButtonText {
changes["buttonText"] = changed.ButtonText
}
return changes
}
func (i *IAM) appendAddMailTextEvent(event *es_models.Event) error {
mailText := &MailText{}
err := mailText.SetDataLabel(event)
if err != nil {
return err
}
mailText.ObjectRoot.CreationDate = event.CreationDate
i.DefaultMailTexts = append(i.DefaultMailTexts, mailText)
return nil
}
func (i *IAM) appendChangeMailTextEvent(event *es_models.Event) error {
mailText := &MailText{}
err := mailText.SetDataLabel(event)
if err != nil {
return err
}
if n, m := GetMailText(i.DefaultMailTexts, mailText.MailTextType, mailText.Language); m != nil {
i.DefaultMailTexts[n] = mailText
}
return nil
}
func (i *IAM) appendRemoveMailTextEvent(event *es_models.Event) error {
mailText := &MailText{}
err := mailText.SetDataLabel(event)
if err != nil {
return err
}
if n, m := GetMailText(i.DefaultMailTexts, mailText.MailTextType, mailText.Language); m != nil {
i.DefaultMailTexts[n] = i.DefaultMailTexts[len(i.DefaultMailTexts)-1]
i.DefaultMailTexts[len(i.DefaultMailTexts)-1] = nil
i.DefaultMailTexts = i.DefaultMailTexts[:len(i.DefaultMailTexts)-1]
}
return nil
}
func (p *MailText) SetDataLabel(event *es_models.Event) error {
err := json.Unmarshal(event.Data, p)
if err != nil {
return errors.ThrowInternal(err, "MODEL-3FUV5", "unable to unmarshal data")
}
return nil
}

View File

@ -0,0 +1,134 @@
package model
import (
"encoding/json"
"testing"
es_models "github.com/caos/zitadel/internal/eventstore/models"
)
func TestAppendAddMailTextEvent(t *testing.T) {
type args struct {
iam *IAM
mailText *MailText
event *es_models.Event
}
tests := []struct {
name string
args args
result *IAM
}{
{
name: "append add mailText event",
args: args{
iam: &IAM{},
mailText: &MailText{
MailTextType: "PasswordReset",
Language: "DE"},
event: &es_models.Event{},
},
result: &IAM{DefaultMailTexts: []*MailText{&MailText{
MailTextType: "PasswordReset",
Language: "DE"}}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.mailText != nil {
data, _ := json.Marshal(tt.args.mailText)
tt.args.event.Data = data
}
tt.args.iam.appendAddMailTextEvent(tt.args.event)
if len(tt.args.iam.DefaultMailTexts) != 1 {
t.Errorf("got wrong result should have one mailText actual: %v ", len(tt.args.iam.DefaultMailTexts))
}
if tt.args.iam.DefaultMailTexts[0] == tt.result.DefaultMailTexts[0] {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultMailTexts[0], tt.args.iam.DefaultMailTexts[0])
}
})
}
}
func TestAppendChangeMailTextEvent(t *testing.T) {
type args struct {
iam *IAM
mailText *MailText
event *es_models.Event
}
tests := []struct {
name string
args args
result *IAM
}{
{
name: "append change mailText event",
args: args{
iam: &IAM{DefaultMailTexts: []*MailText{&MailText{
MailTextType: "PasswordReset",
Language: "DE"}}},
mailText: &MailText{
MailTextType: "ChangedPasswordReset",
Language: "DE"},
event: &es_models.Event{},
},
result: &IAM{DefaultMailTexts: []*MailText{&MailText{
MailTextType: "PasswordReset",
Language: "ChangedDE"}}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.mailText != nil {
data, _ := json.Marshal(tt.args.mailText)
tt.args.event.Data = data
}
tt.args.iam.appendChangeMailTextEvent(tt.args.event)
if len(tt.args.iam.DefaultMailTexts) != 1 {
t.Errorf("got wrong result should have one mailText actual: %v ", len(tt.args.iam.DefaultMailTexts))
}
if tt.args.iam.DefaultMailTexts[0] == tt.result.DefaultMailTexts[0] {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.DefaultMailTexts[0], tt.args.iam.DefaultMailTexts[0])
}
})
}
}
func TestAppendRemoveMailTextEvent(t *testing.T) {
type args struct {
iam *IAM
mailText *MailText
event *es_models.Event
}
tests := []struct {
name string
args args
result *IAM
}{
{
name: "append remove mailText event",
args: args{
iam: &IAM{DefaultMailTexts: []*MailText{&MailText{
MailTextType: "PasswordReset",
Language: "DE",
Subject: "Subject"}}},
mailText: &MailText{
MailTextType: "PasswordReset",
Language: "DE"},
event: &es_models.Event{},
},
result: &IAM{DefaultMailTexts: []*MailText{}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.mailText != nil {
data, _ := json.Marshal(tt.args.mailText)
tt.args.event.Data = data
}
tt.args.iam.appendRemoveMailTextEvent(tt.args.event)
if len(tt.args.iam.DefaultMailTexts) != 0 {
t.Errorf("got wrong result should have no mailText actual: %v ", len(tt.args.iam.DefaultMailTexts))
}
})
}
}

View File

@ -38,6 +38,11 @@ const (
LabelPolicyAdded models.EventType = "iam.policy.label.added" LabelPolicyAdded models.EventType = "iam.policy.label.added"
LabelPolicyChanged models.EventType = "iam.policy.label.changed" LabelPolicyChanged models.EventType = "iam.policy.label.changed"
MailTemplateAdded models.EventType = "iam.mail.template.added"
MailTemplateChanged models.EventType = "iam.mail.template.changed"
MailTextAdded models.EventType = "iam.mail.text.added"
MailTextChanged models.EventType = "iam.mail.text.changed"
PasswordComplexityPolicyAdded models.EventType = "iam.policy.password.complexity.added" PasswordComplexityPolicyAdded models.EventType = "iam.policy.password.complexity.added"
PasswordComplexityPolicyChanged models.EventType = "iam.policy.password.complexity.changed" PasswordComplexityPolicyChanged models.EventType = "iam.policy.password.complexity.changed"

View File

@ -11,8 +11,8 @@ import (
func IDPByID(db *gorm.DB, table, idpID string) (*model.IDPConfigView, error) { func IDPByID(db *gorm.DB, table, idpID string) (*model.IDPConfigView, error) {
idp := new(model.IDPConfigView) idp := new(model.IDPConfigView)
userIDQuery := &model.IDPConfigSearchQuery{Key: iam_model.IDPConfigSearchKeyIdpConfigID, Value: idpID, Method: global_model.SearchMethodEquals} idpIDQuery := &model.IDPConfigSearchQuery{Key: iam_model.IDPConfigSearchKeyIdpConfigID, Value: idpID, Method: global_model.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, userIDQuery) query := repository.PrepareGetByQuery(table, idpIDQuery)
err := query(db, idp) err := query(db, idp)
if caos_errs.IsNotFound(err) { if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-Ahq2s", "Errors.IAM.IdpNotExisting") return nil, caos_errs.ThrowNotFound(nil, "VIEW-Ahq2s", "Errors.IAM.IdpNotExisting")

View File

@ -11,8 +11,8 @@ import (
func GetLabelPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.LabelPolicyView, error) { func GetLabelPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.LabelPolicyView, error) {
policy := new(model.LabelPolicyView) policy := new(model.LabelPolicyView)
userIDQuery := &model.LabelPolicySearchQuery{Key: iam_model.LabelPolicySearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals} aggregateIDQuery := &model.LabelPolicySearchQuery{Key: iam_model.LabelPolicySearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, userIDQuery) query := repository.PrepareGetByQuery(table, aggregateIDQuery)
err := query(db, policy) err := query(db, policy)
if caos_errs.IsNotFound(err) { if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-68G11", "Errors.IAM.LabelPolicy.NotExisting") return nil, caos_errs.ThrowNotFound(nil, "VIEW-68G11", "Errors.IAM.LabelPolicy.NotExisting")

View File

@ -11,8 +11,8 @@ import (
func GetLoginPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.LoginPolicyView, error) { func GetLoginPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.LoginPolicyView, error) {
policy := new(model.LoginPolicyView) policy := new(model.LoginPolicyView)
userIDQuery := &model.LoginPolicySearchQuery{Key: iam_model.LoginPolicySearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals} aggregateIDQuery := &model.LoginPolicySearchQuery{Key: iam_model.LoginPolicySearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, userIDQuery) query := repository.PrepareGetByQuery(table, aggregateIDQuery)
err := query(db, policy) err := query(db, policy)
if caos_errs.IsNotFound(err) { if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-Lso0cs", "Errors.IAM.LoginPolicy.NotExisting") return nil, caos_errs.ThrowNotFound(nil, "VIEW-Lso0cs", "Errors.IAM.LoginPolicy.NotExisting")

View File

@ -0,0 +1,32 @@
package view
import (
caos_errs "github.com/caos/zitadel/internal/errors"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_model "github.com/caos/zitadel/internal/model"
"github.com/caos/zitadel/internal/view/repository"
"github.com/jinzhu/gorm"
)
func GetMailTemplateByAggregateID(db *gorm.DB, table, aggregateID string) (*model.MailTemplateView, error) {
template := new(model.MailTemplateView)
aggregateIDQuery := &model.MailTemplateSearchQuery{Key: iam_model.MailTemplateSearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, aggregateIDQuery)
err := query(db, template)
if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-iPnmU", "Errors.IAM.MailTemplate.NotExisting")
}
return template, err
}
func PutMailTemplate(db *gorm.DB, table string, template *model.MailTemplateView) error {
save := repository.PrepareSave(table)
return save(db, template)
}
func DeleteMailTemplate(db *gorm.DB, table, aggregateID string) error {
delete := repository.PrepareDeleteByKey(table, model.MailTemplateSearchKey(iam_model.MailTemplateSearchKeyAggregateID), aggregateID)
return delete(db)
}

View File

@ -0,0 +1,53 @@
package view
import (
caos_errs "github.com/caos/zitadel/internal/errors"
iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_model "github.com/caos/zitadel/internal/model"
"github.com/caos/zitadel/internal/view/repository"
"github.com/jinzhu/gorm"
)
func GetMailTexts(db *gorm.DB, table string, aggregateID string) ([]*model.MailTextView, error) {
texts := make([]*model.MailTextView, 0)
queries := []*iam_model.MailTextSearchQuery{
{
Key: iam_model.MailTextSearchKeyAggregateID,
Value: aggregateID,
Method: global_model.SearchMethodEquals,
},
}
query := repository.PrepareSearchQuery(table, model.MailTextSearchRequest{Queries: queries})
_, err := query(db, &texts)
if err != nil {
return nil, err
}
return texts, nil
}
func GetMailTextByIDs(db *gorm.DB, table, aggregateID string, textType string, language string) (*model.MailTextView, error) {
mailText := new(model.MailTextView)
aggregateIDQuery := &model.MailTextSearchQuery{Key: iam_model.MailTextSearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals}
textTypeQuery := &model.MailTextSearchQuery{Key: iam_model.MailTextSearchKeyMailTextType, Value: textType, Method: global_model.SearchMethodEquals}
languageQuery := &model.MailTextSearchQuery{Key: iam_model.MailTextSearchKeyLanguage, Value: language, Method: global_model.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, aggregateIDQuery, textTypeQuery, languageQuery)
err := query(db, mailText)
if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-IiJjm", "Errors.IAM.MailText.NotExisting")
}
return mailText, err
}
func PutMailText(db *gorm.DB, table string, mailText *model.MailTextView) error {
save := repository.PrepareSave(table)
return save(db, mailText)
}
func DeleteMailText(db *gorm.DB, table, aggregateID string, textType string, language string) error {
aggregateIDSearch := repository.Key{Key: model.MailTextSearchKey(iam_model.MailTextSearchKeyAggregateID), Value: aggregateID}
textTypeSearch := repository.Key{Key: model.MailTextSearchKey(iam_model.MailTextSearchKeyMailTextType), Value: textType}
languageSearch := repository.Key{Key: model.MailTextSearchKey(iam_model.MailTextSearchKeyLanguage), Value: language}
delete := repository.PrepareDeleteByKeys(table, aggregateIDSearch, textTypeSearch, languageSearch)
return delete(db)
}

View File

@ -0,0 +1,80 @@
package model
import (
"encoding/json"
"time"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/model"
)
const (
MailTemplateKeyAggregateID = "aggregate_id"
)
type MailTemplateView struct {
AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:mail_template_state"`
Template []byte `json:"template" gorm:"column:template"`
Default bool `json:"-" gorm:"-"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}
func MailTemplateViewFromModel(template *model.MailTemplateView) *MailTemplateView {
return &MailTemplateView{
AggregateID: template.AggregateID,
Sequence: template.Sequence,
CreationDate: template.CreationDate,
ChangeDate: template.ChangeDate,
Template: template.Template,
Default: template.Default,
}
}
func MailTemplateViewToModel(template *MailTemplateView) *model.MailTemplateView {
return &model.MailTemplateView{
AggregateID: template.AggregateID,
Sequence: template.Sequence,
CreationDate: template.CreationDate,
ChangeDate: template.ChangeDate,
Template: template.Template,
Default: template.Default,
}
}
func (i *MailTemplateView) AppendEvent(event *models.Event) (err error) {
i.Sequence = event.Sequence
i.ChangeDate = event.CreationDate
switch event.Type {
case es_model.MailTemplateAdded, org_es_model.MailTemplateAdded:
i.setRootData(event)
i.CreationDate = event.CreationDate
err = i.SetData(event)
case es_model.MailTemplateChanged, org_es_model.MailTemplateChanged:
i.ChangeDate = event.CreationDate
err = i.SetData(event)
}
return err
}
func (r *MailTemplateView) setRootData(event *models.Event) {
r.AggregateID = event.AggregateID
}
func (r *MailTemplateView) SetData(event *models.Event) error {
if err := json.Unmarshal(event.Data, r); err != nil {
logging.Log("MODEL-YDZmZ").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(err, "MODEL-sKWwO", "Could not unmarshal data")
}
return nil
}

View File

@ -0,0 +1,59 @@
package model
import (
iam_model "github.com/caos/zitadel/internal/iam/model"
global_model "github.com/caos/zitadel/internal/model"
"github.com/caos/zitadel/internal/view/repository"
)
type MailTemplateSearchRequest iam_model.MailTemplateSearchRequest
type MailTemplateSearchQuery iam_model.MailTemplateSearchQuery
type MailTemplateSearchKey iam_model.MailTemplateSearchKey
func (req MailTemplateSearchRequest) GetLimit() uint64 {
return req.Limit
}
func (req MailTemplateSearchRequest) GetOffset() uint64 {
return req.Offset
}
func (req MailTemplateSearchRequest) GetSortingColumn() repository.ColumnKey {
if req.SortingColumn == iam_model.MailTemplateSearchKeyUnspecified {
return nil
}
return MailTemplateSearchKey(req.SortingColumn)
}
func (req MailTemplateSearchRequest) GetAsc() bool {
return req.Asc
}
func (req MailTemplateSearchRequest) GetQueries() []repository.SearchQuery {
result := make([]repository.SearchQuery, len(req.Queries))
for i, q := range req.Queries {
result[i] = MailTemplateSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
}
return result
}
func (req MailTemplateSearchQuery) GetKey() repository.ColumnKey {
return MailTemplateSearchKey(req.Key)
}
func (req MailTemplateSearchQuery) GetMethod() global_model.SearchMethod {
return req.Method
}
func (req MailTemplateSearchQuery) GetValue() interface{} {
return req.Value
}
func (key MailTemplateSearchKey) ToColumnName() string {
switch iam_model.MailTemplateSearchKey(key) {
case iam_model.MailTemplateSearchKeyAggregateID:
return MailTemplateKeyAggregateID
default:
return ""
}
}

View File

@ -0,0 +1,117 @@
package model
import (
"encoding/json"
"time"
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/model"
)
const (
MailTextKeyAggregateID = "aggregate_id"
MailTextKeyMailTextType = "mail_text_type"
MailTextKeyLanguage = "language"
)
type MailTextView struct {
AggregateID string `json:"-" gorm:"column:aggregate_id;primary_key"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
State int32 `json:"-" gorm:"column:mail_text_state"`
MailTextType string `json:"mailTextType" gorm:"column:mail_text_type;primary_key"`
Language string `json:"language" gorm:"column:language;primary_key"`
Title string `json:"title" gorm:"column:title"`
PreHeader string `json:"preHeader" gorm:"column:pre_header"`
Subject string `json:"subject" gorm:"column:subject"`
Greeting string `json:"greeting" gorm:"column:greeting"`
Text string `json:"text" gorm:"column:text"`
ButtonText string `json:"buttonText" gorm:"column:button_text"`
Default bool `json:"-" gorm:"-"`
Sequence uint64 `json:"-" gorm:"column:sequence"`
}
func MailTextViewFromModel(template *model.MailTextView) *MailTextView {
return &MailTextView{
AggregateID: template.AggregateID,
Sequence: template.Sequence,
CreationDate: template.CreationDate,
ChangeDate: template.ChangeDate,
MailTextType: template.MailTextType,
Language: template.Language,
Title: template.Title,
PreHeader: template.PreHeader,
Subject: template.Subject,
Greeting: template.Greeting,
Text: template.Text,
ButtonText: template.ButtonText,
Default: template.Default,
}
}
func MailTextsViewToModel(textsIn []*MailTextView, defaultIn bool) *model.MailTextsView {
return &model.MailTextsView{
Texts: mailTextsViewToModelArr(textsIn, defaultIn),
}
}
func mailTextsViewToModelArr(texts []*MailTextView, defaultIn bool) []*model.MailTextView {
result := make([]*model.MailTextView, len(texts))
for i, r := range texts {
r.Default = defaultIn
result[i] = MailTextViewToModel(r)
}
return result
}
func MailTextViewToModel(template *MailTextView) *model.MailTextView {
return &model.MailTextView{
AggregateID: template.AggregateID,
Sequence: template.Sequence,
CreationDate: template.CreationDate,
ChangeDate: template.ChangeDate,
MailTextType: template.MailTextType,
Language: template.Language,
Title: template.Title,
PreHeader: template.PreHeader,
Subject: template.Subject,
Greeting: template.Greeting,
Text: template.Text,
ButtonText: template.ButtonText,
Default: template.Default,
}
}
func (i *MailTextView) AppendEvent(event *models.Event) (err error) {
i.Sequence = event.Sequence
switch event.Type {
case es_model.MailTextAdded, org_es_model.MailTextAdded:
i.setRootData(event)
i.CreationDate = event.CreationDate
err = i.SetData(event)
case es_model.MailTextChanged, org_es_model.MailTextChanged:
i.ChangeDate = event.CreationDate
err = i.SetData(event)
}
return err
}
func (r *MailTextView) setRootData(event *models.Event) {
r.AggregateID = event.AggregateID
}
func (r *MailTextView) SetData(event *models.Event) error {
if err := json.Unmarshal(event.Data, r); err != nil {
logging.Log("MODEL-UFqAG").WithError(err).Error("could not unmarshal event data")
return caos_errs.ThrowInternal(err, "MODEL-5CVaR", "Could not unmarshal data")
}
return nil
}

View File

@ -0,0 +1,63 @@
package model
import (
iam_model "github.com/caos/zitadel/internal/iam/model"
global_model "github.com/caos/zitadel/internal/model"
"github.com/caos/zitadel/internal/view/repository"
)
type MailTextSearchRequest iam_model.MailTextSearchRequest
type MailTextSearchQuery iam_model.MailTextSearchQuery
type MailTextSearchKey iam_model.MailTextSearchKey
func (req MailTextSearchRequest) GetLimit() uint64 {
return req.Limit
}
func (req MailTextSearchRequest) GetOffset() uint64 {
return req.Offset
}
func (req MailTextSearchRequest) GetSortingColumn() repository.ColumnKey {
if req.SortingColumn == iam_model.MailTextSearchKeyUnspecified {
return nil
}
return MailTextSearchKey(req.SortingColumn)
}
func (req MailTextSearchRequest) GetAsc() bool {
return req.Asc
}
func (req MailTextSearchRequest) GetQueries() []repository.SearchQuery {
result := make([]repository.SearchQuery, len(req.Queries))
for i, q := range req.Queries {
result[i] = MailTextSearchQuery{Key: q.Key, Value: q.Value, Method: q.Method}
}
return result
}
func (req MailTextSearchQuery) GetKey() repository.ColumnKey {
return MailTextSearchKey(req.Key)
}
func (req MailTextSearchQuery) GetMethod() global_model.SearchMethod {
return req.Method
}
func (req MailTextSearchQuery) GetValue() interface{} {
return req.Value
}
func (key MailTextSearchKey) ToColumnName() string {
switch iam_model.MailTextSearchKey(key) {
case iam_model.MailTextSearchKeyAggregateID:
return MailTextKeyAggregateID
case iam_model.MailTextSearchKeyMailTextType:
return MailTextKeyMailTextType
case iam_model.MailTextSearchKeyLanguage:
return MailTextKeyLanguage
default:
return ""
}
}

View File

@ -11,8 +11,8 @@ import (
func GetOrgIAMPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.OrgIAMPolicyView, error) { func GetOrgIAMPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.OrgIAMPolicyView, error) {
policy := new(model.OrgIAMPolicyView) policy := new(model.OrgIAMPolicyView)
userIDQuery := &model.OrgIAMPolicySearchQuery{Key: iam_model.OrgIAMPolicySearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals} aggregateIDQuery := &model.OrgIAMPolicySearchQuery{Key: iam_model.OrgIAMPolicySearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, userIDQuery) query := repository.PrepareGetByQuery(table, aggregateIDQuery)
err := query(db, policy) err := query(db, policy)
if caos_errs.IsNotFound(err) { if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-5fi9s", "Errors.IAM.OrgIAMPolicy.NotExisting") return nil, caos_errs.ThrowNotFound(nil, "VIEW-5fi9s", "Errors.IAM.OrgIAMPolicy.NotExisting")

View File

@ -11,8 +11,8 @@ import (
func GetPasswordAgePolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.PasswordAgePolicyView, error) { func GetPasswordAgePolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.PasswordAgePolicyView, error) {
policy := new(model.PasswordAgePolicyView) policy := new(model.PasswordAgePolicyView)
userIDQuery := &model.PasswordAgePolicySearchQuery{Key: iam_model.PasswordAgePolicySearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals} aggregateIDQuery := &model.PasswordAgePolicySearchQuery{Key: iam_model.PasswordAgePolicySearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, userIDQuery) query := repository.PrepareGetByQuery(table, aggregateIDQuery)
err := query(db, policy) err := query(db, policy)
if caos_errs.IsNotFound(err) { if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-Lso0cs", "Errors.IAM.PasswordAgePolicy.NotExisting") return nil, caos_errs.ThrowNotFound(nil, "VIEW-Lso0cs", "Errors.IAM.PasswordAgePolicy.NotExisting")

View File

@ -11,8 +11,8 @@ import (
func GetPasswordComplexityPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.PasswordComplexityPolicyView, error) { func GetPasswordComplexityPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.PasswordComplexityPolicyView, error) {
policy := new(model.PasswordComplexityPolicyView) policy := new(model.PasswordComplexityPolicyView)
userIDQuery := &model.PasswordComplexityPolicySearchQuery{Key: iam_model.PasswordComplexityPolicySearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals} aggregateIDQuery := &model.PasswordComplexityPolicySearchQuery{Key: iam_model.PasswordComplexityPolicySearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, userIDQuery) query := repository.PrepareGetByQuery(table, aggregateIDQuery)
err := query(db, policy) err := query(db, policy)
if caos_errs.IsNotFound(err) { if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-Lso0cs", "Errors.IAM.PasswordComplexityPolicy.NotExisting") return nil, caos_errs.ThrowNotFound(nil, "VIEW-Lso0cs", "Errors.IAM.PasswordComplexityPolicy.NotExisting")

View File

@ -11,8 +11,8 @@ import (
func GetPasswordLockoutPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.PasswordLockoutPolicyView, error) { func GetPasswordLockoutPolicyByAggregateID(db *gorm.DB, table, aggregateID string) (*model.PasswordLockoutPolicyView, error) {
policy := new(model.PasswordLockoutPolicyView) policy := new(model.PasswordLockoutPolicyView)
userIDQuery := &model.PasswordLockoutPolicySearchQuery{Key: iam_model.PasswordLockoutPolicySearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals} aggregateIDQuery := &model.PasswordLockoutPolicySearchQuery{Key: iam_model.PasswordLockoutPolicySearchKeyAggregateID, Value: aggregateID, Method: global_model.SearchMethodEquals}
query := repository.PrepareGetByQuery(table, userIDQuery) query := repository.PrepareGetByQuery(table, aggregateIDQuery)
err := query(db, policy) err := query(db, policy)
if caos_errs.IsNotFound(err) { if caos_errs.IsNotFound(err) {
return nil, caos_errs.ThrowNotFound(nil, "VIEW-Lso0cs", "Errors.IAM.PasswordLockoutPolicy.NotExisting") return nil, caos_errs.ThrowNotFound(nil, "VIEW-Lso0cs", "Errors.IAM.PasswordLockoutPolicy.NotExisting")

View File

@ -703,3 +703,83 @@ func (repo *OrgRepository) RemovePasswordLockoutPolicy(ctx context.Context) erro
}} }}
return repo.OrgEventstore.RemovePasswordLockoutPolicy(ctx, policy) return repo.OrgEventstore.RemovePasswordLockoutPolicy(ctx, policy)
} }
func (repo *OrgRepository) GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) {
template, err := repo.View.MailTemplateByAggregateID(repo.SystemDefaults.IamID)
if err != nil {
return nil, err
}
template.Default = true
return iam_es_model.MailTemplateViewToModel(template), err
}
func (repo *OrgRepository) GetMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) {
template, err := repo.View.MailTemplateByAggregateID(authz.GetCtxData(ctx).OrgID)
if errors.IsNotFound(err) {
template, err = repo.View.MailTemplateByAggregateID(repo.SystemDefaults.IamID)
if err != nil {
return nil, err
}
template.Default = true
}
if err != nil {
return nil, err
}
return iam_es_model.MailTemplateViewToModel(template), err
}
func (repo *OrgRepository) AddMailTemplate(ctx context.Context, template *iam_model.MailTemplate) (*iam_model.MailTemplate, error) {
template.AggregateID = authz.GetCtxData(ctx).OrgID
return repo.OrgEventstore.AddMailTemplate(ctx, template)
}
func (repo *OrgRepository) ChangeMailTemplate(ctx context.Context, template *iam_model.MailTemplate) (*iam_model.MailTemplate, error) {
template.AggregateID = authz.GetCtxData(ctx).OrgID
return repo.OrgEventstore.ChangeMailTemplate(ctx, template)
}
func (repo *OrgRepository) RemoveMailTemplate(ctx context.Context) error {
template := &iam_model.MailTemplate{ObjectRoot: models.ObjectRoot{
AggregateID: authz.GetCtxData(ctx).OrgID,
}}
return repo.OrgEventstore.RemoveMailTemplate(ctx, template)
}
func (repo *OrgRepository) GetDefaultMailTexts(ctx context.Context) (*iam_model.MailTextsView, error) {
texts, err := repo.View.MailTextsByAggregateID(repo.SystemDefaults.IamID)
if err != nil {
return nil, err
}
return iam_es_model.MailTextsViewToModel(texts, true), err
}
func (repo *OrgRepository) GetMailTexts(ctx context.Context) (*iam_model.MailTextsView, error) {
defaultIn := false
texts, err := repo.View.MailTextsByAggregateID(authz.GetCtxData(ctx).OrgID)
if errors.IsNotFound(err) || len(texts) == 0 {
texts, err = repo.View.MailTextsByAggregateID(repo.SystemDefaults.IamID)
if err != nil {
return nil, err
}
defaultIn = true
}
if err != nil {
return nil, err
}
return iam_es_model.MailTextsViewToModel(texts, defaultIn), err
}
func (repo *OrgRepository) AddMailText(ctx context.Context, text *iam_model.MailText) (*iam_model.MailText, error) {
text.AggregateID = authz.GetCtxData(ctx).OrgID
return repo.OrgEventstore.AddMailText(ctx, text)
}
func (repo *OrgRepository) ChangeMailText(ctx context.Context, text *iam_model.MailText) (*iam_model.MailText, error) {
text.AggregateID = authz.GetCtxData(ctx).OrgID
return repo.OrgEventstore.ChangeMailText(ctx, text)
}
func (repo *OrgRepository) RemoveMailText(ctx context.Context, text *iam_model.MailText) error {
text.AggregateID = authz.GetCtxData(ctx).OrgID
return repo.OrgEventstore.RemoveMailText(ctx, text)
}

View File

@ -103,6 +103,10 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
handler{view, bulkLimit, configs.cycleDuration("PasswordLockoutPolicy"), errorCount, es}), handler{view, bulkLimit, configs.cycleDuration("PasswordLockoutPolicy"), errorCount, es}),
newOrgIAMPolicy( newOrgIAMPolicy(
handler{view, bulkLimit, configs.cycleDuration("OrgIAMPolicy"), errorCount, es}), handler{view, bulkLimit, configs.cycleDuration("OrgIAMPolicy"), errorCount, es}),
newMailTemplate(
handler{view, bulkLimit, configs.cycleDuration("MailTemplate"), errorCount, es}),
newMailText(
handler{view, bulkLimit, configs.cycleDuration("MailText"), errorCount, es}),
} }
} }

View File

@ -0,0 +1,107 @@
package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
type MailTemplate struct {
handler
subscription *eventstore.Subscription
}
func newMailTemplate(handler handler) *MailTemplate {
h := &MailTemplate{
handler: handler,
}
h.subscribe()
return h
}
func (m *MailTemplate) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
const (
mailTemplateTable = "management.mail_templates"
)
func (m *MailTemplate) ViewModel() string {
return mailTemplateTable
}
func (_ *MailTemplate) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (p *MailTemplate) CurrentSequence(event *models.Event) (uint64, error) {
sequence, err := p.view.GetLatestMailTemplateSequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *MailTemplate) EventQuery() (*models.SearchQuery, error) {
sequence, err := m.view.GetLatestMailTemplateSequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *MailTemplate) Reduce(event *models.Event) (err error) {
switch event.AggregateType {
case model.OrgAggregate, iam_es_model.IAMAggregate:
err = m.processMailTemplate(event)
}
return err
}
func (m *MailTemplate) processMailTemplate(event *models.Event) (err error) {
template := new(iam_model.MailTemplateView)
switch event.Type {
case iam_es_model.MailTemplateAdded, model.MailTemplateAdded:
err = template.AppendEvent(event)
case iam_es_model.MailTemplateChanged, model.MailTemplateChanged:
template, err = m.view.MailTemplateByAggregateID(event.AggregateID)
if err != nil {
return err
}
err = template.AppendEvent(event)
case model.MailTemplateRemoved:
return m.view.DeleteMailTemplate(event.AggregateID, event)
default:
return m.view.ProcessedMailTemplateSequence(event)
}
if err != nil {
return err
}
return m.view.PutMailTemplate(template, event)
}
func (m *MailTemplate) OnError(event *models.Event, err error) error {
logging.LogWithFields("SPOOL-4Djo9", "id", event.AggregateID).WithError(err).Warn("something went wrong in label template handler")
return spooler.HandleError(event, err, m.view.GetLatestMailTemplateFailedEvent, m.view.ProcessedMailTemplateFailedEvent, m.view.ProcessedMailTemplateSequence, m.errorCountUntilSkip)
}
func (o *MailTemplate) OnSuccess() error {
return spooler.HandleSuccess(o.view.UpdateMailTemplateSpoolerRunTimestamp)
}

View File

@ -0,0 +1,113 @@
package handler
import (
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/eventstore/models"
es_models "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/eventstore/query"
"github.com/caos/zitadel/internal/eventstore/spooler"
iam_model "github.com/caos/zitadel/internal/iam/repository/view/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
type MailText struct {
handler
subscription *eventstore.Subscription
}
func newMailText(handler handler) *MailText {
h := &MailText{
handler: handler,
}
h.subscribe()
return h
}
func (m *MailText) subscribe() {
m.subscription = m.es.Subscribe(m.AggregateTypes()...)
go func() {
for event := range m.subscription.Events {
query.ReduceEvent(m, event)
}
}()
}
const (
mailTextTable = "management.mail_texts"
)
func (m *MailText) ViewModel() string {
return mailTextTable
}
func (_ *MailText) AggregateTypes() []es_models.AggregateType {
return []es_models.AggregateType{model.OrgAggregate, iam_es_model.IAMAggregate}
}
func (p *MailText) CurrentSequence(event *models.Event) (uint64, error) {
sequence, err := p.view.GetLatestMailTextSequence(string(event.AggregateType))
if err != nil {
return 0, err
}
return sequence.CurrentSequence, nil
}
func (m *MailText) EventQuery() (*models.SearchQuery, error) {
sequence, err := m.view.GetLatestMailTextSequence("")
if err != nil {
return nil, err
}
return es_models.NewSearchQuery().
AggregateTypeFilter(m.AggregateTypes()...).
LatestSequenceFilter(sequence.CurrentSequence), nil
}
func (m *MailText) Reduce(event *models.Event) (err error) {
switch event.AggregateType {
case model.OrgAggregate, iam_es_model.IAMAggregate:
err = m.processMailText(event)
}
return err
}
func (m *MailText) processMailText(event *models.Event) (err error) {
text := new(iam_model.MailTextView)
switch event.Type {
case iam_es_model.MailTextAdded, model.MailTextAdded:
err = text.AppendEvent(event)
case iam_es_model.MailTextChanged, model.MailTextChanged:
err = text.SetData(event)
if err != nil {
return err
}
text, err = m.view.MailTextByIDs(event.AggregateID, text.MailTextType, text.Language)
if err != nil {
return err
}
text.ChangeDate = event.CreationDate
err = text.AppendEvent(event)
case model.MailTextRemoved:
err = text.SetData(event)
return m.view.DeleteMailText(event.AggregateID, text.MailTextType, text.Language, event)
default:
return m.view.ProcessedMailTextSequence(event)
}
if err != nil {
return err
}
return m.view.PutMailText(text, event)
}
func (m *MailText) OnError(event *models.Event, err error) error {
logging.LogWithFields("SPOOL-4Djo9", "id", event.AggregateID).WithError(err).Warn("something went wrong in label text handler")
return spooler.HandleError(event, err, m.view.GetLatestMailTextFailedEvent, m.view.ProcessedMailTextFailedEvent, m.view.ProcessedMailTextSequence, m.errorCountUntilSkip)
}
func (o *MailText) OnSuccess() error {
return spooler.HandleSuccess(o.view.UpdateMailTextSpoolerRunTimestamp)
}

View File

@ -0,0 +1,53 @@
package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
mailTemplateTable = "management.mail_templates"
)
func (v *View) MailTemplateByAggregateID(aggregateID string) (*model.MailTemplateView, error) {
return view.GetMailTemplateByAggregateID(v.Db, mailTemplateTable, aggregateID)
}
func (v *View) PutMailTemplate(template *model.MailTemplateView, event *models.Event) error {
err := view.PutMailTemplate(v.Db, mailTemplateTable, template)
if err != nil {
return err
}
return v.ProcessedMailTemplateSequence(event)
}
func (v *View) DeleteMailTemplate(aggregateID string, event *models.Event) error {
err := view.DeleteMailTemplate(v.Db, mailTemplateTable, aggregateID)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedMailTemplateSequence(event)
}
func (v *View) GetLatestMailTemplateSequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(mailTemplateTable, aggregateType)
}
func (v *View) ProcessedMailTemplateSequence(event *models.Event) error {
return v.saveCurrentSequence(mailTemplateTable, event)
}
func (v *View) UpdateMailTemplateSpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(mailTemplateTable)
}
func (v *View) GetLatestMailTemplateFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(mailTemplateTable, sequence)
}
func (v *View) ProcessedMailTemplateFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@ -0,0 +1,57 @@
package view
import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
global_view "github.com/caos/zitadel/internal/view/repository"
)
const (
mailTextTable = "management.mail_texts"
)
func (v *View) MailTextsByAggregateID(aggregateID string) ([]*model.MailTextView, error) {
return view.GetMailTexts(v.Db, mailTextTable, aggregateID)
}
func (v *View) MailTextByIDs(aggregateID string, textType string, language string) (*model.MailTextView, error) {
return view.GetMailTextByIDs(v.Db, mailTextTable, aggregateID, textType, language)
}
func (v *View) PutMailText(template *model.MailTextView, event *models.Event) error {
err := view.PutMailText(v.Db, mailTextTable, template)
if err != nil {
return err
}
return v.ProcessedMailTextSequence(event)
}
func (v *View) DeleteMailText(aggregateID string, textType string, language string, event *models.Event) error {
err := view.DeleteMailText(v.Db, mailTextTable, aggregateID, textType, language)
if err != nil && !errors.IsNotFound(err) {
return err
}
return v.ProcessedMailTextSequence(event)
}
func (v *View) GetLatestMailTextSequence(aggregateType string) (*global_view.CurrentSequence, error) {
return v.latestSequence(mailTextTable, aggregateType)
}
func (v *View) ProcessedMailTextSequence(event *models.Event) error {
return v.saveCurrentSequence(mailTextTable, event)
}
func (v *View) UpdateMailTextSpoolerRunTimestamp() error {
return v.updateSpoolerRunSequence(mailTextTable)
}
func (v *View) GetLatestMailTextFailedEvent(sequence uint64) (*global_view.FailedEvent, error) {
return v.latestFailedEvent(mailTextTable, sequence)
}
func (v *View) ProcessedMailTextFailedEvent(failedEvent *global_view.FailedEvent) error {
return v.saveFailedEvent(failedEvent)
}

View File

@ -2,6 +2,7 @@ package repository
import ( import (
"context" "context"
iam_model "github.com/caos/zitadel/internal/iam/model" iam_model "github.com/caos/zitadel/internal/iam/model"
org_model "github.com/caos/zitadel/internal/org/model" org_model "github.com/caos/zitadel/internal/org/model"
@ -73,4 +74,16 @@ type OrgRepository interface {
AddPasswordLockoutPolicy(ctx context.Context, policy *iam_model.PasswordLockoutPolicy) (*iam_model.PasswordLockoutPolicy, error) AddPasswordLockoutPolicy(ctx context.Context, policy *iam_model.PasswordLockoutPolicy) (*iam_model.PasswordLockoutPolicy, error)
ChangePasswordLockoutPolicy(ctx context.Context, policy *iam_model.PasswordLockoutPolicy) (*iam_model.PasswordLockoutPolicy, error) ChangePasswordLockoutPolicy(ctx context.Context, policy *iam_model.PasswordLockoutPolicy) (*iam_model.PasswordLockoutPolicy, error)
RemovePasswordLockoutPolicy(ctx context.Context) error RemovePasswordLockoutPolicy(ctx context.Context) error
GetDefaultMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
GetMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error)
AddMailTemplate(ctx context.Context, template *iam_model.MailTemplate) (*iam_model.MailTemplate, error)
ChangeMailTemplate(ctx context.Context, template *iam_model.MailTemplate) (*iam_model.MailTemplate, error)
RemoveMailTemplate(ctx context.Context) error
GetDefaultMailTexts(ctx context.Context) (*iam_model.MailTextsView, error)
GetMailTexts(ctx context.Context) (*iam_model.MailTextsView, error)
AddMailText(ctx context.Context, mailText *iam_model.MailText) (*iam_model.MailText, error)
ChangeMailText(ctx context.Context, mailText *iam_model.MailText) (*iam_model.MailText, error)
RemoveMailText(ctx context.Context, mailText *iam_model.MailText) error
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
sd "github.com/caos/zitadel/internal/config/systemdefaults" sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/models"
@ -24,10 +25,19 @@ import (
) )
const ( const (
notificationTable = "notification.notifications" notificationTable = "notification.notifications"
NotifyUserID = "NOTIFICATION" NotifyUserID = "NOTIFICATION"
labelPolicyTableOrg = "management.label_policies" labelPolicyTableOrg = "management.label_policies"
labelPolicyTableDef = "adminapi.label_policies" labelPolicyTableDef = "adminapi.label_policies"
mailTemplateTableOrg = "management.mail_templates"
mailTemplateTableDef = "adminapi.mail_templates"
mailTextTableOrg = "management.mail_texts"
mailTextTableDef = "adminapi.mail_texts"
mailTextTypeDomainClaimed = "DomainClaimed"
mailTextTypeInitCode = "InitCode"
mailTextTypePasswordReset = "PasswordReset"
mailTextTypeVerifyEmail = "VerifyEmail"
mailTextTypeVerifyPhone = "VerifyPhone"
) )
type Notification struct { type Notification struct {
@ -135,11 +145,22 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
return err return err
} }
template, err := n.getMailTemplate(context.Background())
if err != nil {
return err
}
user, err := n.view.NotifyUserByID(event.AggregateID) user, err := n.view.NotifyUserByID(event.AggregateID)
if err != nil { if err != nil {
return err return err
} }
err = types.SendUserInitCode(n.statikDir, n.i18n, user, initCode, n.systemDefaults, n.AesCrypto, colors)
text, err := n.getMailText(context.Background(), mailTextTypeInitCode, user.PreferredLanguage[len(user.PreferredLanguage)-2:])
if err != nil {
return err
}
err = types.SendUserInitCode(string(template.Template), text, user, initCode, n.systemDefaults, n.AesCrypto, colors)
if err != nil { if err != nil {
return err return err
} }
@ -163,11 +184,21 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
return err return err
} }
template, err := n.getMailTemplate(context.Background())
if err != nil {
return err
}
user, err := n.view.NotifyUserByID(event.AggregateID) user, err := n.view.NotifyUserByID(event.AggregateID)
if err != nil { if err != nil {
return err return err
} }
err = types.SendPasswordCode(n.statikDir, n.i18n, user, pwCode, n.systemDefaults, n.AesCrypto, colors)
text, err := n.getMailText(context.Background(), mailTextTypePasswordReset, user.PreferredLanguage[len(user.PreferredLanguage)-2:])
if err != nil {
return err
}
err = types.SendPasswordCode(string(template.Template), text, user, pwCode, n.systemDefaults, n.AesCrypto, colors)
if err != nil { if err != nil {
return err return err
} }
@ -191,11 +222,22 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
return err return err
} }
template, err := n.getMailTemplate(context.Background())
if err != nil {
return err
}
user, err := n.view.NotifyUserByID(event.AggregateID) user, err := n.view.NotifyUserByID(event.AggregateID)
if err != nil { if err != nil {
return err return err
} }
err = types.SendEmailVerificationCode(n.statikDir, n.i18n, user, emailCode, n.systemDefaults, n.AesCrypto, colors)
text, err := n.getMailText(context.Background(), mailTextTypeVerifyEmail, user.PreferredLanguage[len(user.PreferredLanguage)-2:])
if err != nil {
return err
}
err = types.SendEmailVerificationCode(string(template.Template), text, user, emailCode, n.systemDefaults, n.AesCrypto, colors)
if err != nil { if err != nil {
return err return err
} }
@ -238,7 +280,21 @@ func (n *Notification) handleDomainClaimed(event *models.Event) (err error) {
if err != nil { if err != nil {
return err return err
} }
err = types.SendDomainClaimed(n.statikDir, n.i18n, user, data["userName"], n.systemDefaults) colors, err := n.getLabelPolicy(context.Background())
if err != nil {
return err
}
template, err := n.getMailTemplate(context.Background())
if err != nil {
return err
}
text, err := n.getMailText(context.Background(), mailTextTypeDomainClaimed, user.PreferredLanguage[len(user.PreferredLanguage)-2:])
if err != nil {
return err
}
err = types.SendDomainClaimed(string(template.Template), text, user, data["userName"], n.systemDefaults, colors)
if err != nil { if err != nil {
return err return err
} }
@ -306,3 +362,39 @@ func (n *Notification) getLabelPolicy(ctx context.Context) (*iam_model.LabelPoli
} }
return iam_es_model.LabelPolicyViewToModel(policy), err return iam_es_model.LabelPolicyViewToModel(policy), err
} }
// Read organization specific template
func (n *Notification) getMailTemplate(ctx context.Context) (*iam_model.MailTemplateView, error) {
// read from Org
template, err := n.view.MailTemplateByAggregateID(authz.GetCtxData(ctx).OrgID, mailTemplateTableOrg)
if errors.IsNotFound(err) {
// read from default
template, err = n.view.MailTemplateByAggregateID(n.systemDefaults.IamID, mailTemplateTableDef)
if err != nil {
return nil, err
}
template.Default = true
}
if err != nil {
return nil, err
}
return iam_es_model.MailTemplateViewToModel(template), err
}
// Read organization specific texts
func (n *Notification) getMailText(ctx context.Context, textType string, language string) (*iam_model.MailTextView, error) {
// read from Org
mailText, err := n.view.MailTextByIDs(authz.GetCtxData(ctx).OrgID, textType, language, mailTextTableOrg)
if errors.IsNotFound(err) {
// read from default
mailText, err = n.view.MailTextByIDs(n.systemDefaults.IamID, textType, language, mailTextTableDef)
if err != nil {
return nil, err
}
mailText.Default = true
}
if err != nil {
return nil, err
}
return iam_es_model.MailTextViewToModel(mailText), err
}

View File

@ -0,0 +1,10 @@
package view
import (
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
)
func (v *View) MailTemplateByAggregateID(aggregateID string, mailTemplateTableVar string) (*model.MailTemplateView, error) {
return view.GetMailTemplateByAggregateID(v.Db, mailTemplateTableVar, aggregateID)
}

View File

@ -0,0 +1,10 @@
package view
import (
"github.com/caos/zitadel/internal/iam/repository/view"
"github.com/caos/zitadel/internal/iam/repository/view/model"
)
func (v *View) MailTextByIDs(aggregateID string, textType string, language string, mailTextTableVar string) (*model.MailTextView, error) {
return view.GetMailTextByIDs(v.Db, mailTextTableVar, aggregateID, textType, language)
}

View File

@ -12,23 +12,21 @@ const (
templateFileName = "template.html" templateFileName = "template.html"
) )
func GetParsedTemplate(dir http.FileSystem, contentData interface{}) (string, error) { func GetParsedTemplate(mailhtml string, contentData interface{}) (string, error) {
template, err := ParseTemplateFile(dir, "", contentData) template, err := ParseTemplateFile(mailhtml, contentData)
if err != nil { if err != nil {
return "", err return "", err
} }
return ParseTemplateText(template, contentData) return ParseTemplateText(template, contentData)
} }
func ParseTemplateFile(dir http.FileSystem, fileName string, data interface{}) (string, error) { func ParseTemplateFile(mailhtml string, data interface{}) (string, error) {
if fileName == "" { tmpl, err := template.New("tmpl").Parse(mailhtml)
fileName = templateFileName
}
template, err := readFile(dir, fileName)
if err != nil { if err != nil {
return "", err return "", err
} }
return parseTemplate(template, data)
return parseTemplate(tmpl, data)
} }
func ParseTemplateText(text string, data interface{}) (string, error) { func ParseTemplateText(text string, data interface{}) (string, error) {
@ -63,3 +61,20 @@ func readFile(dir http.FileSystem, fileName string) (*template.Template, error)
} }
return tmpl, nil return tmpl, nil
} }
func readFileFromDatabase(dir http.FileSystem, fileName string) (*template.Template, error) {
f, err := dir.Open(templatesPath + "/" + fileName)
if err != nil {
return nil, err
}
defer f.Close()
content, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
tmpl, err := template.New(fileName).Parse(string(content))
if err != nil {
return nil, err
}
return tmpl, nil
}

View File

@ -1,11 +1,11 @@
package types package types
import ( import (
"net/http" "html"
"strings" "strings"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/i18n" iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/notification/templates"
view_model "github.com/caos/zitadel/internal/user/repository/view/model" view_model "github.com/caos/zitadel/internal/user/repository/view/model"
) )
@ -15,7 +15,7 @@ type DomainClaimedData struct {
URL string URL string
} }
func SendDomainClaimed(dir http.FileSystem, i18n *i18n.Translator, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults) error { func SendDomainClaimed(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, username string, systemDefaults systemdefaults.SystemDefaults, colors *iam_model.LabelPolicyView) error {
url, err := templates.ParseTemplateText(systemDefaults.Notifications.Endpoints.DomainClaimed, &UrlData{UserID: user.ID}) url, err := templates.ParseTemplateText(systemDefaults.Notifications.Endpoints.DomainClaimed, &UrlData{UserID: user.ID})
if err != nil { if err != nil {
return err return err
@ -27,11 +27,28 @@ func SendDomainClaimed(dir http.FileSystem, i18n *i18n.Translator, user *view_mo
"TempUsername": username, "TempUsername": username,
"Domain": strings.Split(user.LastEmail, "@")[1], "Domain": strings.Split(user.LastEmail, "@")[1],
} }
systemDefaults.Notifications.TemplateData.DomainClaimed.Translate(i18n, args, user.PreferredLanguage)
data := &DomainClaimedData{TemplateData: systemDefaults.Notifications.TemplateData.DomainClaimed, URL: url} text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
template, err := templates.GetParsedTemplate(dir, data) text.Text, err = templates.ParseTemplateText(text.Text, args)
text.Text = html.UnescapeString(text.Text)
emailCodeData := &DomainClaimedData{
TemplateData: templates.TemplateData{
Title: text.Title,
PreHeader: text.PreHeader,
Subject: text.Subject,
Greeting: text.Greeting,
Text: html.UnescapeString(text.Text),
Href: url,
ButtonText: text.ButtonText,
PrimaryColor: colors.PrimaryColor,
SecondaryColor: colors.SecondaryColor,
},
URL: url,
}
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
if err != nil { if err != nil {
return err return err
} }
return generateEmail(user, systemDefaults.Notifications.TemplateData.DomainClaimed.Subject, template, systemDefaults.Notifications, true) return generateEmail(user, text.Subject, template, systemDefaults.Notifications, true)
} }

View File

@ -1,11 +1,10 @@
package types package types
import ( import (
"net/http" "html"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/i18n"
iam_model "github.com/caos/zitadel/internal/iam/model" iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/notification/templates"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
@ -17,7 +16,7 @@ type EmailVerificationCodeData struct {
URL string URL string
} }
func SendEmailVerificationCode(dir http.FileSystem, i18n *i18n.Translator, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView) error { func SendEmailVerificationCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.EmailCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView) error {
codeString, err := crypto.DecryptString(code.Code, alg) codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil { if err != nil {
return err return err
@ -31,15 +30,29 @@ func SendEmailVerificationCode(dir http.FileSystem, i18n *i18n.Translator, user
"LastName": user.LastName, "LastName": user.LastName,
"Code": codeString, "Code": codeString,
} }
systemDefaults.Notifications.TemplateData.VerifyEmail.Translate(i18n, args, user.PreferredLanguage)
emailCodeData := &EmailVerificationCodeData{TemplateData: systemDefaults.Notifications.TemplateData.VerifyEmail, URL: url}
// Set the color in initCodeData text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
emailCodeData.PrimaryColor = colors.PrimaryColor text.Text, err = templates.ParseTemplateText(text.Text, args)
emailCodeData.SecondaryColor = colors.SecondaryColor text.Text = html.UnescapeString(text.Text)
template, err := templates.GetParsedTemplate(dir, emailCodeData)
emailCodeData := &EmailVerificationCodeData{
TemplateData: templates.TemplateData{
Title: text.Title,
PreHeader: text.PreHeader,
Subject: text.Subject,
Greeting: text.Greeting,
Text: html.UnescapeString(text.Text),
Href: url,
ButtonText: text.ButtonText,
PrimaryColor: colors.PrimaryColor,
SecondaryColor: colors.SecondaryColor,
},
URL: url,
}
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
if err != nil { if err != nil {
return err return err
} }
return generateEmail(user, systemDefaults.Notifications.TemplateData.VerifyEmail.Subject, template, systemDefaults.Notifications, true) return generateEmail(user, text.Subject, template, systemDefaults.Notifications, true)
} }

View File

@ -1,11 +1,10 @@
package types package types
import ( import (
"net/http" "html"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/i18n"
iam_model "github.com/caos/zitadel/internal/iam/model" iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/notification/templates"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
@ -23,7 +22,7 @@ type UrlData struct {
PasswordSet bool PasswordSet bool
} }
func SendUserInitCode(dir http.FileSystem, i18n *i18n.Translator, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView) error { func SendUserInitCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.InitUserCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView) error {
codeString, err := crypto.DecryptString(code.Code, alg) codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil { if err != nil {
return err return err
@ -38,15 +37,28 @@ func SendUserInitCode(dir http.FileSystem, i18n *i18n.Translator, user *view_mod
"Code": codeString, "Code": codeString,
"PreferredLoginName": user.PreferredLoginName, "PreferredLoginName": user.PreferredLoginName,
} }
systemDefaults.Notifications.TemplateData.InitCode.Translate(i18n, args, user.PreferredLanguage)
initCodeData := &InitCodeEmailData{TemplateData: systemDefaults.Notifications.TemplateData.InitCode, URL: url}
// Set the color in initCodeData text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
initCodeData.PrimaryColor = colors.PrimaryColor text.Text, err = templates.ParseTemplateText(text.Text, args)
initCodeData.SecondaryColor = colors.SecondaryColor text.Text = html.UnescapeString(text.Text)
template, err := templates.GetParsedTemplate(dir, initCodeData)
emailCodeData := &InitCodeEmailData{
TemplateData: templates.TemplateData{
Title: text.Title,
PreHeader: text.PreHeader,
Subject: text.Subject,
Greeting: text.Greeting,
Text: html.UnescapeString(text.Text),
Href: url,
ButtonText: text.ButtonText,
PrimaryColor: colors.PrimaryColor,
SecondaryColor: colors.SecondaryColor,
},
URL: url,
}
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
if err != nil { if err != nil {
return err return err
} }
return generateEmail(user, systemDefaults.Notifications.TemplateData.InitCode.Subject, template, systemDefaults.Notifications, true) return generateEmail(user, text.Subject, template, systemDefaults.Notifications, true)
} }

View File

@ -1,11 +1,10 @@
package types package types
import ( import (
"net/http" "html"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/i18n"
iam_model "github.com/caos/zitadel/internal/iam/model" iam_model "github.com/caos/zitadel/internal/iam/model"
"github.com/caos/zitadel/internal/notification/templates" "github.com/caos/zitadel/internal/notification/templates"
es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model" es_model "github.com/caos/zitadel/internal/user/repository/eventsourcing/model"
@ -19,7 +18,7 @@ type PasswordCodeData struct {
URL string URL string
} }
func SendPasswordCode(dir http.FileSystem, i18n *i18n.Translator, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView) error { func SendPasswordCode(mailhtml string, text *iam_model.MailTextView, user *view_model.NotifyUser, code *es_model.PasswordCode, systemDefaults systemdefaults.SystemDefaults, alg crypto.EncryptionAlgorithm, colors *iam_model.LabelPolicyView) error {
codeString, err := crypto.DecryptString(code.Code, alg) codeString, err := crypto.DecryptString(code.Code, alg)
if err != nil { if err != nil {
return err return err
@ -33,15 +32,30 @@ func SendPasswordCode(dir http.FileSystem, i18n *i18n.Translator, user *view_mod
"LastName": user.LastName, "LastName": user.LastName,
"Code": codeString, "Code": codeString,
} }
systemDefaults.Notifications.TemplateData.PasswordReset.Translate(i18n, args, user.PreferredLanguage)
passwordCodeData := &PasswordCodeData{TemplateData: systemDefaults.Notifications.TemplateData.PasswordReset, FirstName: user.FirstName, LastName: user.LastName, URL: url}
// Set the color in initCodeData text.Greeting, err = templates.ParseTemplateText(text.Greeting, args)
passwordCodeData.PrimaryColor = colors.PrimaryColor text.Text, err = templates.ParseTemplateText(text.Text, args)
passwordCodeData.SecondaryColor = colors.SecondaryColor text.Text = html.UnescapeString(text.Text)
template, err := templates.GetParsedTemplate(dir, passwordCodeData)
emailCodeData := &PasswordCodeData{
TemplateData: templates.TemplateData{
Title: text.Title,
PreHeader: text.PreHeader,
Subject: text.Subject,
Greeting: text.Greeting,
Text: html.UnescapeString(text.Text),
Href: url,
ButtonText: text.ButtonText,
PrimaryColor: colors.PrimaryColor,
SecondaryColor: colors.SecondaryColor,
},
FirstName: user.FirstName,
LastName: user.LastName,
URL: url,
}
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
if err != nil { if err != nil {
return err return err
} }
return generateEmail(user, systemDefaults.Notifications.TemplateData.PasswordReset.Subject, template, systemDefaults.Notifications, false) return generateEmail(user, text.Subject, template, systemDefaults.Notifications, true)
} }

View File

@ -19,6 +19,8 @@ type Org struct {
OrgIamPolicy *iam_model.OrgIAMPolicy OrgIamPolicy *iam_model.OrgIAMPolicy
LoginPolicy *iam_model.LoginPolicy LoginPolicy *iam_model.LoginPolicy
LabelPolicy *iam_model.LabelPolicy LabelPolicy *iam_model.LabelPolicy
MailTemplate *iam_model.MailTemplate
MailTexts []*iam_model.MailText
PasswordComplexityPolicy *iam_model.PasswordComplexityPolicy PasswordComplexityPolicy *iam_model.PasswordComplexityPolicy
PasswordAgePolicy *iam_model.PasswordAgePolicy PasswordAgePolicy *iam_model.PasswordAgePolicy
PasswordLockoutPolicy *iam_model.PasswordLockoutPolicy PasswordLockoutPolicy *iam_model.PasswordLockoutPolicy

View File

@ -1153,3 +1153,121 @@ func (es *OrgEventstore) RemovePasswordLockoutPolicy(ctx context.Context, policy
addAggregate := PasswordLockoutPolicyRemovedAggregate(es.Eventstore.AggregateCreator(), repoOrg) addAggregate := PasswordLockoutPolicyRemovedAggregate(es.Eventstore.AggregateCreator(), repoOrg)
return es_sdk.Push(ctx, es.PushAggregates, repoOrg.AppendEvents, addAggregate) return es_sdk.Push(ctx, es.PushAggregates, repoOrg.AppendEvents, addAggregate)
} }
func (es *OrgEventstore) AddMailTemplate(ctx context.Context, template *iam_model.MailTemplate) (*iam_model.MailTemplate, error) {
if template == nil || !template.IsValid() {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-nb66d", "Errors.Org.MailTemplateInvalid")
}
org, err := es.OrgByID(ctx, org_model.NewOrg(template.AggregateID))
if err != nil {
return nil, err
}
repoOrg := model.OrgFromModel(org)
repoMailTemplate := iam_es_model.MailTemplateFromModel(template)
addAggregate := MailTemplateAddedAggregate(es.Eventstore.AggregateCreator(), repoOrg, repoMailTemplate)
err = es_sdk.Push(ctx, es.PushAggregates, repoOrg.AppendEvents, addAggregate)
if err != nil {
return nil, err
}
return iam_es_model.MailTemplateToModel(repoOrg.MailTemplate), nil
}
func (es *OrgEventstore) ChangeMailTemplate(ctx context.Context, template *iam_model.MailTemplate) (*iam_model.MailTemplate, error) {
if template == nil || !template.IsValid() {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-FV2qE", "Errors.Org.MailTemplateInvalid")
}
org, err := es.OrgByID(ctx, org_model.NewOrg(template.AggregateID))
if err != nil {
return nil, err
}
repoOrg := model.OrgFromModel(org)
repoMailTemplate := iam_es_model.MailTemplateFromModel(template)
addAggregate := MailTemplateChangedAggregate(es.Eventstore.AggregateCreator(), repoOrg, repoMailTemplate)
err = es_sdk.Push(ctx, es.PushAggregates, repoOrg.AppendEvents, addAggregate)
if err != nil {
return nil, err
}
repoOrg.MailTemplate.Template = repoMailTemplate.Template
return iam_es_model.MailTemplateToModel(repoOrg.MailTemplate), nil
}
func (es *OrgEventstore) RemoveMailTemplate(ctx context.Context, template *iam_model.MailTemplate) error {
if template == nil || !template.IsValid() {
return errors.ThrowPreconditionFailed(nil, "EVENT-LulaW", "Errors.Org.MailTemplate.Invalid")
}
org, err := es.OrgByID(ctx, org_model.NewOrg(template.AggregateID))
if err != nil {
return err
}
repoOrg := model.OrgFromModel(org)
addAggregate := MailTemplateRemovedAggregate(es.Eventstore.AggregateCreator(), repoOrg)
return es_sdk.Push(ctx, es.PushAggregates, repoOrg.AppendEvents, addAggregate)
}
func (es *OrgEventstore) AddMailText(ctx context.Context, mailtext *iam_model.MailText) (*iam_model.MailText, error) {
if mailtext == nil || !mailtext.IsValid() {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-108Iz", "Errors.Org.MailTextInvalid")
}
org, err := es.OrgByID(ctx, org_model.NewOrg(mailtext.AggregateID))
if err != nil {
return nil, err
}
repoOrg := model.OrgFromModel(org)
repoMailText := iam_es_model.MailTextFromModel(mailtext)
addAggregate := MailTextAddedAggregate(es.Eventstore.AggregateCreator(), repoOrg, repoMailText)
err = es_sdk.Push(ctx, es.PushAggregates, repoOrg.AppendEvents, addAggregate)
if err != nil {
return nil, err
}
if _, r := iam_es_model.GetMailText(repoOrg.MailTexts, repoMailText.MailTextType, repoMailText.Language); r != nil {
return iam_es_model.MailTextToModel(r), nil
}
return nil, errors.ThrowInternal(nil, "EVENT-oc1GN", "Errors.Internal")
}
func (es *OrgEventstore) ChangeMailText(ctx context.Context, mailtext *iam_model.MailText) (*iam_model.MailText, error) {
if mailtext == nil || !mailtext.IsValid() {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-fdbqE", "Errors.Org.MailTextInvalid")
}
org, err := es.OrgByID(ctx, org_model.NewOrg(mailtext.AggregateID))
if err != nil {
return nil, err
}
repoOrg := model.OrgFromModel(org)
repoMailText := iam_es_model.MailTextFromModel(mailtext)
addAggregate := MailTextChangedAggregate(es.Eventstore.AggregateCreator(), repoOrg, repoMailText)
err = es_sdk.Push(ctx, es.PushAggregates, repoOrg.AppendEvents, addAggregate)
if err != nil {
return nil, err
}
if _, r := iam_es_model.GetMailText(repoOrg.MailTexts, mailtext.MailTextType, mailtext.Language); r != nil {
return iam_es_model.MailTextToModel(r), nil
}
return nil, errors.ThrowInternal(nil, "EVENT-F2whI", "Errors.Internal")
}
func (es *OrgEventstore) RemoveMailText(ctx context.Context, mailtext *iam_model.MailText) error {
if mailtext == nil || !mailtext.IsValid() {
return errors.ThrowPreconditionFailed(nil, "EVENT-LulaW", "Errors.Org.MailText.Invalid")
}
org, err := es.OrgByID(ctx, org_model.NewOrg(mailtext.AggregateID))
if err != nil {
return err
}
repoOrg := model.OrgFromModel(org)
repoMailText := iam_es_model.MailTextFromModel(mailtext)
addAggregate := MailTextRemovedAggregate(es.Eventstore.AggregateCreator(), repoOrg, repoMailText)
return es_sdk.Push(ctx, es.PushAggregates, repoOrg.AppendEvents, addAggregate)
}

View File

@ -192,3 +192,31 @@ func GetMockChangesOrgWithLabelPolicy(ctrl *gomock.Controller) *OrgEventstore {
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil) mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
return GetMockedEventstore(ctrl, mockEs) return GetMockedEventstore(ctrl, mockEs)
} }
func GetMockChangesOrgWithMailTemplate(ctrl *gomock.Controller) *OrgEventstore {
orgData, _ := json.Marshal(model.Org{Name: "MusterOrg"})
mailTemplate, _ := json.Marshal(iam_es_model.MailTemplate{Template: []byte("<!doctype htm>")})
events := []*es_models.Event{
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.OrgAdded, Data: orgData},
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.MailTemplateAdded, Data: mailTemplate},
}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
return GetMockedEventstore(ctrl, mockEs)
}
func GetMockChangesOrgWithMailText(ctrl *gomock.Controller) *OrgEventstore {
orgData, _ := json.Marshal(model.Org{Name: "MusterOrg"})
mailText, _ := json.Marshal(iam_es_model.MailText{MailTextType: "Type", Language: "DE"})
events := []*es_models.Event{
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.OrgAdded, Data: orgData},
&es_models.Event{AggregateID: "AggregateID", Sequence: 1, Type: model.MailTextAdded, Data: mailText},
}
mockEs := mock.NewMockEventstore(ctrl)
mockEs.EXPECT().FilterEvents(gomock.Any(), gomock.Any()).Return(events, nil)
mockEs.EXPECT().AggregateCreator().Return(es_models.NewAggregateCreator("TEST"))
mockEs.EXPECT().PushAggregates(gomock.Any(), gomock.Any()).Return(nil)
return GetMockedEventstore(ctrl, mockEs)
}

View File

@ -3996,3 +3996,327 @@ func TestRemovePasswordLockoutPolicy(t *testing.T) {
}) })
} }
} }
func TestAddMailTemplate(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *OrgEventstore
ctx context.Context
template *iam_model.MailTemplate
}
type res struct {
result *iam_model.MailTemplate
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "add label template, ok",
args: args{
es: GetMockChangesOrgOK(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
template: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
Template: []byte("<!doctype html>"),
},
},
res: res{
result: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
Template: []byte("<!doctype html>"),
},
},
},
{
name: "invalid template",
args: args{
es: GetMockChangesOrgOK(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
template: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing org not found",
args: args{
es: GetMockChangesOrgNoEvents(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
template: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.AddMailTemplate(tt.args.ctx, tt.args.template)
if (tt.res.wantErr && !tt.res.errFunc(err)) || (err != nil && !tt.res.wantErr) {
t.Errorf("got wrong err: %v ", err)
return
}
if tt.res.wantErr && tt.res.errFunc(err) {
return
}
if string(result.Template) != string(tt.res.result.Template) {
t.Errorf("got wrong result Template: expected: %v, actual: %v ", tt.res.result.Template, result.Template)
}
})
}
}
func TestChangeMailTemplate(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *OrgEventstore
ctx context.Context
template *iam_model.MailTemplate
}
type res struct {
result *iam_model.MailTemplate
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "add mail template, ok",
args: args{
es: GetMockChangesOrgWithMailTemplate(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
template: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
Template: []byte("<!doctype html>"),
},
},
res: res{
result: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
Template: []byte("<!doctype html>"),
},
},
},
{
name: "invalid template",
args: args{
es: GetMockChangesOrgOK(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
template: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing iam not found",
args: args{
es: GetMockChangesOrgNoEvents(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
template: &iam_model.MailTemplate{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.ChangeMailTemplate(tt.args.ctx, tt.args.template)
if (tt.res.wantErr && !tt.res.errFunc(err)) || (err != nil && !tt.res.wantErr) {
t.Errorf("got wrong err: %v ", err)
return
}
if tt.res.wantErr && tt.res.errFunc(err) {
return
}
if string(result.Template) != string(tt.res.result.Template) {
t.Errorf("got wrong result Template: expected: %v, actual: %v ", tt.res.result.Template, result.Template)
}
})
}
}
func TestAddMailText(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *OrgEventstore
ctx context.Context
mailtext *iam_model.MailText
}
type res struct {
result *iam_model.MailText
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "add label mailtext, ok",
args: args{
es: GetMockChangesOrgOK(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
mailtext: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
MailTextType: "Type",
Language: "DE",
},
},
res: res{
result: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
MailTextType: "Type",
Language: "DE",
},
},
},
{
name: "invalid mailtext",
args: args{
es: GetMockChangesOrgOK(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
mailtext: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing org not found",
args: args{
es: GetMockChangesOrgNoEvents(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
mailtext: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.AddMailText(tt.args.ctx, tt.args.mailtext)
if (tt.res.wantErr && !tt.res.errFunc(err)) || (err != nil && !tt.res.wantErr) {
t.Errorf("got wrong err: %v ", err)
return
}
if tt.res.wantErr && tt.res.errFunc(err) {
return
}
if result.MailTextType != tt.res.result.MailTextType {
t.Errorf("got wrong result MailTextType: expected: %v, actual: %v ", tt.res.result.MailTextType, result.MailTextType)
}
})
}
}
func TestChangeMailText(t *testing.T) {
ctrl := gomock.NewController(t)
type args struct {
es *OrgEventstore
ctx context.Context
mailtext *iam_model.MailText
}
type res struct {
result *iam_model.MailText
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "add label mailtext, ok",
args: args{
es: GetMockChangesOrgWithMailText(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
mailtext: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
MailTextType: "Type",
Language: "DE",
},
},
res: res{
result: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
MailTextType: "Type",
Language: "DE",
},
},
},
{
name: "invalid mailtext",
args: args{
es: GetMockChangesOrgOK(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
mailtext: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "existing iam not found",
args: args{
es: GetMockChangesOrgNoEvents(ctrl),
ctx: authz.NewMockContext("orgID", "userID"),
mailtext: &iam_model.MailText{
ObjectRoot: es_models.ObjectRoot{AggregateID: "AggregateID", Sequence: 0},
},
},
res: res{
wantErr: true,
errFunc: caos_errs.IsNotFound,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := tt.args.es.ChangeMailText(tt.args.ctx, tt.args.mailtext)
if (tt.res.wantErr && !tt.res.errFunc(err)) || (err != nil && !tt.res.wantErr) {
t.Errorf("got wrong err: %v ", err)
return
}
if tt.res.wantErr && tt.res.errFunc(err) {
return
}
if string(result.MailTextType) != string(tt.res.result.MailTextType) {
t.Errorf("got wrong result MailTextType: expected: %v, actual: %v ", tt.res.result.MailTextType, result.MailTextType)
}
})
}
}

View File

@ -30,7 +30,7 @@ func TestLabelPolicyAddedAggregate(t *testing.T) {
res res res res
}{ }{
{ {
name: "add label polciy", name: "add label policy",
args: args{ args: args{
ctx: authz.NewMockContext("orgID", "userID"), ctx: authz.NewMockContext("orgID", "userID"),
existing: &model.Org{ existing: &model.Org{

View File

@ -0,0 +1,77 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
func MailTemplateAddedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Org, policy *iam_es_model.MailTemplate) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if policy == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-4BeRi", "Errors.Internal")
}
agg, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
validationQuery := es_models.NewSearchQuery().
AggregateTypeFilter(model.OrgAggregate).
AggregateIDFilter(existing.AggregateID)
validation := checkExistingMailTemplateValidation()
agg.SetPrecondition(validationQuery, validation)
return agg.AppendEvent(model.MailTemplateAdded, policy)
}
}
func MailTemplateChangedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Org, template *iam_es_model.MailTemplate) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if template == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-yzXO0", "Errors.Internal")
}
agg, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
changes := existing.MailTemplate.Changes(template)
if len(changes) == 0 {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-erTCI", "Errors.NoChangesFound")
}
return agg.AppendEvent(model.MailTemplateChanged, changes)
}
}
func MailTemplateRemovedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Org) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if existing == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-2jVit", "Errors.Internal")
}
agg, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
return agg.AppendEvent(model.MailTemplateRemoved, nil)
}
}
func checkExistingMailTemplateValidation() func(...*es_models.Event) error {
return func(events ...*es_models.Event) error {
existing := false
for _, event := range events {
switch event.Type {
case model.MailTemplateAdded:
existing = true
case model.MailTemplateRemoved:
existing = false
}
}
if existing {
return errors.ThrowPreconditionFailed(nil, "EVENT-aUH4D", "Errors.Org.MailTemplate.AlreadyExists")
}
return nil
}
}

View File

@ -0,0 +1,185 @@
package eventsourcing
import (
"context"
"testing"
"github.com/caos/zitadel/internal/api/authz"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
func TestMailTemplateAddedAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *model.Org
new *iam_es_model.MailTemplate
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventTypes []models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "add mailtemplate",
args: args{
ctx: authz.NewMockContext("orgID", "userID"),
existing: &model.Org{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
},
new: &iam_es_model.MailTemplate{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
Template: []byte("<!doctype html>"),
},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventTypes: []models.EventType{model.MailTemplateAdded},
},
},
{
name: "existing org nil",
args: args{
ctx: authz.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "mailtemplate config nil",
args: args{
ctx: authz.NewMockContext("orgID", "userID"),
existing: &model.Org{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
new: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := MailTemplateAddedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
for i := 0; i < tt.res.eventLen; i++ {
if !tt.res.wantErr && agg.Events[i].Type != tt.res.eventTypes[i] {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
}
if !tt.res.wantErr && agg.Events[i].Data == nil {
t.Errorf("should have data in event")
}
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestMailTemplateChangedAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *model.Org
new *iam_es_model.MailTemplate
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventTypes []models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "change mailtemplate",
args: args{
ctx: authz.NewMockContext("orgID", "userID"),
existing: &model.Org{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
MailTemplate: &iam_es_model.MailTemplate{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
},
},
new: &iam_es_model.MailTemplate{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
Template: []byte("<!doctype html>"),
},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventTypes: []models.EventType{model.MailTemplateChanged},
},
},
{
name: "existing org nil",
args: args{
ctx: authz.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "mailtemplate config nil",
args: args{
ctx: authz.NewMockContext("orgID", "userID"),
existing: &model.Org{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
new: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := MailTemplateChangedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
for i := 0; i < tt.res.eventLen; i++ {
if !tt.res.wantErr && agg.Events[i].Type != tt.res.eventTypes[i] {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
}
if !tt.res.wantErr && agg.Events[i].Data == nil {
t.Errorf("should have data in event")
}
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@ -0,0 +1,94 @@
package eventsourcing
import (
"context"
"github.com/caos/zitadel/internal/errors"
es_models "github.com/caos/zitadel/internal/eventstore/models"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
func MailTextAddedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Org, mailText *iam_es_model.MailText) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if mailText == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Gk3Cn", "Errors.Internal")
}
agg, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
validationQuery := es_models.NewSearchQuery().
AggregateTypeFilter(model.OrgAggregate).
AggregateIDFilter(existing.AggregateID)
validation := checkExistingMailTextValidation(mailText, existing.MailTexts)
agg.SetPrecondition(validationQuery, validation)
return agg.AppendEvent(model.MailTextAdded, mailText)
}
}
func MailTextChangedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Org, mailText *iam_es_model.MailText) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if mailText == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-Hog8a", "Errors.Internal")
}
agg, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
changes := make(map[string]interface{}, 2)
for _, exMailText := range existing.MailTexts {
if exMailText.MailTextType == mailText.MailTextType && exMailText.Language == mailText.Language {
changes = exMailText.Changes(mailText)
if len(changes) == 0 {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-DuRxA", "Errors.NoChangesFound")
}
}
}
return agg.AppendEvent(model.MailTextChanged, changes)
}
}
func MailTextRemovedAggregate(aggCreator *es_models.AggregateCreator, existing *model.Org, mailText *iam_es_model.MailText) func(ctx context.Context) (*es_models.Aggregate, error) {
return func(ctx context.Context) (*es_models.Aggregate, error) {
if existing == nil {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-cJ5Wp", "Errors.Internal")
}
agg, err := OrgAggregate(ctx, aggCreator, existing.AggregateID, existing.Sequence)
if err != nil {
return nil, err
}
changes := make(map[string]interface{}, 2)
for _, exMailText := range existing.MailTexts {
if exMailText.MailTextType == mailText.MailTextType && exMailText.Language == mailText.Language {
mailText.ButtonText = exMailText.ButtonText
mailText.Greeting = exMailText.Greeting
mailText.Text = exMailText.Text
mailText.Title = exMailText.Title
mailText.Subject = exMailText.Subject
mailText.PreHeader = exMailText.PreHeader
changes = exMailText.Changes(mailText)
if len(changes) == 0 {
return nil, errors.ThrowPreconditionFailed(nil, "EVENT-DuRxA", "Errors.NoChangesFound")
}
}
}
return agg.AppendEvent(model.MailTextRemoved, changes)
}
}
func checkExistingMailTextValidation(mailText *iam_es_model.MailText, existingMailTexts []*iam_es_model.MailText) func(...*es_models.Event) error {
return func(events ...*es_models.Event) error {
existing := false
for _, text := range existingMailTexts {
if text.MailTextType == mailText.MailTextType && text.Language == mailText.Language {
existing = true
}
}
if existing {
return errors.ThrowPreconditionFailed(nil, "EVENT-zEZh7", "Errors.Org.MailText.AlreadyExists")
}
return nil
}
}

View File

@ -0,0 +1,188 @@
package eventsourcing
import (
"context"
"testing"
"github.com/caos/zitadel/internal/api/authz"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
)
func TestMailTextAddedAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *model.Org
new *iam_es_model.MailText
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventTypes []models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "add mailtext",
args: args{
ctx: authz.NewMockContext("orgID", "userID"),
existing: &model.Org{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
},
new: &iam_es_model.MailText{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
MailTextType: "Type",
Language: "DE",
},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventTypes: []models.EventType{model.MailTextAdded},
},
},
{
name: "existing org nil",
args: args{
ctx: authz.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "mailtext config nil",
args: args{
ctx: authz.NewMockContext("orgID", "userID"),
existing: &model.Org{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
new: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := MailTextAddedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
for i := 0; i < tt.res.eventLen; i++ {
if !tt.res.wantErr && agg.Events[i].Type != tt.res.eventTypes[i] {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
}
if !tt.res.wantErr && agg.Events[i].Data == nil {
t.Errorf("should have data in event")
}
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}
func TestMailTextChangedAggregate(t *testing.T) {
type args struct {
ctx context.Context
existing *model.Org
new *iam_es_model.MailText
aggCreator *models.AggregateCreator
}
type res struct {
eventLen int
eventTypes []models.EventType
wantErr bool
errFunc func(err error) bool
}
tests := []struct {
name string
args args
res res
}{
{
name: "change mailtext",
args: args{
ctx: authz.NewMockContext("orgID", "userID"),
existing: &model.Org{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
MailTexts: []*iam_es_model.MailText{&iam_es_model.MailText{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
},
},
new: &iam_es_model.MailText{
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
MailTextType: "Type",
Language: "DE",
Subject: "Subject",
},
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
eventLen: 1,
eventTypes: []models.EventType{model.MailTextChanged},
},
},
{
name: "existing org nil",
args: args{
ctx: authz.NewMockContext("orgID", "userID"),
existing: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
{
name: "mailtext config nil",
args: args{
ctx: authz.NewMockContext("orgID", "userID"),
existing: &model.Org{ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"}},
new: nil,
aggCreator: models.NewAggregateCreator("Test"),
},
res: res{
wantErr: true,
errFunc: caos_errs.IsPreconditionFailed,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
agg, err := MailTextChangedAggregate(tt.args.aggCreator, tt.args.existing, tt.args.new)(tt.args.ctx)
if !tt.res.wantErr && len(agg.Events) != tt.res.eventLen {
t.Errorf("got wrong event len: expected: %v, actual: %v ", tt.res.eventLen, len(agg.Events))
}
for i := 0; i < tt.res.eventLen; i++ {
if !tt.res.wantErr && agg.Events[i].Type != tt.res.eventTypes[i] {
t.Errorf("got wrong event type: expected: %v, actual: %v ", tt.res.eventTypes[i], agg.Events[i].Type.String())
}
if !tt.res.wantErr && agg.Events[i].Data == nil {
t.Errorf("should have data in event")
}
}
if tt.res.wantErr && !tt.res.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}

View File

@ -0,0 +1,31 @@
package model
import (
es_models "github.com/caos/zitadel/internal/eventstore/models"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
)
func (o *Org) appendAddMailTemplateEvent(event *es_models.Event) error {
o.MailTemplate = new(iam_es_model.MailTemplate)
err := o.MailTemplate.SetDataLabel(event)
if err != nil {
return err
}
o.MailTemplate.ObjectRoot.CreationDate = event.CreationDate
return nil
}
func (o *Org) appendChangeMailTemplateEvent(event *es_models.Event) error {
mailTemplate := &iam_es_model.MailTemplate{}
err := mailTemplate.SetDataLabel(event)
if err != nil {
return err
}
mailTemplate.ObjectRoot.ChangeDate = event.CreationDate
o.MailTemplate = mailTemplate
return nil
}
func (o *Org) appendRemoveMailTemplateEvent(event *es_models.Event) {
o.MailTemplate = nil
}

View File

@ -0,0 +1,83 @@
package model
import (
"encoding/json"
"testing"
es_models "github.com/caos/zitadel/internal/eventstore/models"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
)
func TestAppendAddMailTemplateEvent(t *testing.T) {
type args struct {
org *Org
policy *iam_es_model.MailTemplate
event *es_models.Event
}
tests := []struct {
name string
args args
result *Org
}{
{
name: "append add label policy event",
args: args{
org: &Org{},
policy: &iam_es_model.MailTemplate{Template: []byte("<!doctype html>")},
event: &es_models.Event{},
},
result: &Org{MailTemplate: &iam_es_model.MailTemplate{Template: []byte("<!doctype html>")}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.policy != nil {
data, _ := json.Marshal(tt.args.policy)
tt.args.event.Data = data
}
tt.args.org.appendAddMailTemplateEvent(tt.args.event)
if string(tt.result.MailTemplate.Template) != string(tt.args.org.MailTemplate.Template) {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.MailTemplate.Template, tt.args.org.MailTemplate.Template)
}
})
}
}
func TestAppendChangeMailTemplateEvent(t *testing.T) {
type args struct {
org *Org
policy *iam_es_model.MailTemplate
event *es_models.Event
}
tests := []struct {
name string
args args
result *Org
}{
{
name: "append change label policy event",
args: args{
org: &Org{MailTemplate: &iam_es_model.MailTemplate{
Template: []byte("<x!doctype html>"),
}},
policy: &iam_es_model.MailTemplate{Template: []byte("<!doctype html>")},
event: &es_models.Event{},
},
result: &Org{MailTemplate: &iam_es_model.MailTemplate{
Template: []byte("<!doctype html>"),
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.policy != nil {
data, _ := json.Marshal(tt.args.policy)
tt.args.event.Data = data
}
tt.args.org.appendChangeMailTemplateEvent(tt.args.event)
if string(tt.result.MailTemplate.Template) != string(tt.args.org.MailTemplate.Template) {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.MailTemplate.Template, tt.args.org.MailTemplate.Template)
}
})
}
}

View File

@ -0,0 +1,44 @@
package model
import (
es_models "github.com/caos/zitadel/internal/eventstore/models"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
)
func (o *Org) appendAddMailTextEvent(event *es_models.Event) error {
mailText := &iam_es_model.MailText{}
err := mailText.SetDataLabel(event)
if err != nil {
return err
}
mailText.ObjectRoot.CreationDate = event.CreationDate
o.MailTexts = append(o.MailTexts, mailText)
return nil
}
func (o *Org) appendChangeMailTextEvent(event *es_models.Event) error {
mailText := &iam_es_model.MailText{}
err := mailText.SetDataLabel(event)
if err != nil {
return err
}
mailText.ObjectRoot.ChangeDate = event.CreationDate
if n, m := iam_es_model.GetMailText(o.MailTexts, mailText.MailTextType, mailText.Language); m != nil {
o.MailTexts[n] = mailText
}
return nil
}
func (o *Org) appendRemoveMailTextEvent(event *es_models.Event) error {
mailText := &iam_es_model.MailText{}
err := mailText.SetDataLabel(event)
if err != nil {
return err
}
if n, m := iam_es_model.GetMailText(o.MailTexts, mailText.MailTextType, mailText.Language); m != nil {
o.MailTexts[n] = o.MailTexts[len(o.MailTexts)-1]
o.MailTexts[len(o.MailTexts)-1] = nil
o.MailTexts = o.MailTexts[:len(o.MailTexts)-1]
}
return nil
}

View File

@ -0,0 +1,91 @@
package model
import (
"encoding/json"
"testing"
es_models "github.com/caos/zitadel/internal/eventstore/models"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
)
func TestAppendAddMailTextEvent(t *testing.T) {
type args struct {
org *Org
mailText *iam_es_model.MailText
event *es_models.Event
}
tests := []struct {
name string
args args
result *Org
}{
{
name: "append add mail text event",
args: args{
org: &Org{},
mailText: &iam_es_model.MailText{MailTextType: "Type", Language: "DE"},
event: &es_models.Event{},
},
result: &Org{MailTexts: []*iam_es_model.MailText{&iam_es_model.MailText{MailTextType: "Type", Language: "DE"}}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.mailText != nil {
data, _ := json.Marshal(tt.args.mailText)
tt.args.event.Data = data
}
tt.args.org.appendAddMailTextEvent(tt.args.event)
if len(tt.args.org.MailTexts) != 1 {
t.Errorf("got wrong result should have one mailtext actual: %v ", len(tt.args.org.MailTexts))
}
if tt.result.MailTexts[0].Language != tt.args.org.MailTexts[0].Language {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.MailTexts[0].Language, tt.args.org.MailTexts[0].Language)
}
})
}
}
func TestAppendChangeMailTextEvent(t *testing.T) {
type args struct {
org *Org
mailText *iam_es_model.MailText
event *es_models.Event
}
tests := []struct {
name string
args args
result *Org
}{
{
name: "append change mail text event",
args: args{
org: &Org{MailTexts: []*iam_es_model.MailText{&iam_es_model.MailText{
Language: "DE",
MailTextType: "TypeX",
}}},
mailText: &iam_es_model.MailText{MailTextType: "Type", Language: "DE"},
event: &es_models.Event{},
},
result: &Org{MailTexts: []*iam_es_model.MailText{&iam_es_model.MailText{
Language: "DE",
MailTextType: "Type",
}}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.args.mailText != nil {
data, _ := json.Marshal(tt.args.mailText)
tt.args.event.Data = data
}
tt.args.org.appendChangeMailTextEvent(tt.args.event)
if len(tt.args.org.MailTexts) != 1 {
t.Errorf("got wrong result should have one mailtext actual: %v ", len(tt.args.org.MailTexts))
}
if tt.result.MailTexts[0].Language != tt.args.org.MailTexts[0].Language {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.MailTexts[0].Language, tt.args.org.MailTexts[0].Language)
}
})
}
}

View File

@ -25,6 +25,8 @@ type Org struct {
Members []*OrgMember `json:"-"` Members []*OrgMember `json:"-"`
OrgIAMPolicy *iam_es_model.OrgIAMPolicy `json:"-"` OrgIAMPolicy *iam_es_model.OrgIAMPolicy `json:"-"`
LabelPolicy *iam_es_model.LabelPolicy `json:"-"` LabelPolicy *iam_es_model.LabelPolicy `json:"-"`
MailTemplate *iam_es_model.MailTemplate `json:"-"`
MailTexts []*iam_es_model.MailText `json:"-"`
IDPs []*iam_es_model.IDPConfig `json:"-"` IDPs []*iam_es_model.IDPConfig `json:"-"`
LoginPolicy *iam_es_model.LoginPolicy `json:"-"` LoginPolicy *iam_es_model.LoginPolicy `json:"-"`
PasswordComplexityPolicy *iam_es_model.PasswordComplexityPolicy `json:"-"` PasswordComplexityPolicy *iam_es_model.PasswordComplexityPolicy `json:"-"`
@ -36,11 +38,13 @@ func OrgFromModel(org *org_model.Org) *Org {
members := OrgMembersFromModel(org.Members) members := OrgMembersFromModel(org.Members)
domains := OrgDomainsFromModel(org.Domains) domains := OrgDomainsFromModel(org.Domains)
idps := iam_es_model.IDPConfigsFromModel(org.IDPs) idps := iam_es_model.IDPConfigsFromModel(org.IDPs)
mailTexts := iam_es_model.MailTextsFromModel(org.MailTexts)
converted := &Org{ converted := &Org{
ObjectRoot: org.ObjectRoot, ObjectRoot: org.ObjectRoot,
Name: org.Name, Name: org.Name,
State: int32(org.State), State: int32(org.State),
Domains: domains, Domains: domains,
MailTexts: mailTexts,
Members: members, Members: members,
IDPs: idps, IDPs: idps,
} }
@ -53,6 +57,9 @@ func OrgFromModel(org *org_model.Org) *Org {
if org.LabelPolicy != nil { if org.LabelPolicy != nil {
converted.LabelPolicy = iam_es_model.LabelPolicyFromModel(org.LabelPolicy) converted.LabelPolicy = iam_es_model.LabelPolicyFromModel(org.LabelPolicy)
} }
if org.MailTemplate != nil {
converted.MailTemplate = iam_es_model.MailTemplateFromModel(org.MailTemplate)
}
if org.PasswordComplexityPolicy != nil { if org.PasswordComplexityPolicy != nil {
converted.PasswordComplexityPolicy = iam_es_model.PasswordComplexityPolicyFromModel(org.PasswordComplexityPolicy) converted.PasswordComplexityPolicy = iam_es_model.PasswordComplexityPolicyFromModel(org.PasswordComplexityPolicy)
} }
@ -72,6 +79,7 @@ func OrgToModel(org *Org) *org_model.Org {
State: org_model.OrgState(org.State), State: org_model.OrgState(org.State),
Domains: OrgDomainsToModel(org.Domains), Domains: OrgDomainsToModel(org.Domains),
Members: OrgMembersToModel(org.Members), Members: OrgMembersToModel(org.Members),
MailTexts: iam_es_model.MailTextsToModel(org.MailTexts),
IDPs: iam_es_model.IDPConfigsToModel(org.IDPs), IDPs: iam_es_model.IDPConfigsToModel(org.IDPs),
} }
if org.OrgIAMPolicy != nil { if org.OrgIAMPolicy != nil {
@ -83,6 +91,9 @@ func OrgToModel(org *Org) *org_model.Org {
if org.LabelPolicy != nil { if org.LabelPolicy != nil {
converted.LabelPolicy = iam_es_model.LabelPolicyToModel(org.LabelPolicy) converted.LabelPolicy = iam_es_model.LabelPolicyToModel(org.LabelPolicy)
} }
if org.MailTemplate != nil {
converted.MailTemplate = iam_es_model.MailTemplateToModel(org.MailTemplate)
}
if org.PasswordComplexityPolicy != nil { if org.PasswordComplexityPolicy != nil {
converted.PasswordComplexityPolicy = iam_es_model.PasswordComplexityPolicyToModel(org.PasswordComplexityPolicy) converted.PasswordComplexityPolicy = iam_es_model.PasswordComplexityPolicyToModel(org.PasswordComplexityPolicy)
} }
@ -199,6 +210,18 @@ func (o *Org) AppendEvent(event *es_models.Event) (err error) {
err = o.appendAddIdpProviderToLoginPolicyEvent(event) err = o.appendAddIdpProviderToLoginPolicyEvent(event)
case LoginPolicyIDPProviderRemoved: case LoginPolicyIDPProviderRemoved:
err = o.appendRemoveIdpProviderFromLoginPolicyEvent(event) err = o.appendRemoveIdpProviderFromLoginPolicyEvent(event)
case MailTemplateAdded:
err = o.appendAddMailTemplateEvent(event)
case MailTemplateChanged:
err = o.appendChangeMailTemplateEvent(event)
case MailTemplateRemoved:
o.appendRemoveMailTemplateEvent(event)
case MailTextAdded:
err = o.appendAddMailTextEvent(event)
case MailTextChanged:
err = o.appendChangeMailTextEvent(event)
case MailTextRemoved:
o.appendRemoveMailTextEvent(event)
case LoginPolicySecondFactorAdded: case LoginPolicySecondFactorAdded:
err = o.appendAddSecondFactorToLoginPolicyEvent(event) err = o.appendAddSecondFactorToLoginPolicyEvent(event)
case LoginPolicySecondFactorRemoved: case LoginPolicySecondFactorRemoved:

View File

@ -60,6 +60,13 @@ const (
LabelPolicyChanged models.EventType = "org.policy.label.changed" LabelPolicyChanged models.EventType = "org.policy.label.changed"
LabelPolicyRemoved models.EventType = "org.policy.label.removed" LabelPolicyRemoved models.EventType = "org.policy.label.removed"
MailTemplateAdded models.EventType = "org.mail.template.added"
MailTemplateChanged models.EventType = "org.mail.template.changed"
MailTemplateRemoved models.EventType = "org.mail.template.removed"
MailTextAdded models.EventType = "org.mail.text.added"
MailTextChanged models.EventType = "org.mail.text.changed"
MailTextRemoved models.EventType = "org.mail.text.removed"
PasswordComplexityPolicyAdded models.EventType = "org.policy.password.complexity.added" PasswordComplexityPolicyAdded models.EventType = "org.policy.password.complexity.added"
PasswordComplexityPolicyChanged models.EventType = "org.policy.password.complexity.changed" PasswordComplexityPolicyChanged models.EventType = "org.policy.password.complexity.changed"
PasswordComplexityPolicyRemoved models.EventType = "org.policy.password.complexity.removed" PasswordComplexityPolicyRemoved models.EventType = "org.policy.password.complexity.removed"

View File

@ -1,8 +1,9 @@
package model package model
import ( import (
"github.com/caos/zitadel/internal/model"
"time" "time"
"github.com/caos/zitadel/internal/model"
) )
type ProjectRoleView struct { type ProjectRoleView struct {
@ -13,6 +14,7 @@ type ProjectRoleView struct {
DisplayName string DisplayName string
Group string Group string
CreationDate time.Time CreationDate time.Time
ChangeDate time.Time
Sequence uint64 Sequence uint64
} }

View File

@ -2,12 +2,13 @@ package model
import ( import (
"encoding/json" "encoding/json"
"time"
"github.com/caos/logging" "github.com/caos/logging"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore/models" "github.com/caos/zitadel/internal/eventstore/models"
"github.com/caos/zitadel/internal/project/model" "github.com/caos/zitadel/internal/project/model"
es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model" es_model "github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
"time"
) )
const ( const (
@ -27,6 +28,7 @@ type ProjectRoleView struct {
ResourceOwner string `json:"-" gorm:"resource_owner"` ResourceOwner string `json:"-" gorm:"resource_owner"`
CreationDate time.Time `json:"-" gorm:"column:creation_date"` CreationDate time.Time `json:"-" gorm:"column:creation_date"`
ChangeDate time.Time `json:"-" gorm:"column:change_date"`
} }
func ProjectRoleViewFromModel(role *model.ProjectRoleView) *ProjectRoleView { func ProjectRoleViewFromModel(role *model.ProjectRoleView) *ProjectRoleView {
@ -39,6 +41,7 @@ func ProjectRoleViewFromModel(role *model.ProjectRoleView) *ProjectRoleView {
Group: role.Group, Group: role.Group,
Sequence: role.Sequence, Sequence: role.Sequence,
CreationDate: role.CreationDate, CreationDate: role.CreationDate,
ChangeDate: role.ChangeDate,
} }
} }
@ -52,6 +55,7 @@ func ProjectRoleToModel(role *ProjectRoleView) *model.ProjectRoleView {
Group: role.Group, Group: role.Group,
Sequence: role.Sequence, Sequence: role.Sequence,
CreationDate: role.CreationDate, CreationDate: role.CreationDate,
ChangeDate: role.ChangeDate,
} }
} }
@ -71,6 +75,7 @@ func (r *ProjectRoleView) AppendEvent(event *models.Event) (err error) {
r.CreationDate = event.CreationDate r.CreationDate = event.CreationDate
err = r.SetData(event) err = r.SetData(event)
case es_model.ProjectRoleChanged: case es_model.ProjectRoleChanged:
r.ChangeDate = event.CreationDate
err = r.SetData(event) err = r.SetData(event)
} }
return err return err

View File

@ -6,15 +6,16 @@ import (
) )
type IAMSetUp struct { type IAMSetUp struct {
Step1 *Step1 Step1 *Step1
Step2 *Step2 Step2 *Step2
Step3 *Step3 Step3 *Step3
Step4 *Step4 Step4 *Step4
Step5 *Step5 Step5 *Step5
Step6 *Step6 Step6 *Step6
Step7 *Step7 Step7 *Step7
Step8 *Step8 Step8 *Step8
Step9 *Step9 Step9 *Step9
Step10 *Step10
} }
func (setup *IAMSetUp) steps(currentDone iam_model.Step) ([]step, error) { func (setup *IAMSetUp) steps(currentDone iam_model.Step) ([]step, error) {
@ -31,6 +32,7 @@ func (setup *IAMSetUp) steps(currentDone iam_model.Step) ([]step, error) {
setup.Step7, setup.Step7,
setup.Step8, setup.Step8,
setup.Step9, setup.Step9,
setup.Step10,
} { } {
if step.step() <= currentDone { if step.step() <= currentDone {
continue continue

107
internal/setup/step10.go Normal file
View File

@ -0,0 +1,107 @@
package setup
import (
"context"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/eventstore/models"
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
iam_model "github.com/caos/zitadel/internal/iam/model"
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
)
type Step10 struct {
DefaultMailTemplate iam_model.MailTemplate
DefaultMailTexts []iam_model.MailText
setup *Setup
}
func (s *Step10) isNil() bool {
return s == nil
}
func (step *Step10) step() iam_model.Step {
return iam_model.Step10
}
func (step *Step10) init(setup *Setup) {
step.setup = setup
}
func (step *Step10) execute(ctx context.Context) (*iam_model.IAM, error) {
iam, agg, err := step.mailTemplate(ctx, &step.DefaultMailTemplate)
if err != nil {
logging.Log("SETUP-1UYCt").WithField("step", step.step()).WithError(err).Error("unable to finish setup (Mail template)")
return nil, err
}
iam, agg, push, err := step.setup.IamEvents.PrepareSetupDone(ctx, iam, agg, step.step())
if err != nil {
logging.Log("SETUP-fMLsb").WithField("step", step.step()).WithError(err).Error("unable to finish setup (prepare setup done)")
return nil, err
}
err = es_sdk.PushAggregates(ctx, push, iam.AppendEvents, agg)
if err != nil {
logging.Log("SETUP-GuS3f").WithField("step", step.step()).WithError(err).Error("unable to finish setup")
return nil, err
}
iam, agg, err = step.defaultMailTexts(ctx, &step.DefaultMailTexts)
if err != nil {
logging.Log("SETUP-p4oWq").WithError(err).Error("unable to set up defaultMailTexts")
return nil, err
}
iam, agg, push, err = step.setup.IamEvents.PrepareSetupDone(ctx, iam, agg, step.step())
if err != nil {
logging.Log("SETUP-fMLsb").WithField("step", step.step()).WithError(err).Error("unable to finish setup (prepare setup done)")
return nil, err
}
err = es_sdk.PushAggregates(ctx, push, iam.AppendEvents, agg)
if err != nil {
logging.Log("SETUP-GuS3f").WithField("step", step.step()).WithError(err).Error("unable to finish setup")
return nil, err
}
return iam_es_model.IAMToModel(iam), nil
}
func (step *Step10) mailTemplate(ctx context.Context, mailTemplate *iam_model.MailTemplate) (*iam_es_model.IAM, *models.Aggregate, error) {
logging.Log("SETUP-cNrF3").Info("setting up mail template")
mailTemplate.AggregateID = step.setup.iamID
iam, aggregate, err := step.setup.IamEvents.PrepareAddMailTemplate(ctx, mailTemplate)
if err != nil {
return nil, nil, err
}
return iam, aggregate, nil
}
func (step *Step10) defaultMailTexts(ctx context.Context, defaultMailTexts *[]iam_model.MailText) (*iam_es_model.IAM, *models.Aggregate, error) {
logging.Log("SETUP-dsTh3").Info("setting up defaultMailTexts")
iam := &iam_es_model.IAM{}
var aggregate *models.Aggregate
for index, iamDefaultMailText := range *defaultMailTexts {
iaml, aggregatel, err := step.defaultMailText(ctx, &iamDefaultMailText)
if err != nil {
logging.LogWithFields("SETUP-IlLif", "DefaultMailText", iamDefaultMailText.MailTextType).WithError(err).Error("unable to create defaultMailText")
return nil, nil, err
}
if index == 0 {
aggregate = aggregatel
} else {
aggregate.Events = append(aggregate.Events, aggregatel.Events...)
}
iam = iaml
}
logging.Log("SETUP-dgjT4").Info("defaultMailTexts set up")
return iam, aggregate, nil
}
func (step *Step10) defaultMailText(ctx context.Context, mailText *iam_model.MailText) (*iam_es_model.IAM, *models.Aggregate, error) {
logging.Log("SETUP-cNrF3").Info("setting up mail text")
mailText.AggregateID = step.setup.iamID
iam, aggregate, err := step.setup.IamEvents.PrepareAddMailText(ctx, mailText)
if err != nil {
return nil, nil, err
}
return iam, aggregate, nil
}

View File

@ -0,0 +1,78 @@
CREATE TABLE adminapi.mail_templates (
aggregate_id TEXT,
creation_date TIMESTAMPTZ,
change_date TIMESTAMPTZ,
mail_template_state SMALLINT,
sequence BIGINT,
template BYTES,
PRIMARY KEY (aggregate_id)
);
CREATE TABLE management.mail_templates (
aggregate_id TEXT,
creation_date TIMESTAMPTZ,
change_date TIMESTAMPTZ,
mail_template_state SMALLINT,
sequence BIGINT,
template BYTES,
PRIMARY KEY (aggregate_id)
);
GRANT SELECT ON TABLE adminapi.mail_templates TO notification;
GRANT SELECT ON TABLE management.mail_templates TO notification;
CREATE TABLE adminapi.mail_texts (
aggregate_id TEXT,
creation_date TIMESTAMPTZ,
change_date TIMESTAMPTZ,
mail_text_state SMALLINT,
sequence BIGINT,
mail_text_type TEXT,
language TEXT,
title TEXT,
pre_header TEXT,
subject TEXT,
greeting TEXT,
text TEXT,
button_text TEXT,
PRIMARY KEY (aggregate_id, mail_text_type, language)
);
CREATE TABLE management.mail_texts (
aggregate_id TEXT,
creation_date TIMESTAMPTZ,
change_date TIMESTAMPTZ,
mail_text_state SMALLINT,
sequence BIGINT,
mail_text_type TEXT,
language TEXT,
title TEXT,
pre_header TEXT,
subject TEXT,
greeting TEXT,
text TEXT,
button_text TEXT,
PRIMARY KEY (aggregate_id, mail_text_type, language)
);
GRANT SELECT ON TABLE adminapi.mail_texts TO notification;
GRANT SELECT ON TABLE management.mail_texts TO notification;
ALTER TABLE management.project_roles ADD COLUMN change_date TIMESTAMPTZ;
ALTER TABLE auth.project_roles ADD COLUMN change_date TIMESTAMPTZ;

View File

@ -316,6 +316,46 @@ func (mr *MockAdminServiceClientMockRecorder) GetDefaultLoginPolicySecondFactors
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultLoginPolicySecondFactors", reflect.TypeOf((*MockAdminServiceClient)(nil).GetDefaultLoginPolicySecondFactors), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultLoginPolicySecondFactors", reflect.TypeOf((*MockAdminServiceClient)(nil).GetDefaultLoginPolicySecondFactors), varargs...)
} }
// GetDefaultMailTemplate mocks base method
func (m *MockAdminServiceClient) GetDefaultMailTemplate(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*admin.DefaultMailTemplateView, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "GetDefaultMailTemplate", varargs...)
ret0, _ := ret[0].(*admin.DefaultMailTemplateView)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetDefaultMailTemplate indicates an expected call of GetDefaultMailTemplate
func (mr *MockAdminServiceClientMockRecorder) GetDefaultMailTemplate(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultMailTemplate", reflect.TypeOf((*MockAdminServiceClient)(nil).GetDefaultMailTemplate), varargs...)
}
// GetDefaultMailTexts mocks base method
func (m *MockAdminServiceClient) GetDefaultMailTexts(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*admin.DefaultMailTextsView, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "GetDefaultMailTexts", varargs...)
ret0, _ := ret[0].(*admin.DefaultMailTextsView)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetDefaultMailTexts indicates an expected call of GetDefaultMailTexts
func (mr *MockAdminServiceClientMockRecorder) GetDefaultMailTexts(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultMailTexts", reflect.TypeOf((*MockAdminServiceClient)(nil).GetDefaultMailTexts), varargs...)
}
// GetDefaultOrgIamPolicy mocks base method // GetDefaultOrgIamPolicy mocks base method
func (m *MockAdminServiceClient) GetDefaultOrgIamPolicy(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*admin.OrgIamPolicyView, error) { func (m *MockAdminServiceClient) GetDefaultOrgIamPolicy(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*admin.OrgIamPolicyView, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -836,6 +876,46 @@ func (mr *MockAdminServiceClientMockRecorder) UpdateDefaultLoginPolicy(arg0, arg
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDefaultLoginPolicy", reflect.TypeOf((*MockAdminServiceClient)(nil).UpdateDefaultLoginPolicy), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDefaultLoginPolicy", reflect.TypeOf((*MockAdminServiceClient)(nil).UpdateDefaultLoginPolicy), varargs...)
} }
// UpdateDefaultMailTemplate mocks base method
func (m *MockAdminServiceClient) UpdateDefaultMailTemplate(arg0 context.Context, arg1 *admin.DefaultMailTemplateUpdate, arg2 ...grpc.CallOption) (*admin.DefaultMailTemplate, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdateDefaultMailTemplate", varargs...)
ret0, _ := ret[0].(*admin.DefaultMailTemplate)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateDefaultMailTemplate indicates an expected call of UpdateDefaultMailTemplate
func (mr *MockAdminServiceClientMockRecorder) UpdateDefaultMailTemplate(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDefaultMailTemplate", reflect.TypeOf((*MockAdminServiceClient)(nil).UpdateDefaultMailTemplate), varargs...)
}
// UpdateDefaultMailText mocks base method
func (m *MockAdminServiceClient) UpdateDefaultMailText(arg0 context.Context, arg1 *admin.DefaultMailTextUpdate, arg2 ...grpc.CallOption) (*admin.DefaultMailText, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdateDefaultMailText", varargs...)
ret0, _ := ret[0].(*admin.DefaultMailText)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateDefaultMailText indicates an expected call of UpdateDefaultMailText
func (mr *MockAdminServiceClientMockRecorder) UpdateDefaultMailText(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDefaultMailText", reflect.TypeOf((*MockAdminServiceClient)(nil).UpdateDefaultMailText), varargs...)
}
// UpdateDefaultOrgIamPolicy mocks base method // UpdateDefaultOrgIamPolicy mocks base method
func (m *MockAdminServiceClient) UpdateDefaultOrgIamPolicy(arg0 context.Context, arg1 *admin.OrgIamPolicyRequest, arg2 ...grpc.CallOption) (*admin.OrgIamPolicy, error) { func (m *MockAdminServiceClient) UpdateDefaultOrgIamPolicy(arg0 context.Context, arg1 *admin.OrgIamPolicyRequest, arg2 ...grpc.CallOption) (*admin.OrgIamPolicy, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@ -349,6 +349,48 @@ service AdminService {
}; };
} }
rpc GetDefaultMailTemplate(google.protobuf.Empty) returns (DefaultMailTemplateView) {
option (google.api.http) = {
get: "/policies/mailtemplate"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "iam.policy.read"
};
}
rpc UpdateDefaultMailTemplate(DefaultMailTemplateUpdate) returns (DefaultMailTemplate) {
option (google.api.http) = {
put: "/policies/mailtemplate"
body: "*"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "iam.policy.write"
};
}
rpc GetDefaultMailTexts(google.protobuf.Empty) returns (DefaultMailTextsView) {
option (google.api.http) = {
get: "/policies/mailtexts"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "iam.policy.read"
};
}
rpc UpdateDefaultMailText(DefaultMailTextUpdate) returns (DefaultMailText) {
option (google.api.http) = {
put: "/policies/mailtext"
body: "*"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "iam.policy.write"
};
}
rpc GetDefaultLoginPolicy(google.protobuf.Empty) returns (DefaultLoginPolicyView) { rpc GetDefaultLoginPolicy(google.protobuf.Empty) returns (DefaultLoginPolicyView) {
option (google.api.http) = { option (google.api.http) = {
get: "/policies/login" get: "/policies/login"
@ -992,6 +1034,63 @@ message DefaultLabelPolicyView {
google.protobuf.Timestamp change_date = 4; google.protobuf.Timestamp change_date = 4;
} }
message DefaultMailTemplate {
bytes template = 1;
google.protobuf.Timestamp creation_date = 2;
google.protobuf.Timestamp change_date = 3;
}
message DefaultMailTemplateUpdate {
bytes template = 1;
}
message DefaultMailTemplateView {
bytes template = 1;
google.protobuf.Timestamp creation_date = 2;
google.protobuf.Timestamp change_date = 3;
}
message DefaultMailText {
string mail_text_type = 1;
string language = 2;
string title = 3;
string pre_header = 4;
string subject = 5;
string greeting = 6;
string text = 7;
string button_text = 8;
google.protobuf.Timestamp creation_date = 9;
google.protobuf.Timestamp change_date = 10;
}
message DefaultMailTextUpdate {
string mail_text_type = 1;
string language = 2;
string title = 3;
string pre_header = 4;
string subject = 5;
string greeting = 6;
string text = 7;
string button_text = 8;
}
message DefaultMailTextsView{
repeated DefaultMailTextView texts = 1;
}
message DefaultMailTextView {
string mail_text_type = 1;
string language = 2;
string title = 3;
string pre_header = 4;
string subject = 5;
string greeting = 6;
string text = 7;
string button_text = 8;
google.protobuf.Timestamp creation_date = 9;
google.protobuf.Timestamp change_date = 10;
}
message DefaultLoginPolicy { message DefaultLoginPolicy {
bool allow_username_password = 1; bool allow_username_password = 1;
bool allow_register = 2; bool allow_register = 2;

View File

@ -216,6 +216,26 @@ func (mr *MockAuthServiceClientMockRecorder) GetMyPasswordComplexityPolicy(arg0,
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMyPasswordComplexityPolicy", reflect.TypeOf((*MockAuthServiceClient)(nil).GetMyPasswordComplexityPolicy), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMyPasswordComplexityPolicy", reflect.TypeOf((*MockAuthServiceClient)(nil).GetMyPasswordComplexityPolicy), varargs...)
} }
// GetMyPasswordless mocks base method
func (m *MockAuthServiceClient) GetMyPasswordless(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*auth.WebAuthNTokens, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "GetMyPasswordless", varargs...)
ret0, _ := ret[0].(*auth.WebAuthNTokens)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetMyPasswordless indicates an expected call of GetMyPasswordless
func (mr *MockAuthServiceClientMockRecorder) GetMyPasswordless(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMyPasswordless", reflect.TypeOf((*MockAuthServiceClient)(nil).GetMyPasswordless), varargs...)
}
// GetMyProjectPermissions mocks base method // GetMyProjectPermissions mocks base method
func (m *MockAuthServiceClient) GetMyProjectPermissions(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*auth.MyPermissions, error) { func (m *MockAuthServiceClient) GetMyProjectPermissions(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*auth.MyPermissions, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@ -456,6 +456,46 @@ func (mr *MockManagementServiceClientMockRecorder) CreateLoginPolicy(arg0, arg1
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLoginPolicy", reflect.TypeOf((*MockManagementServiceClient)(nil).CreateLoginPolicy), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLoginPolicy", reflect.TypeOf((*MockManagementServiceClient)(nil).CreateLoginPolicy), varargs...)
} }
// CreateMailTemplate mocks base method
func (m *MockManagementServiceClient) CreateMailTemplate(arg0 context.Context, arg1 *management.MailTemplateUpdate, arg2 ...grpc.CallOption) (*management.MailTemplate, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "CreateMailTemplate", varargs...)
ret0, _ := ret[0].(*management.MailTemplate)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateMailTemplate indicates an expected call of CreateMailTemplate
func (mr *MockManagementServiceClientMockRecorder) CreateMailTemplate(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMailTemplate", reflect.TypeOf((*MockManagementServiceClient)(nil).CreateMailTemplate), varargs...)
}
// CreateMailText mocks base method
func (m *MockManagementServiceClient) CreateMailText(arg0 context.Context, arg1 *management.MailTextUpdate, arg2 ...grpc.CallOption) (*management.MailText, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "CreateMailText", varargs...)
ret0, _ := ret[0].(*management.MailText)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CreateMailText indicates an expected call of CreateMailText
func (mr *MockManagementServiceClientMockRecorder) CreateMailText(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMailText", reflect.TypeOf((*MockManagementServiceClient)(nil).CreateMailText), varargs...)
}
// CreateOIDCApplication mocks base method // CreateOIDCApplication mocks base method
func (m *MockManagementServiceClient) CreateOIDCApplication(arg0 context.Context, arg1 *management.OIDCApplicationCreate, arg2 ...grpc.CallOption) (*management.Application, error) { func (m *MockManagementServiceClient) CreateOIDCApplication(arg0 context.Context, arg1 *management.OIDCApplicationCreate, arg2 ...grpc.CallOption) (*management.Application, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -876,6 +916,46 @@ func (mr *MockManagementServiceClientMockRecorder) GetDefaultLoginPolicy(arg0, a
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultLoginPolicy", reflect.TypeOf((*MockManagementServiceClient)(nil).GetDefaultLoginPolicy), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultLoginPolicy", reflect.TypeOf((*MockManagementServiceClient)(nil).GetDefaultLoginPolicy), varargs...)
} }
// GetDefaultMailTemplate mocks base method
func (m *MockManagementServiceClient) GetDefaultMailTemplate(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*management.MailTemplateView, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "GetDefaultMailTemplate", varargs...)
ret0, _ := ret[0].(*management.MailTemplateView)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetDefaultMailTemplate indicates an expected call of GetDefaultMailTemplate
func (mr *MockManagementServiceClientMockRecorder) GetDefaultMailTemplate(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultMailTemplate", reflect.TypeOf((*MockManagementServiceClient)(nil).GetDefaultMailTemplate), varargs...)
}
// GetDefaultMailTexts mocks base method
func (m *MockManagementServiceClient) GetDefaultMailTexts(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*management.MailTextsView, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "GetDefaultMailTexts", varargs...)
ret0, _ := ret[0].(*management.MailTextsView)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetDefaultMailTexts indicates an expected call of GetDefaultMailTexts
func (mr *MockManagementServiceClientMockRecorder) GetDefaultMailTexts(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultMailTexts", reflect.TypeOf((*MockManagementServiceClient)(nil).GetDefaultMailTexts), varargs...)
}
// GetDefaultPasswordAgePolicy mocks base method // GetDefaultPasswordAgePolicy mocks base method
func (m *MockManagementServiceClient) GetDefaultPasswordAgePolicy(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*management.PasswordAgePolicyView, error) { func (m *MockManagementServiceClient) GetDefaultPasswordAgePolicy(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*management.PasswordAgePolicyView, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -1076,6 +1156,46 @@ func (mr *MockManagementServiceClientMockRecorder) GetMachineKey(arg0, arg1 inte
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMachineKey", reflect.TypeOf((*MockManagementServiceClient)(nil).GetMachineKey), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMachineKey", reflect.TypeOf((*MockManagementServiceClient)(nil).GetMachineKey), varargs...)
} }
// GetMailTemplate mocks base method
func (m *MockManagementServiceClient) GetMailTemplate(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*management.MailTemplateView, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "GetMailTemplate", varargs...)
ret0, _ := ret[0].(*management.MailTemplateView)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetMailTemplate indicates an expected call of GetMailTemplate
func (mr *MockManagementServiceClientMockRecorder) GetMailTemplate(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMailTemplate", reflect.TypeOf((*MockManagementServiceClient)(nil).GetMailTemplate), varargs...)
}
// GetMailTexts mocks base method
func (m *MockManagementServiceClient) GetMailTexts(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*management.MailTextsView, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "GetMailTexts", varargs...)
ret0, _ := ret[0].(*management.MailTextsView)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetMailTexts indicates an expected call of GetMailTexts
func (mr *MockManagementServiceClientMockRecorder) GetMailTexts(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMailTexts", reflect.TypeOf((*MockManagementServiceClient)(nil).GetMailTexts), varargs...)
}
// GetMyOrg mocks base method // GetMyOrg mocks base method
func (m *MockManagementServiceClient) GetMyOrg(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*management.OrgView, error) { func (m *MockManagementServiceClient) GetMyOrg(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*management.OrgView, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -1216,6 +1336,26 @@ func (mr *MockManagementServiceClientMockRecorder) GetPasswordLockoutPolicy(arg0
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPasswordLockoutPolicy", reflect.TypeOf((*MockManagementServiceClient)(nil).GetPasswordLockoutPolicy), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPasswordLockoutPolicy", reflect.TypeOf((*MockManagementServiceClient)(nil).GetPasswordLockoutPolicy), varargs...)
} }
// GetPasswordless mocks base method
func (m *MockManagementServiceClient) GetPasswordless(arg0 context.Context, arg1 *management.UserID, arg2 ...grpc.CallOption) (*management.WebAuthNTokens, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "GetPasswordless", varargs...)
ret0, _ := ret[0].(*management.WebAuthNTokens)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetPasswordless indicates an expected call of GetPasswordless
func (mr *MockManagementServiceClientMockRecorder) GetPasswordless(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPasswordless", reflect.TypeOf((*MockManagementServiceClient)(nil).GetPasswordless), varargs...)
}
// GetProjectGrantMemberRoles mocks base method // GetProjectGrantMemberRoles mocks base method
func (m *MockManagementServiceClient) GetProjectGrantMemberRoles(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*management.ProjectGrantMemberRoles, error) { func (m *MockManagementServiceClient) GetProjectGrantMemberRoles(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*management.ProjectGrantMemberRoles, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -1836,6 +1976,46 @@ func (mr *MockManagementServiceClientMockRecorder) RemoveLoginPolicy(arg0, arg1
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveLoginPolicy", reflect.TypeOf((*MockManagementServiceClient)(nil).RemoveLoginPolicy), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveLoginPolicy", reflect.TypeOf((*MockManagementServiceClient)(nil).RemoveLoginPolicy), varargs...)
} }
// RemoveMailTemplate mocks base method
func (m *MockManagementServiceClient) RemoveMailTemplate(arg0 context.Context, arg1 *emptypb.Empty, arg2 ...grpc.CallOption) (*emptypb.Empty, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "RemoveMailTemplate", varargs...)
ret0, _ := ret[0].(*emptypb.Empty)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RemoveMailTemplate indicates an expected call of RemoveMailTemplate
func (mr *MockManagementServiceClientMockRecorder) RemoveMailTemplate(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveMailTemplate", reflect.TypeOf((*MockManagementServiceClient)(nil).RemoveMailTemplate), varargs...)
}
// RemoveMailText mocks base method
func (m *MockManagementServiceClient) RemoveMailText(arg0 context.Context, arg1 *management.MailTextRemove, arg2 ...grpc.CallOption) (*emptypb.Empty, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "RemoveMailText", varargs...)
ret0, _ := ret[0].(*emptypb.Empty)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RemoveMailText indicates an expected call of RemoveMailText
func (mr *MockManagementServiceClientMockRecorder) RemoveMailText(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveMailText", reflect.TypeOf((*MockManagementServiceClient)(nil).RemoveMailText), varargs...)
}
// RemoveMfaOTP mocks base method // RemoveMfaOTP mocks base method
func (m *MockManagementServiceClient) RemoveMfaOTP(arg0 context.Context, arg1 *management.UserID, arg2 ...grpc.CallOption) (*emptypb.Empty, error) { func (m *MockManagementServiceClient) RemoveMfaOTP(arg0 context.Context, arg1 *management.UserID, arg2 ...grpc.CallOption) (*emptypb.Empty, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -1996,6 +2176,26 @@ func (mr *MockManagementServiceClientMockRecorder) RemovePasswordLockoutPolicy(a
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemovePasswordLockoutPolicy", reflect.TypeOf((*MockManagementServiceClient)(nil).RemovePasswordLockoutPolicy), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemovePasswordLockoutPolicy", reflect.TypeOf((*MockManagementServiceClient)(nil).RemovePasswordLockoutPolicy), varargs...)
} }
// RemovePasswordless mocks base method
func (m *MockManagementServiceClient) RemovePasswordless(arg0 context.Context, arg1 *management.WebAuthNTokenID, arg2 ...grpc.CallOption) (*emptypb.Empty, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "RemovePasswordless", varargs...)
ret0, _ := ret[0].(*emptypb.Empty)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RemovePasswordless indicates an expected call of RemovePasswordless
func (mr *MockManagementServiceClientMockRecorder) RemovePasswordless(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemovePasswordless", reflect.TypeOf((*MockManagementServiceClient)(nil).RemovePasswordless), varargs...)
}
// RemoveProject mocks base method // RemoveProject mocks base method
func (m *MockManagementServiceClient) RemoveProject(arg0 context.Context, arg1 *management.ProjectID, arg2 ...grpc.CallOption) (*emptypb.Empty, error) { func (m *MockManagementServiceClient) RemoveProject(arg0 context.Context, arg1 *management.ProjectID, arg2 ...grpc.CallOption) (*emptypb.Empty, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@ -2676,6 +2876,46 @@ func (mr *MockManagementServiceClientMockRecorder) UpdateLoginPolicy(arg0, arg1
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLoginPolicy", reflect.TypeOf((*MockManagementServiceClient)(nil).UpdateLoginPolicy), varargs...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLoginPolicy", reflect.TypeOf((*MockManagementServiceClient)(nil).UpdateLoginPolicy), varargs...)
} }
// UpdateMailTemplate mocks base method
func (m *MockManagementServiceClient) UpdateMailTemplate(arg0 context.Context, arg1 *management.MailTemplateUpdate, arg2 ...grpc.CallOption) (*management.MailTemplate, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdateMailTemplate", varargs...)
ret0, _ := ret[0].(*management.MailTemplate)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateMailTemplate indicates an expected call of UpdateMailTemplate
func (mr *MockManagementServiceClientMockRecorder) UpdateMailTemplate(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMailTemplate", reflect.TypeOf((*MockManagementServiceClient)(nil).UpdateMailTemplate), varargs...)
}
// UpdateMailText mocks base method
func (m *MockManagementServiceClient) UpdateMailText(arg0 context.Context, arg1 *management.MailTextUpdate, arg2 ...grpc.CallOption) (*management.MailText, error) {
m.ctrl.T.Helper()
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdateMailText", varargs...)
ret0, _ := ret[0].(*management.MailText)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateMailText indicates an expected call of UpdateMailText
func (mr *MockManagementServiceClientMockRecorder) UpdateMailText(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMailText", reflect.TypeOf((*MockManagementServiceClient)(nil).UpdateMailText), varargs...)
}
// UpdateOidcIdpConfig mocks base method // UpdateOidcIdpConfig mocks base method
func (m *MockManagementServiceClient) UpdateOidcIdpConfig(arg0 context.Context, arg1 *management.OidcIdpConfigUpdate, arg2 ...grpc.CallOption) (*management.OidcIdpConfig, error) { func (m *MockManagementServiceClient) UpdateOidcIdpConfig(arg0 context.Context, arg1 *management.OidcIdpConfigUpdate, arg2 ...grpc.CallOption) (*management.OidcIdpConfig, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@ -1620,6 +1620,112 @@ service ManagementService {
permission: "policy.delete" permission: "policy.delete"
}; };
} }
rpc GetMailTemplate(google.protobuf.Empty) returns (MailTemplateView) {
option (google.api.http) = {
get: "/orgs/me/policies/mailtemplate"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.read"
};
}
rpc GetDefaultMailTemplate(google.protobuf.Empty) returns (MailTemplateView) {
option (google.api.http) = {
get: "/orgs/default/policies/mailtemplate"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.read"
};
}
rpc CreateMailTemplate(MailTemplateUpdate) returns (MailTemplate) {
option (google.api.http) = {
post: "/orgs/me/policies/mailtemplate"
body: "*"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.write"
};
}
rpc UpdateMailTemplate(MailTemplateUpdate) returns (MailTemplate) {
option (google.api.http) = {
put: "/orgs/me/policies/mailtemplate"
body: "*"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.write"
};
}
rpc RemoveMailTemplate(google.protobuf.Empty) returns (google.protobuf.Empty) {
option (google.api.http) = {
delete: "/orgs/me/policies/mailtemplate"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.delete"
};
}
rpc GetMailTexts(google.protobuf.Empty) returns (MailTextsView) {
option (google.api.http) = {
get: "/orgs/me/policies/mailtexts"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.read"
};
}
rpc GetDefaultMailTexts(google.protobuf.Empty) returns (MailTextsView) {
option (google.api.http) = {
get: "/orgs/default/policies/mailtexts"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.read"
};
}
rpc CreateMailText(MailTextUpdate) returns (MailText) {
option (google.api.http) = {
post: "/orgs/me/policies/mailtext"
body: "*"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.write"
};
}
rpc UpdateMailText(MailTextUpdate) returns (MailText) {
option (google.api.http) = {
put: "/orgs/me/policies/mailtext"
body: "*"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.write"
};
}
rpc RemoveMailText(MailTextRemove) returns (google.protobuf.Empty) {
option (google.api.http) = {
delete: "/orgs/me/policies/mailtext/type/{mail_text_type}/language/{language}"
};
option (caos.zitadel.utils.v1.auth_option) = {
permission: "policy.delete"
};
}
} }
message ZitadelDocs { message ZitadelDocs {
@ -2437,6 +2543,7 @@ message ProjectRoleView {
string key = 2; string key = 2;
string display_name = 3; string display_name = 3;
google.protobuf.Timestamp creation_date = 4; google.protobuf.Timestamp creation_date = 4;
google.protobuf.Timestamp change_date = 5;
string group = 6; string group = 6;
uint64 sequence = 7; uint64 sequence = 7;
} }
@ -3324,3 +3431,66 @@ message PasswordLockoutPolicyView {
google.protobuf.Timestamp creation_date = 5; google.protobuf.Timestamp creation_date = 5;
google.protobuf.Timestamp change_date = 6; google.protobuf.Timestamp change_date = 6;
} }
message MailTemplate {
bytes template = 1;
google.protobuf.Timestamp creation_date = 2;
google.protobuf.Timestamp change_date = 3;
}
message MailTemplateUpdate {
bytes template = 1;
}
message MailTemplateView {
bool default = 1;
bytes template = 2;
google.protobuf.Timestamp creation_date = 3;
google.protobuf.Timestamp change_date = 4;
}
message MailText {
string mail_text_type = 1;
string language = 2;
string title = 3;
string pre_header = 4;
string subject = 5;
string greeting = 6;
string text = 7;
string button_text = 8;
google.protobuf.Timestamp creation_date = 9;
google.protobuf.Timestamp change_date = 10;
}
message MailTextUpdate {
string mail_text_type = 1;
string language = 2;
string title = 3;
string pre_header = 4;
string subject = 5;
string greeting = 6;
string text = 7;
string button_text = 8;
}
message MailTextRemove {
string mail_text_type = 1;
string language = 2;
}
message MailTextsView{
repeated MailTextView texts = 1;
}
message MailTextView {
bool default = 1;
string mail_text_type = 2;
string language = 3;
string title = 4;
string pre_header = 5;
string subject = 6;
string greeting = 7;
string text = 8;
string button_text = 9;
google.protobuf.Timestamp creation_date = 10;
google.protobuf.Timestamp change_date = 11;
}