feat: added xoauth for smtp (#11239)

# Which Problems Are Solved

- Some mail providers (e.g.: MS Exchange) have deprecated plain auth

# How the Problems Are Solved

Adds XOauth2 auth option to SMPT
- SMTP config now can contain XOAuth2 auth config
- Proto files are updated to have a oneof for configuration selection.
Old fields for configuring plain auth are still available not to create
a breaking change. These can be removed in the future.
- Columns are added to the database to persist the config

# Additional Changes

none

# Additional Context

Replace this example with links to related issues, discussions, discord
threads, or other sources with more context.
Use the Closing #issue syntax for issues that are resolved with this PR.
- Closes #8042

---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: Tim Möhlmann <[email protected]>
This commit is contained in:
Wim Van Laer
2026-01-27 15:11:56 +01:00
committed by GitHub
co-authored by Copilot Tim Möhlmann
parent 96bbd6a65c
commit 3fc0ef2506
31 changed files with 2990 additions and 1059 deletions
@@ -1406,6 +1406,8 @@ func TestServer_TestInstanceSecretGeneratorSettingsReduces(t *testing.T) {
),
)
require.NoError(collect, err)
require.NotNil(collect, setting)
// ClientSecret
assert.Equal(collect, uint(64), *setting.ClientSecret.Length)
assert.Equal(collect, true, *setting.ClientSecret.IncludeLowerLetters)
@@ -16,14 +16,18 @@ ZITADEL integrates with Twilio as SMS provider.
## SMTP providers
Integration with most SMTP providers is possible through a generic SMTP provider template that allows you to configure custom SMTP providers.
Additionally, integration templates are available for:
For easy configuration, there are some integration templates available such as:
- Amazon SES
- Mailgun
- Mailjet
- Postmark
- Sendgrid
- SendGrid
- ...
Aside from those, it is also possible to configure a generic SMTP provider. With a generic SMTP provider you specify what
the SMTP server, username and what kind of auth method you want to use. Currently, the Console only supports plain auth.
However, with [the API's](https://zitadel.com/docs/apis/resources/admin/admin-service-add-email-provider-smtp) you can
configure XOAUTH2 as well.
:::info Default Provider ZITADEL Cloud
A default SMTP provider is configured for ZITADEL Cloud customers.
@@ -46,7 +50,7 @@ A provider with HTTP type will send the messages and the data to a pre-defined w
curl -L 'https://${CUSTOM_DOMAIN}/admin/v1/sms/http' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Authorization: Bearer ${TOKEN}' \
-d '{
"endpoint": "http://relay.example.com/provider",
"description": "provider description"
@@ -63,7 +67,7 @@ A provider with HTTP type will send the messages and the data to a pre-defined w
curl -L 'https://${CUSTOM_DOMAIN}/admin/v1/sms/:id/_activate' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Authorization: Bearer ${TOKEN}' \
-d '{}'
```
@@ -80,7 +84,7 @@ A provider with HTTP type will send the messages and the data to a pre-defined w
curl -L 'https://${CUSTOM_DOMAIN}/admin/v1/email/http' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Authorization: Bearer ${TOKEN}' \
-d '{
"endpoint": "http://relay.example.com/provider",
"description": "provider description"
@@ -97,7 +101,7 @@ A provider with HTTP type will send the messages and the data to a pre-defined w
curl -L 'https://${CUSTOM_DOMAIN}/admin/v1/email/:id/_activate' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Authorization: Bearer ${TOKEN}' \
-d '{}'
```
+130 -9
View File
@@ -75,7 +75,7 @@ func httpToPb(http *query.HTTP) *settings_pb.EmailProvider_Http {
}
func smtpToPb(config *query.SMTP) *settings_pb.EmailProvider_Smtp {
return &settings_pb.EmailProvider_Smtp{
ret := &settings_pb.EmailProvider_Smtp{
Smtp: &settings_pb.EmailProviderSMTP{
Tls: config.TLS,
Host: config.Host,
@@ -85,10 +85,34 @@ func smtpToPb(config *query.SMTP) *settings_pb.EmailProvider_Smtp {
ReplyToAddress: config.ReplyToAddress,
},
}
if config.PlainAuth != nil {
ret.Smtp.Auth = &settings_pb.EmailProviderSMTP_Plain{
Plain: &settings_pb.SMTPPlainAuth{},
}
}
if config.XOAuth2Auth != nil {
xoauth2 := &settings_pb.EmailProviderSMTP_Xoauth2{
Xoauth2: &settings_pb.SMTPXOAuth2Auth{
TokenEndpoint: config.XOAuth2Auth.TokenEndpoint,
Scopes: config.XOAuth2Auth.Scopes,
},
}
if config.XOAuth2Auth.ClientCredentials != nil {
xoauth2.Xoauth2.OAuth2Type = &settings_pb.SMTPXOAuth2Auth_ClientCredentials_{
ClientCredentials: &settings_pb.SMTPXOAuth2Auth_ClientCredentials{
ClientId: config.XOAuth2Auth.ClientCredentials.ClientId,
},
}
}
ret.Smtp.Auth = xoauth2
}
return ret
}
func addEmailProviderSMTPToConfig(ctx context.Context, req *admin_pb.AddEmailProviderSMTPRequest) *command.AddSMTPConfig {
return &command.AddSMTPConfig{
cmd := &command.AddSMTPConfig{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
Description: req.Description,
Tls: req.Tls,
@@ -97,12 +121,41 @@ func addEmailProviderSMTPToConfig(ctx context.Context, req *admin_pb.AddEmailPro
ReplyToAddress: req.ReplyToAddress,
Host: req.Host,
User: req.User,
Password: req.Password,
}
switch v := req.Auth.(type) {
case *admin_pb.AddEmailProviderSMTPRequest_None:
// Nothing to do, no auth is required
case *admin_pb.AddEmailProviderSMTPRequest_Plain:
cmd.PlainAuth = &command.PlainAuth{
Password: v.Plain.Password,
}
case *admin_pb.AddEmailProviderSMTPRequest_Xoauth2:
if xoauth2, ok := v.Xoauth2.OAuth2Type.(*admin_pb.SMTPXOAuth2Auth_ClientCredentials_); ok {
cmd.XOAuth2Auth = &command.XOAuth2Auth{
TokenEndpoint: v.Xoauth2.TokenEndpoint,
Scopes: v.Xoauth2.Scopes,
ClientCredentialsAuth: &command.OAuth2ClientCredentials{
ClientId: xoauth2.ClientCredentials.ClientId,
ClientSecret: xoauth2.ClientCredentials.ClientSecret,
},
}
}
default:
// ensure backwards compatibility
//nolint:staticcheck
if req.User != "" || req.Password != "" {
cmd.PlainAuth = &command.PlainAuth{
Password: req.Password,
}
}
}
return cmd
}
func updateEmailProviderSMTPToConfig(ctx context.Context, req *admin_pb.UpdateEmailProviderSMTPRequest) *command.ChangeSMTPConfig {
return &command.ChangeSMTPConfig{
cmd := &command.ChangeSMTPConfig{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
ID: req.Id,
Description: req.Description,
@@ -112,8 +165,41 @@ func updateEmailProviderSMTPToConfig(ctx context.Context, req *admin_pb.UpdateEm
ReplyToAddress: req.ReplyToAddress,
Host: req.Host,
User: req.User,
Password: req.Password,
}
switch v := req.Auth.(type) {
case *admin_pb.UpdateEmailProviderSMTPRequest_None:
// Nothing to do, no auth is required
case *admin_pb.UpdateEmailProviderSMTPRequest_Plain:
cmd.PlainAuth = &command.PlainAuth{
Password: v.Plain.Password,
}
case *admin_pb.UpdateEmailProviderSMTPRequest_Xoauth2:
cmd.XOAuth2Auth = xoauth2ToCmd(v)
default:
// ensure backwards compatibility
if req.User != "" || req.Password != "" {
cmd.PlainAuth = &command.PlainAuth{
Password: req.Password,
}
}
}
return cmd
}
func xoauth2ToCmd(xoauth2 *admin_pb.UpdateEmailProviderSMTPRequest_Xoauth2) *command.XOAuth2Auth {
cmd := &command.XOAuth2Auth{
TokenEndpoint: xoauth2.Xoauth2.TokenEndpoint,
Scopes: xoauth2.Xoauth2.Scopes,
}
if v, ok := xoauth2.Xoauth2.OAuth2Type.(*admin_pb.SMTPXOAuth2Auth_ClientCredentials_); ok {
cmd.ClientCredentialsAuth = &command.OAuth2ClientCredentials{
ClientId: v.ClientCredentials.ClientId,
ClientSecret: v.ClientCredentials.ClientSecret,
}
}
return cmd
}
func addEmailProviderHTTPToConfig(ctx context.Context, req *admin_pb.AddEmailProviderHTTPRequest) *command.AddSMTPConfigHTTP {
@@ -137,14 +223,49 @@ func updateEmailProviderHTTPToConfig(ctx context.Context, req *admin_pb.UpdateEm
}
func testEmailProviderSMTPToConfig(req *admin_pb.TestEmailProviderSMTPRequest) *smtp.Config {
return &smtp.Config{
cfg := &smtp.Config{
Tls: req.Tls,
From: req.SenderAddress,
FromName: req.SenderName,
SMTP: smtp.SMTP{
Host: req.Host,
User: req.User,
Password: req.Password,
Host: req.Host,
},
}
switch v := req.Auth.(type) {
case *admin_pb.TestEmailProviderSMTPRequest_None:
// Nothing to do, no auth is required
case *admin_pb.TestEmailProviderSMTPRequest_Plain:
cfg.SMTP.PlainAuth = &smtp.PlainAuthConfig{
User: req.User,
Password: v.Plain.Password,
}
case *admin_pb.TestEmailProviderSMTPRequest_Xoauth2:
cfg.SMTP.XOAuth2Auth = xoauth2ToSmtp(v, req.User)
default:
// ensure backwards compatibility
if req.User != "" || req.Password != "" {
cfg.SMTP.PlainAuth = &smtp.PlainAuthConfig{
User: req.User,
Password: req.Password,
}
}
}
return cfg
}
func xoauth2ToSmtp(xoauth2 *admin_pb.TestEmailProviderSMTPRequest_Xoauth2, user string) *smtp.XOAuth2AuthConfig {
cfg := &smtp.XOAuth2AuthConfig{
User: user,
TokenEndpoint: xoauth2.Xoauth2.TokenEndpoint,
Scopes: xoauth2.Xoauth2.Scopes,
}
if v, ok := xoauth2.Xoauth2.OAuth2Type.(*admin_pb.SMTPXOAuth2Auth_ClientCredentials_); ok {
cfg.ClientCredentialsAuth = &smtp.OAuth2ClientCredentials{
ClientId: v.ClientCredentials.ClientId,
ClientSecret: v.ClientCredentials.ClientSecret,
}
}
return cfg
}
+361 -12
View File
@@ -13,6 +13,7 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
"github.com/zitadel/zitadel/internal/query"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
object_pb "github.com/zitadel/zitadel/pkg/grpc/object"
@@ -85,6 +86,7 @@ func Test_emailProvidersToPb(t *testing.T) {
ReplyToAddress: "address",
Host: "host",
User: "user",
PlainAuth: &query.PlainAuth{},
},
HTTPConfig: nil,
State: 1,
@@ -125,6 +127,9 @@ func Test_emailProvidersToPb(t *testing.T) {
Host: "host",
User: "user",
ReplyToAddress: "address",
Auth: &settings_pb.EmailProviderSMTP_Plain{
Plain: &settings_pb.SMTPPlainAuth{},
},
},
},
},
@@ -184,6 +189,7 @@ func Test_emailProviderToProviderPb(t *testing.T) {
ReplyToAddress: "address",
Host: "host",
User: "user",
PlainAuth: &query.PlainAuth{},
},
HTTPConfig: &query.HTTP{
Endpoint: "endpoint",
@@ -210,6 +216,9 @@ func Test_emailProviderToProviderPb(t *testing.T) {
Host: "host",
User: "user",
ReplyToAddress: "address",
Auth: &settings_pb.EmailProviderSMTP_Plain{
Plain: &settings_pb.SMTPPlainAuth{},
},
},
},
},
@@ -254,7 +263,7 @@ func Test_emailProviderToProviderPb(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := emailProviderToProviderPb(tt.args.req)
assert.Equal(t, tt.res, got)
assert.EqualValues(t, tt.res, got)
})
}
}
@@ -363,6 +372,7 @@ func Test_smtpToPb(t *testing.T) {
TLS: true,
Host: "host",
User: "user",
PlainAuth: &query.PlainAuth{},
ReplyToAddress: "address",
},
},
@@ -374,6 +384,74 @@ func Test_smtpToPb(t *testing.T) {
Host: "host",
User: "user",
ReplyToAddress: "address",
Auth: &settings_pb.EmailProviderSMTP_Plain{
Plain: &settings_pb.SMTPPlainAuth{},
},
},
},
},
{
name: "legacy auth must still be supported",
args: args{
req: &query.SMTP{
User: "user",
PlainAuth: &query.PlainAuth{},
},
},
res: &settings_pb.EmailProvider_Smtp{
Smtp: &settings_pb.EmailProviderSMTP{
User: "user",
Auth: &settings_pb.EmailProviderSMTP_Plain{
Plain: &settings_pb.SMTPPlainAuth{},
},
},
},
},
{
name: "plain auth must map to plain",
args: args{
req: &query.SMTP{
User: "plain-user",
PlainAuth: &query.PlainAuth{},
},
},
res: &settings_pb.EmailProvider_Smtp{
Smtp: &settings_pb.EmailProviderSMTP{
User: "plain-user",
Auth: &settings_pb.EmailProviderSMTP_Plain{
Plain: &settings_pb.SMTPPlainAuth{},
},
},
},
},
{
name: "xoauth2 auth must map to xoauth2",
args: args{
req: &query.SMTP{
User: "xoauth2-user",
XOAuth2Auth: &query.XOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scopes"},
ClientCredentials: &query.XOAuthClientCredentials{
ClientId: "my-client",
},
},
},
},
res: &settings_pb.EmailProvider_Smtp{
Smtp: &settings_pb.EmailProviderSMTP{
User: "xoauth2-user",
Auth: &settings_pb.EmailProviderSMTP_Xoauth2{
Xoauth2: &settings_pb.SMTPXOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scopes"},
OAuth2Type: &settings_pb.SMTPXOAuth2Auth_ClientCredentials_{
ClientCredentials: &settings_pb.SMTPXOAuth2Auth_ClientCredentials{
ClientId: "my-client",
},
},
},
},
},
},
},
@@ -412,17 +490,90 @@ func Test_addEmailProviderSMTPToConfig(t *testing.T) {
},
},
res: &command.AddSMTPConfig{
ResourceOwner: "instance",
Description: "description",
Host: "host",
User: "user",
Password: "password",
ResourceOwner: "instance",
Description: "description",
Host: "host",
User: "user",
PlainAuth: &command.PlainAuth{
Password: "password",
},
Tls: true,
From: "sender",
FromName: "sendername",
ReplyToAddress: "address",
},
},
{
name: "legacy auth (username password) should map to plain",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.AddEmailProviderSMTPRequest{
User: "user",
Password: "password",
},
},
res: &command.AddSMTPConfig{
ResourceOwner: "instance",
User: "user",
PlainAuth: &command.PlainAuth{
Password: "password",
},
},
},
{
name: "plain auth should map to plain",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.AddEmailProviderSMTPRequest{
User: "plain-user",
Auth: &admin_pb.AddEmailProviderSMTPRequest_Plain{
Plain: &admin_pb.SMTPPlainAuth{
Password: "other_password",
},
},
},
},
res: &command.AddSMTPConfig{
ResourceOwner: "instance",
User: "plain-user",
PlainAuth: &command.PlainAuth{
Password: "other_password",
},
},
},
{
name: "xoauth2 auth should map to xoauth2",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.AddEmailProviderSMTPRequest{
User: "xoauth2-user",
Auth: &admin_pb.AddEmailProviderSMTPRequest_Xoauth2{
Xoauth2: &admin_pb.SMTPXOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scopes"},
OAuth2Type: &admin_pb.SMTPXOAuth2Auth_ClientCredentials_{
ClientCredentials: &admin_pb.SMTPXOAuth2Auth_ClientCredentials{
ClientId: "my-client",
ClientSecret: "some-secret",
},
},
},
},
},
},
res: &command.AddSMTPConfig{
ResourceOwner: "instance",
User: "xoauth2-user",
XOAuth2Auth: &command.XOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scopes"},
ClientCredentialsAuth: &command.OAuth2ClientCredentials{
ClientId: "my-client",
ClientSecret: "some-secret",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -459,18 +610,97 @@ func Test_updateEmailProviderSMTPToConfig(t *testing.T) {
},
},
res: &command.ChangeSMTPConfig{
ResourceOwner: "instance",
ID: "id",
Description: "description",
Host: "host",
User: "user",
Password: "password",
ResourceOwner: "instance",
ID: "id",
Description: "description",
Host: "host",
User: "user",
PlainAuth: &command.PlainAuth{
Password: "password",
},
Tls: true,
From: "sender",
FromName: "sendername",
ReplyToAddress: "address",
},
},
{
name: "legacy auth (username password) should map to plain",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.UpdateEmailProviderSMTPRequest{
User: "user",
Password: "password",
Id: "id",
},
},
res: &command.ChangeSMTPConfig{
ResourceOwner: "instance",
ID: "id",
User: "user",
PlainAuth: &command.PlainAuth{
Password: "password",
},
},
},
{
name: "plain auth should map to plain",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.UpdateEmailProviderSMTPRequest{
Id: "id",
User: "plain-user",
Auth: &admin_pb.UpdateEmailProviderSMTPRequest_Plain{
Plain: &admin_pb.SMTPPlainAuth{
Password: "other_password",
},
},
},
},
res: &command.ChangeSMTPConfig{
ResourceOwner: "instance",
ID: "id",
User: "plain-user",
PlainAuth: &command.PlainAuth{
Password: "other_password",
},
},
},
{
name: "xoauth2 auth should map to xoauth2",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.UpdateEmailProviderSMTPRequest{
Id: "id",
User: "xoauth2-user",
Auth: &admin_pb.UpdateEmailProviderSMTPRequest_Xoauth2{
Xoauth2: &admin_pb.SMTPXOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scopes"},
OAuth2Type: &admin_pb.SMTPXOAuth2Auth_ClientCredentials_{
ClientCredentials: &admin_pb.SMTPXOAuth2Auth_ClientCredentials{
ClientId: "my-client",
ClientSecret: "some-secret",
},
},
},
},
},
},
res: &command.ChangeSMTPConfig{
ResourceOwner: "instance",
ID: "id",
User: "xoauth2-user",
XOAuth2Auth: &command.XOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scopes"},
ClientCredentialsAuth: &command.OAuth2ClientCredentials{
ClientId: "my-client",
ClientSecret: "some-secret",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -552,3 +782,122 @@ func Test_updateEmailProviderHTTPToConfig(t *testing.T) {
})
}
}
func Test_testEmailProviderSMTPToConfig(t *testing.T) {
type args struct {
ctx context.Context
req *admin_pb.TestEmailProviderSMTPRequest
}
tests := []struct {
name string
args args
res *smtp.Config
}{
{
name: "all fields filled",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.TestEmailProviderSMTPRequest{
SenderAddress: "sender",
SenderName: "sendername",
Tls: true,
Host: "host",
User: "user",
Password: "password",
},
},
res: &smtp.Config{
Tls: true,
From: "sender",
FromName: "sendername",
SMTP: smtp.SMTP{
Host: "host",
PlainAuth: &smtp.PlainAuthConfig{
User: "user",
Password: "password",
},
},
},
},
{
name: "legacy auth (username password) should map to plain",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.TestEmailProviderSMTPRequest{
User: "user",
Password: "password",
},
},
res: &smtp.Config{
SMTP: smtp.SMTP{
PlainAuth: &smtp.PlainAuthConfig{
User: "user",
Password: "password",
},
},
},
},
{
name: "plain auth should map to plain",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.TestEmailProviderSMTPRequest{
User: "plain-user",
Auth: &admin_pb.TestEmailProviderSMTPRequest_Plain{
Plain: &admin_pb.SMTPPlainAuth{
Password: "other_password",
},
},
},
},
res: &smtp.Config{
SMTP: smtp.SMTP{
PlainAuth: &smtp.PlainAuthConfig{
User: "plain-user",
Password: "other_password",
},
},
},
},
{
name: "xoauth2 auth should map to xoauth2",
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance"),
req: &admin_pb.TestEmailProviderSMTPRequest{
User: "xoauth2-user",
Auth: &admin_pb.TestEmailProviderSMTPRequest_Xoauth2{
Xoauth2: &admin_pb.SMTPXOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scopes"},
OAuth2Type: &admin_pb.SMTPXOAuth2Auth_ClientCredentials_{
ClientCredentials: &admin_pb.SMTPXOAuth2Auth_ClientCredentials{
ClientId: "my-client",
ClientSecret: "some-secret",
},
},
},
},
},
},
res: &smtp.Config{
SMTP: smtp.SMTP{
XOAuth2Auth: &smtp.XOAuth2AuthConfig{
User: "xoauth2-user",
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scopes"},
ClientCredentialsAuth: &smtp.OAuth2ClientCredentials{
ClientId: "my-client",
ClientSecret: "some-secret",
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := testEmailProviderSMTPToConfig(tt.args.req)
assert.Equal(t, tt.res, got)
})
}
}
@@ -133,8 +133,9 @@ func SecretGeneratorTypeToDomain(generatorType settings_pb.SecretGeneratorType)
}
}
// Deprecated: use [addEmailProviderSMTPToConfig] instead
func addSMTPToConfig(ctx context.Context, req *admin_pb.AddSMTPConfigRequest) *command.AddSMTPConfig {
return &command.AddSMTPConfig{
cmd := &command.AddSMTPConfig{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
Description: req.Description,
Tls: req.Tls,
@@ -143,12 +144,20 @@ func addSMTPToConfig(ctx context.Context, req *admin_pb.AddSMTPConfigRequest) *c
ReplyToAddress: req.ReplyToAddress,
Host: req.Host,
User: req.User,
Password: req.Password,
}
if req.User != "" || req.Password != "" {
cmd.PlainAuth = &command.PlainAuth{
Password: req.Password,
}
}
return cmd
}
// Deprecated: use [updateEmailProviderSMTPToConfig] instead
func updateSMTPToConfig(ctx context.Context, req *admin_pb.UpdateSMTPConfigRequest) *command.ChangeSMTPConfig {
return &command.ChangeSMTPConfig{
cmd := &command.ChangeSMTPConfig{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
ID: req.Id,
Description: req.Description,
@@ -158,26 +167,35 @@ func updateSMTPToConfig(ctx context.Context, req *admin_pb.UpdateSMTPConfigReque
ReplyToAddress: req.ReplyToAddress,
Host: req.Host,
User: req.User,
Password: req.Password,
}
}
func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig {
if smtp.SMTPConfig != nil {
return &settings_pb.SMTPConfig{
Description: smtp.Description,
Tls: smtp.SMTPConfig.TLS,
SenderAddress: smtp.SMTPConfig.SenderAddress,
SenderName: smtp.SMTPConfig.SenderName,
ReplyToAddress: smtp.SMTPConfig.ReplyToAddress,
Host: smtp.SMTPConfig.Host,
User: smtp.SMTPConfig.User,
Details: obj_grpc.ToViewDetailsPb(smtp.Sequence, smtp.CreationDate, smtp.ChangeDate, smtp.ResourceOwner),
Id: smtp.ID,
State: settings_pb.SMTPConfigState(smtp.State),
if req.User != "" || req.Password != "" {
cmd.PlainAuth = &command.PlainAuth{
Password: req.Password,
}
}
return nil
return cmd
}
// Deprecated: use [smtpToPb] instead.
func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig {
if smtp.SMTPConfig == nil {
return nil
}
return &settings_pb.SMTPConfig{
Description: smtp.Description,
Tls: smtp.SMTPConfig.TLS,
SenderAddress: smtp.SMTPConfig.SenderAddress,
SenderName: smtp.SMTPConfig.SenderName,
ReplyToAddress: smtp.SMTPConfig.ReplyToAddress,
Host: smtp.SMTPConfig.Host,
Details: obj_grpc.ToViewDetailsPb(smtp.Sequence, smtp.CreationDate, smtp.ChangeDate, smtp.ResourceOwner),
Id: smtp.ID,
State: settings_pb.SMTPConfigState(smtp.State),
User: smtp.SMTPConfig.User,
}
}
func SecurityPolicyToPb(policy *query.SecurityPolicy) *settings_pb.SecurityPolicy {
+23 -7
View File
@@ -9,6 +9,7 @@ import (
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
)
// Deprecated: use [GetEmailProvider] instead.
func (s *Server) GetSMTPConfig(ctx context.Context, req *admin_pb.GetSMTPConfigRequest) (*admin_pb.GetSMTPConfigResponse, error) {
smtp, err := s.query.SMTPConfigActive(ctx, authz.GetInstance(ctx).InstanceID())
if err != nil {
@@ -19,6 +20,7 @@ func (s *Server) GetSMTPConfig(ctx context.Context, req *admin_pb.GetSMTPConfigR
}, nil
}
// Deprecated: use [GetEmailProviderById] instead.
func (s *Server) GetSMTPConfigById(ctx context.Context, req *admin_pb.GetSMTPConfigByIdRequest) (*admin_pb.GetSMTPConfigByIdResponse, error) {
smtp, err := s.query.SMTPConfigByID(ctx, authz.GetInstance(ctx).InstanceID(), req.Id)
if err != nil {
@@ -29,6 +31,7 @@ func (s *Server) GetSMTPConfigById(ctx context.Context, req *admin_pb.GetSMTPCon
}, nil
}
// Deprecated: use [AddEmailProviderSMTP] instead.
func (s *Server) AddSMTPConfig(ctx context.Context, req *admin_pb.AddSMTPConfigRequest) (*admin_pb.AddSMTPConfigResponse, error) {
config := addSMTPToConfig(ctx, req)
if err := s.command.AddSMTPConfig(ctx, config); err != nil {
@@ -40,6 +43,7 @@ func (s *Server) AddSMTPConfig(ctx context.Context, req *admin_pb.AddSMTPConfigR
}, nil
}
// Deprecated: use [UpdateEmailProviderSMTP] instead.
func (s *Server) UpdateSMTPConfig(ctx context.Context, req *admin_pb.UpdateSMTPConfigRequest) (*admin_pb.UpdateSMTPConfigResponse, error) {
config := updateSMTPToConfig(ctx, req)
if err := s.command.ChangeSMTPConfig(ctx, config); err != nil {
@@ -50,6 +54,7 @@ func (s *Server) UpdateSMTPConfig(ctx context.Context, req *admin_pb.UpdateSMTPC
}, nil
}
// Deprecated: use [RemoveEmailProviderSMTP] instead.
func (s *Server) RemoveSMTPConfig(ctx context.Context, req *admin_pb.RemoveSMTPConfigRequest) (*admin_pb.RemoveSMTPConfigResponse, error) {
details, err := s.command.RemoveSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id)
if err != nil {
@@ -60,6 +65,7 @@ func (s *Server) RemoveSMTPConfig(ctx context.Context, req *admin_pb.RemoveSMTPC
}, nil
}
// Deprecated: use [UpdateEmailProviderSMTPPassword] instead.
func (s *Server) UpdateSMTPConfigPassword(ctx context.Context, req *admin_pb.UpdateSMTPConfigPasswordRequest) (*admin_pb.UpdateSMTPConfigPasswordResponse, error) {
details, err := s.command.ChangeSMTPConfigPassword(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, req.Password)
if err != nil {
@@ -70,6 +76,7 @@ func (s *Server) UpdateSMTPConfigPassword(ctx context.Context, req *admin_pb.Upd
}, nil
}
// Deprecated: use [ListEmailProviders] instead.
func (s *Server) ListSMTPConfigs(ctx context.Context, req *admin_pb.ListSMTPConfigsRequest) (*admin_pb.ListSMTPConfigsResponse, error) {
queries, err := listSMTPConfigsToModel(req)
if err != nil {
@@ -85,6 +92,7 @@ func (s *Server) ListSMTPConfigs(ctx context.Context, req *admin_pb.ListSMTPConf
}, nil
}
// Deprecated: use [ActivateEmailProvider] instead.
func (s *Server) ActivateSMTPConfig(ctx context.Context, req *admin_pb.ActivateSMTPConfigRequest) (*admin_pb.ActivateSMTPConfigResponse, error) {
result, err := s.command.ActivateSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id)
if err != nil {
@@ -96,6 +104,7 @@ func (s *Server) ActivateSMTPConfig(ctx context.Context, req *admin_pb.ActivateS
}, nil
}
// Deprecated: use [DeactivateEmailProvider] instead.
func (s *Server) DeactivateSMTPConfig(ctx context.Context, req *admin_pb.DeactivateSMTPConfigRequest) (*admin_pb.DeactivateSMTPConfigResponse, error) {
result, err := s.command.DeactivateSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id)
if err != nil {
@@ -107,6 +116,7 @@ func (s *Server) DeactivateSMTPConfig(ctx context.Context, req *admin_pb.Deactiv
}, nil
}
// Deprecated: use [TestEmailProviderSMTPById] instead.
func (s *Server) TestSMTPConfigById(ctx context.Context, req *admin_pb.TestSMTPConfigByIdRequest) (*admin_pb.TestSMTPConfigByIdResponse, error) {
err := s.command.TestSMTPConfigById(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, req.ReceiverAddress)
if err != nil {
@@ -116,14 +126,20 @@ func (s *Server) TestSMTPConfigById(ctx context.Context, req *admin_pb.TestSMTPC
return &admin_pb.TestSMTPConfigByIdResponse{}, nil
}
// Deprecated: use [TestEmailProviderSMTP] instead.
func (s *Server) TestSMTPConfig(ctx context.Context, req *admin_pb.TestSMTPConfigRequest) (*admin_pb.TestSMTPConfigResponse, error) {
config := smtp.Config{}
config.Tls = req.Tls
config.From = req.SenderAddress
config.FromName = req.SenderName
config.SMTP.Host = req.Host
config.SMTP.User = req.User
config.SMTP.Password = req.Password
config := smtp.Config{
Tls: req.Tls,
From: req.SenderAddress,
FromName: req.SenderName,
SMTP: smtp.SMTP{
Host: req.Host,
PlainAuth: &smtp.PlainAuthConfig{
User: req.User,
Password: req.Password,
},
},
}
err := s.command.TestSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, req.ReceiverAddress, &config)
if err != nil {
@@ -165,7 +165,7 @@ func TestListInstances(t *testing.T) {
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
relTableState := integration.RelationalTablesEnableMatrix()
relTableState := integration.RelationalTablesEnableMatrix(t, ctx, ctxWithSysAuthZ)
tt := []struct {
testName string
@@ -222,16 +222,13 @@ func TestListInstances(t *testing.T) {
}
for _, stateCase := range relTableState {
integration.EnsureInstanceFeature(t, ctx, inst, stateCase.FeatureSet, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
assert.Equal(tCollect, stateCase.FeatureSet.GetEnableRelationalTables(), got.EnableRelationalTables.GetEnabled())
})
for _, tc := range tt {
// TODO(IAM-Marco): Fix this test for relational case when permission checks are in place (see https://github.com/zitadel/zitadel/issues/10917)
if tc.testName == "when unauthZ context should return unauthZ error" && stateCase.State == "when relational tables are enabled" {
if tc.testName == "when unauthZ context should return unauthZ error" && stateCase.Name == "when relational tables are enabled" {
continue
}
t.Run(fmt.Sprintf("%s - %s", stateCase.State, tc.testName), func(t *testing.T) {
t.Run(fmt.Sprintf("%s - %s", stateCase.Name, tc.testName), func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2.ListInstances(tc.inputContext, tc.inputRequest)
@@ -251,7 +248,6 @@ func TestListInstances(t *testing.T) {
})
}
}
}
func TestListCustomDomains(t *testing.T) {
@@ -34,9 +34,12 @@ func TestAddCustomDomain(t *testing.T) {
relationalInst := integration.NewInstance(ctxWithSysAuthZ)
iamOwnerRelationalCtx := relationalInst.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, relationalInst, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)}, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
})
integration.EnsureInstanceFeature(t, ctxWithSysAuthZ, relationalInst,
&feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)},
func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
assert.True(tCollect, got.EnableRelationalTables.GetEnabled())
},
)
t.Cleanup(func() {
inst.Client.InstanceV2Beta.DeleteInstance(ctxWithSysAuthZ, &instance.DeleteInstanceRequest{InstanceId: inst.ID()})
@@ -140,7 +140,7 @@ func TestListInstances(t *testing.T) {
orgOwnerCtx := inst.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner)
relTableState := integration.RelationalTablesEnableMatrix()
relTableState := integration.RelationalTablesEnableMatrix(t, ctx, ctxWithSysAuthZ)
tt := []struct {
testName string
@@ -196,12 +196,8 @@ func TestListInstances(t *testing.T) {
}
for _, stateCase := range relTableState {
integration.EnsureInstanceFeature(t, ctx, inst, stateCase.FeatureSet, func(tCollect *assert.CollectT, got *feature.GetInstanceFeaturesResponse) {
assert.Equal(tCollect, stateCase.FeatureSet.GetEnableRelationalTables(), got.EnableRelationalTables.GetEnabled())
})
for _, tc := range tt {
t.Run(fmt.Sprintf("%s - %s", stateCase.State, tc.testName), func(t *testing.T) {
t.Run(fmt.Sprintf("%s - %s", stateCase.Name, tc.testName), func(t *testing.T) {
// Test
res, err := inst.Client.InstanceV2Beta.ListInstances(tc.inputContext, tc.inputRequest)
File diff suppressed because it is too large Load Diff
+10 -2
View File
@@ -520,6 +520,14 @@ func setupSMTPSettings(commands *Commands, validations *[]preparation.Validation
if smtpConfig == nil {
return
}
var username string
var pwd []byte
if smtpConfig.SMTP.PlainAuth != nil {
username = smtpConfig.SMTP.PlainAuth.User
if smtpConfig.SMTP.PlainAuth != nil {
pwd = []byte(smtpConfig.SMTP.PlainAuth.Password)
}
}
*validations = append(*validations,
commands.prepareAddAndActivateSMTPConfig(
instanceAgg,
@@ -528,8 +536,8 @@ func setupSMTPSettings(commands *Commands, validations *[]preparation.Validation
smtpConfig.FromName,
smtpConfig.ReplyToAddress,
smtpConfig.SMTP.Host,
smtpConfig.SMTP.User,
[]byte(smtpConfig.SMTP.Password),
username,
pwd,
smtpConfig.Tls,
),
)
+151 -9
View File
@@ -2,6 +2,7 @@ package command
import (
"context"
"slices"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
@@ -29,10 +30,11 @@ type SMTPConfig struct {
TLS bool
Host string
User string
Password *crypto.CryptoValue
SenderAddress string
SenderName string
ReplyToAddress string
PlainAuth *instance.PlainAuth
XOAuth2Auth *instance.XOAuth2Auth
}
func NewIAMSMTPConfigWriteModel(instanceID, id, domain string) *IAMSMTPConfigWriteModel {
@@ -85,7 +87,11 @@ func (wm *IAMSMTPConfigWriteModel) Reduce() error {
continue
}
if e.Password != nil {
wm.SMTPConfig.Password = e.Password
if wm.SMTPConfig.PlainAuth == nil {
wm.SMTPConfig.PlainAuth = &instance.PlainAuth{Password: e.Password}
} else {
wm.SMTPConfig.PlainAuth.Password = e.Password
}
}
case *instance.SMTPConfigHTTPAddedEvent:
if wm.ID != e.ID {
@@ -157,7 +163,19 @@ func (wm *IAMSMTPConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
Builder()
}
func (wm *IAMSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, id, description string, tls bool, fromAddress, fromName, replyToAddress, smtpHost, smtpUser string, smtpPassword *crypto.CryptoValue) (*instance.SMTPConfigChangedEvent, bool, error) {
func (wm *IAMSMTPConfigWriteModel) NewChangedEvent(
ctx context.Context, aggregate *eventstore.Aggregate,
id,
description string,
tls bool,
fromAddress,
fromName,
replyToAddress,
smtpHost string,
smtpUser string,
plainAuth *instance.PlainAuth,
xoauth2Auth *instance.XOAuth2Auth,
) (*instance.SMTPConfigChangedEvent, bool, error) {
changes := make([]instance.SMTPConfigChanges, 0)
var err error
if wm.SMTPConfig == nil {
@@ -188,9 +206,13 @@ func (wm *IAMSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregat
if wm.SMTPConfig.User != smtpUser {
changes = append(changes, instance.ChangeSMTPConfigSMTPUser(smtpUser))
}
if smtpPassword != nil {
changes = append(changes, instance.ChangeSMTPConfigSMTPPassword(smtpPassword))
if plainAuth != nil {
changes = append(changes, smtpPlainAuthChanges(wm.SMTPConfig.PlainAuth, *plainAuth)...)
}
if xoauth2Auth != nil {
changes = append(changes, smtpXOAuthChanges(wm.SMTPConfig.XOAuth2Auth, *xoauth2Auth)...)
}
if len(changes) == 0 {
return nil, false, nil
}
@@ -201,6 +223,77 @@ func (wm *IAMSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregat
return changeEvent, true, nil
}
func smtpPlainAuthChanges(wm *instance.PlainAuth, auth instance.PlainAuth) []instance.SMTPConfigChanges {
// if no auth is yet present, set both
if wm == nil {
return []instance.SMTPConfigChanges{
instance.ChangeSMTPConfigSMTPPassword(auth.Password),
}
}
// if auth is already present, add changes for the changed values
var changes []instance.SMTPConfigChanges
if auth.Password != nil {
changes = append(changes, instance.ChangeSMTPConfigSMTPPassword(auth.Password))
}
return changes
}
func smtpXOAuthChanges(wm *instance.XOAuth2Auth, auth instance.XOAuth2Auth) []instance.SMTPConfigChanges {
// if no auth is yet present, set all properties
if wm == nil {
return []instance.SMTPConfigChanges{
instance.ChangeSMTPConfigXOAuth2ClientCredentialsClientId(auth.ClientCredentials.ClientId),
instance.ChangeSMTPConfigXOAuth2ClientCredentialsClientSecret(auth.ClientCredentials.ClientSecret),
instance.ChangeSMTPConfigXOAuth2TokenEndpoint(auth.TokenEndpoint),
instance.ChangeSMTPConfigXOAuth2Scopes(auth.Scopes),
}
}
// if auth is already present, add changes for the changed values
var changes []instance.SMTPConfigChanges
if wm.TokenEndpoint != auth.TokenEndpoint {
changes = append(changes, instance.ChangeSMTPConfigXOAuth2TokenEndpoint(auth.TokenEndpoint))
}
if len(wm.Scopes) != len(auth.Scopes) {
changes = append(changes, instance.ChangeSMTPConfigXOAuth2Scopes(auth.Scopes))
} else {
for _, s := range auth.Scopes {
if !slices.Contains(wm.Scopes, s) {
changes = append(changes, instance.ChangeSMTPConfigXOAuth2Scopes(auth.Scopes))
break
}
}
}
if auth.ClientCredentials != nil {
changes = append(changes, smtpXOAuthClientCredentialChanges(auth.ClientCredentials, *auth.ClientCredentials)...)
}
return changes
}
func smtpXOAuthClientCredentialChanges(wm *instance.XOAuth2ClientCredentials, cc instance.XOAuth2ClientCredentials) []instance.SMTPConfigChanges {
if wm == nil {
return []instance.SMTPConfigChanges{
instance.ChangeSMTPConfigXOAuth2ClientCredentialsClientId(cc.ClientId),
instance.ChangeSMTPConfigXOAuth2ClientCredentialsClientSecret(cc.ClientSecret),
}
}
var changes []instance.SMTPConfigChanges
if wm.ClientId != cc.ClientId {
changes = append(changes, instance.ChangeSMTPConfigXOAuth2ClientCredentialsClientId(cc.ClientId))
}
if wm.ClientSecret != cc.ClientSecret {
changes = append(changes, instance.ChangeSMTPConfigXOAuth2ClientCredentialsClientSecret(cc.ClientSecret))
}
return changes
}
func (wm *IAMSMTPConfigWriteModel) NewHTTPChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
@@ -242,11 +335,30 @@ func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigAddedEvent(e *instance.SMTPCo
TLS: e.TLS,
Host: e.Host,
User: e.User,
Password: e.Password,
SenderName: e.SenderName,
SenderAddress: e.SenderAddress,
ReplyToAddress: e.ReplyToAddress,
}
if e.PlainAuth != nil {
wm.SMTPConfig.PlainAuth = &instance.PlainAuth{
Password: e.PlainAuth.Password,
}
}
if e.XOAuth2Auth != nil {
wm.SMTPConfig.XOAuth2Auth = &instance.XOAuth2Auth{
TokenEndpoint: e.XOAuth2Auth.TokenEndpoint,
Scopes: e.XOAuth2Auth.Scopes,
}
if e.XOAuth2Auth.ClientCredentials != nil {
wm.SMTPConfig.XOAuth2Auth.ClientCredentials = &instance.XOAuth2ClientCredentials{
ClientId: e.XOAuth2Auth.ClientCredentials.ClientId,
ClientSecret: e.XOAuth2Auth.ClientCredentials.ClientSecret,
}
}
}
wm.State = domain.SMTPConfigStateInactive
// If ID has empty value we're dealing with the old and unique smtp settings
// These would be the default values for ID and State
@@ -290,9 +402,6 @@ func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigChangedEvent(e *instance.SMTP
if e.User != nil {
wm.SMTPConfig.User = *e.User
}
if e.Password != nil {
wm.SMTPConfig.Password = e.Password
}
if e.FromAddress != nil {
wm.SMTPConfig.SenderAddress = *e.FromAddress
}
@@ -303,6 +412,39 @@ func (wm *IAMSMTPConfigWriteModel) reduceSMTPConfigChangedEvent(e *instance.SMTP
wm.SMTPConfig.ReplyToAddress = *e.ReplyToAddress
}
if !e.PlainAuth.IsEmpty() {
if wm.SMTPConfig.PlainAuth == nil {
wm.SMTPConfig.PlainAuth = &instance.PlainAuth{}
wm.SMTPConfig.XOAuth2Auth = nil
}
if e.PlainAuth.Password != nil {
wm.SMTPConfig.PlainAuth.Password = e.PlainAuth.Password
} else if e.Password != nil {
wm.SMTPConfig.PlainAuth.Password = e.Password
}
}
if !e.XOAuth2Auth.IsEmpty() {
if wm.SMTPConfig.XOAuth2Auth == nil {
wm.SMTPConfig.XOAuth2Auth = &instance.XOAuth2Auth{}
wm.SMTPConfig.PlainAuth = nil
}
if e.XOAuth2Auth.TokenEndpoint != nil {
wm.SMTPConfig.XOAuth2Auth.TokenEndpoint = *e.XOAuth2Auth.TokenEndpoint
}
if e.XOAuth2Auth.Scopes != nil {
wm.SMTPConfig.XOAuth2Auth.Scopes = e.XOAuth2Auth.Scopes
}
if wm.SMTPConfig.XOAuth2Auth.ClientCredentials != nil && !e.XOAuth2Auth.ClientCredentials.IsEmpty() {
if e.XOAuth2Auth.ClientCredentials.ClientId != nil {
wm.SMTPConfig.XOAuth2Auth.ClientCredentials.ClientId = *e.XOAuth2Auth.ClientCredentials.ClientId
}
if e.XOAuth2Auth.ClientCredentials.ClientSecret != nil {
wm.SMTPConfig.XOAuth2Auth.ClientCredentials.ClientSecret = e.XOAuth2Auth.ClientCredentials.ClientSecret
}
}
}
// If ID has empty value we're dealing with the old and unique smtp settings
// These would be the default values for ID and State
if e.ID == "" {
+140 -36
View File
@@ -23,11 +23,25 @@ type AddSMTPConfig struct {
Description string
Host string
User string
Password string
Tls bool
From string
FromName string
ReplyToAddress string
PlainAuth *PlainAuth
XOAuth2Auth *XOAuth2Auth
}
type PlainAuth struct {
Password string
}
type XOAuth2Auth struct {
TokenEndpoint string
Scopes []string
ClientCredentialsAuth *OAuth2ClientCredentials
}
type OAuth2ClientCredentials struct {
ClientId string
ClientSecret string
}
func (c *Commands) AddSMTPConfig(ctx context.Context, config *AddSMTPConfig) (err error) {
@@ -55,14 +69,6 @@ func (c *Commands) AddSMTPConfig(ctx context.Context, config *AddSMTPConfig) (er
return zerrors.ThrowInvalidArgument(nil, "COMMAND-EvAtufIinh", "Errors.Invalid.Argument")
}
var smtpPassword *crypto.CryptoValue
if config.Password != "" {
smtpPassword, err = crypto.Encrypt([]byte(config.Password), c.smtpEncryption)
if err != nil {
return err
}
}
smtpConfigWriteModel, err := c.getSMTPConfig(ctx, config.ResourceOwner, config.ID, senderDomain)
if err != nil {
return err
@@ -73,6 +79,36 @@ func (c *Commands) AddSMTPConfig(ctx context.Context, config *AddSMTPConfig) (er
return err
}
var plainAuth *instance.PlainAuth
var xoauth2Auth *instance.XOAuth2Auth
if config.XOAuth2Auth != nil {
xoauth2Auth = &instance.XOAuth2Auth{
TokenEndpoint: config.XOAuth2Auth.TokenEndpoint,
Scopes: config.XOAuth2Auth.Scopes,
}
if config.XOAuth2Auth.ClientCredentialsAuth != nil {
clientSecret, err := crypto.Encrypt([]byte(config.XOAuth2Auth.ClientCredentialsAuth.ClientSecret), c.smtpEncryption)
if err != nil {
return err
}
xoauth2Auth.ClientCredentials = &instance.XOAuth2ClientCredentials{
ClientId: config.XOAuth2Auth.ClientCredentialsAuth.ClientId,
ClientSecret: clientSecret,
}
}
}
if config.PlainAuth != nil {
plainAuth = &instance.PlainAuth{}
if config.PlainAuth.Password != "" {
plainAuth.Password, err = crypto.Encrypt([]byte(config.PlainAuth.Password), c.smtpEncryption)
if err != nil {
return err
}
}
}
err = c.pushAppendAndReduce(ctx,
smtpConfigWriteModel,
instance.NewSMTPConfigAddedEvent(
@@ -86,12 +122,14 @@ func (c *Commands) AddSMTPConfig(ctx context.Context, config *AddSMTPConfig) (er
replyTo,
hostAndPort,
config.User,
smtpPassword,
plainAuth,
xoauth2Auth,
),
)
if err != nil {
return err
}
config.Details = writeModelToObjectDetails(&smtpConfigWriteModel.WriteModel)
return nil
}
@@ -104,11 +142,12 @@ type ChangeSMTPConfig struct {
Description string
Host string
User string
Password string
Tls bool
From string
FromName string
ReplyToAddress string
PlainAuth *PlainAuth
XOAuth2Auth *XOAuth2Auth
}
func (c *Commands) ChangeSMTPConfig(ctx context.Context, config *ChangeSMTPConfig) error {
@@ -132,15 +171,6 @@ func (c *Commands) ChangeSMTPConfig(ctx context.Context, config *ChangeSMTPConfi
return zerrors.ThrowInvalidArgument(nil, "COMMAND-kZNVkuL32L", "Errors.Invalid.Argument")
}
var smtpPassword *crypto.CryptoValue
var err error
if config.Password != "" {
smtpPassword, err = crypto.Encrypt([]byte(config.Password), c.smtpEncryption)
if err != nil {
return err
}
}
smtpConfigWriteModel, err := c.getSMTPConfig(ctx, config.ResourceOwner, config.ID, senderDomain)
if err != nil {
return err
@@ -155,6 +185,35 @@ func (c *Commands) ChangeSMTPConfig(ctx context.Context, config *ChangeSMTPConfi
return err
}
var plainAuth *instance.PlainAuth
var xoauth2Auth *instance.XOAuth2Auth
if config.PlainAuth != nil {
plainAuth = &instance.PlainAuth{}
if config.PlainAuth.Password != "" {
plainAuth.Password, err = crypto.Encrypt([]byte(config.PlainAuth.Password), c.smtpEncryption)
if err != nil {
return err
}
}
}
if config.XOAuth2Auth != nil {
xoauth2Auth = &instance.XOAuth2Auth{
TokenEndpoint: config.XOAuth2Auth.TokenEndpoint,
Scopes: config.XOAuth2Auth.Scopes,
}
if config.XOAuth2Auth.ClientCredentialsAuth != nil {
xoauth2Auth.ClientCredentials = &instance.XOAuth2ClientCredentials{
ClientId: config.XOAuth2Auth.ClientCredentialsAuth.ClientId,
}
if config.XOAuth2Auth.ClientCredentialsAuth.ClientSecret != "" {
xoauth2Auth.ClientCredentials.ClientSecret, err = crypto.Encrypt([]byte(config.XOAuth2Auth.ClientCredentialsAuth.ClientSecret), c.smtpEncryption)
if err != nil {
return err
}
}
}
}
changedEvent, hasChanged, err := smtpConfigWriteModel.NewChangedEvent(
ctx,
InstanceAggregateFromWriteModel(&smtpConfigWriteModel.WriteModel),
@@ -166,7 +225,8 @@ func (c *Commands) ChangeSMTPConfig(ctx context.Context, config *ChangeSMTPConfi
replyTo,
hostAndPort,
config.User,
smtpPassword,
plainAuth,
xoauth2Auth,
)
if err != nil {
return err
@@ -430,34 +490,50 @@ func (c *Commands) RemoveSMTPConfig(ctx context.Context, resourceOwner, id strin
}
func (c *Commands) TestSMTPConfig(ctx context.Context, instanceID, id, email string, config *smtp.Config) error {
password := config.SMTP.Password
if email == "" {
return zerrors.ThrowInvalidArgument(nil, "SMTP-p9uy", "Errors.SMTPConfig.TestEmailNotFound")
}
if id == "" && password == "" {
if id == "" && config.SMTP.PlainAuth != nil && config.SMTP.PlainAuth.Password == "" {
return zerrors.ThrowInvalidArgument(nil, "SMTP-p9kj", "Errors.SMTPConfig.TestPassword")
}
// If the password is not sent it'd mean that the password hasn't been changed for
// the stored configuration identified by its id so we can try to retrieve it
if id != "" && password == "" {
if id == "" && config.SMTP.XOAuth2Auth != nil &&
config.SMTP.XOAuth2Auth.ClientCredentialsAuth != nil &&
config.SMTP.XOAuth2Auth.ClientCredentialsAuth.ClientSecret == "" {
return zerrors.ThrowInvalidArgument(nil, "SMTP-9OP96s", "Errors.SMTPConfig.TestClientSecret")
}
if config.SMTP.PlainAuth != nil && config.SMTP.PlainAuth.Password == "" {
smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, "")
if err != nil {
return err
}
if !smtpConfigWriteModel.State.Exists() || smtpConfigWriteModel.SMTPConfig == nil {
if !smtpConfigWriteModel.State.Exists() || smtpConfigWriteModel.SMTPConfig == nil || smtpConfigWriteModel.SMTPConfig.PlainAuth == nil {
return zerrors.ThrowNotFound(nil, "SMTP-p9cc", "Errors.SMTPConfig.NotFound")
}
password, err = crypto.DecryptString(smtpConfigWriteModel.SMTPConfig.Password, c.smtpEncryption)
config.SMTP.PlainAuth.Password, err = crypto.DecryptString(smtpConfigWriteModel.SMTPConfig.PlainAuth.Password, c.smtpEncryption)
if err != nil {
return err
}
}
config.SMTP.Password = password
if config.SMTP.XOAuth2Auth != nil && config.SMTP.XOAuth2Auth.ClientCredentialsAuth != nil && config.SMTP.XOAuth2Auth.ClientCredentialsAuth.ClientSecret == "" {
smtpConfigWriteModel, err := c.getSMTPConfig(ctx, instanceID, id, "")
if err != nil {
return err
}
if !smtpConfigWriteModel.State.Exists() || smtpConfigWriteModel.SMTPConfig == nil || smtpConfigWriteModel.SMTPConfig.XOAuth2Auth == nil {
return zerrors.ThrowNotFound(nil, "SMTP-p9c2", "Errors.SMTPConfig.NotFound")
}
config.SMTP.XOAuth2Auth.ClientCredentialsAuth.ClientSecret, err = crypto.DecryptString(smtpConfigWriteModel.SMTPConfig.XOAuth2Auth.ClientCredentials.ClientSecret, c.smtpEncryption)
if err != nil {
return err
}
}
// Try to send an email
err := smtp.TestConfiguration(config, email)
@@ -486,9 +562,36 @@ func (c *Commands) TestSMTPConfigById(ctx context.Context, instanceID, id, email
return zerrors.ThrowNotFound(nil, "SMTP-99klw", "Errors.SMTPConfig.NotFound")
}
password, err := crypto.DecryptString(smtpConfigWriteModel.SMTPConfig.Password, c.smtpEncryption)
if err != nil {
return err
var plainAuth *smtp.PlainAuthConfig
var xoauth2Auth *smtp.XOAuth2AuthConfig
if smtpConfigWriteModel.SMTPConfig.PlainAuth != nil {
password, err := crypto.DecryptString(smtpConfigWriteModel.SMTPConfig.PlainAuth.Password, c.smtpEncryption)
if err != nil {
return err
}
plainAuth = &smtp.PlainAuthConfig{
User: smtpConfigWriteModel.SMTPConfig.User,
Password: password,
}
}
if smtpConfigWriteModel.SMTPConfig.XOAuth2Auth != nil {
xoauth2Auth = &smtp.XOAuth2AuthConfig{
User: smtpConfigWriteModel.SMTPConfig.User,
TokenEndpoint: smtpConfigWriteModel.SMTPConfig.XOAuth2Auth.TokenEndpoint,
Scopes: smtpConfigWriteModel.SMTPConfig.XOAuth2Auth.Scopes,
}
if smtpConfigWriteModel.SMTPConfig.XOAuth2Auth.ClientCredentials != nil {
clientSecret, err := crypto.DecryptString(smtpConfigWriteModel.SMTPConfig.XOAuth2Auth.ClientCredentials.ClientSecret, c.smtpEncryption)
if err != nil {
return err
}
xoauth2Auth.ClientCredentialsAuth = &smtp.OAuth2ClientCredentials{
ClientId: smtpConfigWriteModel.SMTPConfig.XOAuth2Auth.ClientCredentials.ClientId,
ClientSecret: clientSecret,
}
}
}
smtpConfig := &smtp.Config{
@@ -496,9 +599,9 @@ func (c *Commands) TestSMTPConfigById(ctx context.Context, instanceID, id, email
From: smtpConfigWriteModel.SMTPConfig.SenderAddress,
FromName: smtpConfigWriteModel.SMTPConfig.SenderName,
SMTP: smtp.SMTP{
Host: smtpConfigWriteModel.SMTPConfig.Host,
User: smtpConfigWriteModel.SMTPConfig.User,
Password: password,
Host: smtpConfigWriteModel.SMTPConfig.Host,
PlainAuth: plainAuth,
XOAuth2Auth: xoauth2Auth,
},
}
@@ -582,7 +685,8 @@ func (c *Commands) prepareAddAndActivateSMTPConfig(a *instance.Aggregate, descri
replyTo,
hostAndPort,
user,
smtpPassword,
&instance.PlainAuth{Password: smtpPassword},
nil,
),
instance.NewSMTPConfigActivatedEvent(
ctx,
+674 -59
View File
@@ -78,7 +78,9 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
From: "[email protected]",
Host: "host:587",
User: "user",
Password: "password",
PlainAuth: &PlainAuth{
Password: "password",
},
},
},
res: res{
@@ -136,12 +138,15 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
"",
"host:587",
"user",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
&instance.PlainAuth{
Password: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
},
nil,
),
),
),
@@ -157,7 +162,9 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
FromName: "name",
Host: "host:587",
User: "user",
Password: "password",
PlainAuth: &PlainAuth{
Password: "password",
},
},
},
res: res{
@@ -197,12 +204,15 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
"[email protected]",
"host:587",
"user",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
&instance.PlainAuth{
Password: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
},
nil,
),
),
),
@@ -219,7 +229,9 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
ReplyToAddress: "[email protected]",
Host: "host:587",
User: "user",
Password: "password",
PlainAuth: &PlainAuth{
Password: "password",
},
},
},
res: res{
@@ -244,7 +256,9 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
FromName: "name",
Host: "host",
User: "user",
Password: "password",
PlainAuth: &PlainAuth{
Password: "password",
},
},
},
res: res{
@@ -269,7 +283,9 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
FromName: "name",
Host: " ",
User: "user",
Password: "password",
PlainAuth: &PlainAuth{
Password: "password",
},
},
},
res: res{
@@ -309,12 +325,15 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
"",
"[2001:db8::1]:2525",
"user",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
&instance.PlainAuth{
Password: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
},
nil,
),
),
),
@@ -330,7 +349,200 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
FromName: "name",
Host: "[2001:db8::1]:2525",
User: "user",
Password: "password",
PlainAuth: &PlainAuth{
Password: "password",
},
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
},
},
{
name: "add smtp config with plain auth but no password, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewDomainAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"domain.ch",
false,
),
),
eventFromEventPusher(
instance.NewDomainPolicyAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
true, true, false,
),
),
),
expectPush(
instance.NewSMTPConfigAddedEvent(
context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"configid",
"test",
true,
"[email protected]",
"name",
"[email protected]",
"host:587",
"user",
&instance.PlainAuth{},
nil,
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
smtp: &AddSMTPConfig{
ResourceOwner: "INSTANCE",
Description: "test",
Tls: true,
From: "[email protected]",
FromName: "name",
ReplyToAddress: "[email protected]",
Host: "host:587",
User: "user",
PlainAuth: &PlainAuth{},
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
},
},
{
name: "add smtp config without auth, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewDomainAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"domain.ch",
false,
),
),
eventFromEventPusher(
instance.NewDomainPolicyAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
true, true, false,
),
),
),
expectPush(
instance.NewSMTPConfigAddedEvent(
context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"configid",
"test",
true,
"[email protected]",
"name",
"[email protected]",
"host:587",
"",
nil,
nil,
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
smtp: &AddSMTPConfig{
ResourceOwner: "INSTANCE",
Description: "test",
Tls: true,
From: "[email protected]",
FromName: "name",
ReplyToAddress: "[email protected]",
Host: "host:587",
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
},
},
{
name: "add smtp config with xoauth2 auth, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewDomainAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"domain.ch",
false,
),
),
eventFromEventPusher(
instance.NewDomainPolicyAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
true, true, false,
),
),
),
expectPush(
instance.NewSMTPConfigAddedEvent(
context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"configid",
"test",
true,
"[email protected]",
"name",
"[email protected]",
"host:587",
"user",
nil,
&instance.XOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scope"},
ClientCredentials: &instance.XOAuth2ClientCredentials{
ClientId: "client-id",
ClientSecret: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
},
},
),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "configid"),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
smtp: &AddSMTPConfig{
ResourceOwner: "INSTANCE",
Description: "test",
Tls: true,
From: "[email protected]",
FromName: "name",
ReplyToAddress: "[email protected]",
Host: "host:587",
User: "user",
XOAuth2Auth: &XOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scope"},
ClientCredentialsAuth: &OAuth2ClientCredentials{
ClientId: "client-id",
ClientSecret: "password",
},
},
},
},
res: res{
@@ -365,6 +577,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
alg crypto.EncryptionAlgorithm
}
type args struct {
smtp *ChangeSMTPConfig
@@ -443,6 +656,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
FromName: "name",
Host: "host:587",
User: "user",
PlainAuth: &PlainAuth{},
},
},
res: res{
@@ -481,7 +695,10 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
"",
"host:587",
"user",
&crypto.CryptoValue{},
&instance.PlainAuth{
Password: &crypto.CryptoValue{},
},
nil,
),
),
),
@@ -497,6 +714,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
FromName: "name",
Host: "host:587",
User: "user",
PlainAuth: &PlainAuth{},
},
},
res: res{
@@ -533,7 +751,10 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
"",
"host:587",
"user",
&crypto.CryptoValue{},
&instance.PlainAuth{
Password: &crypto.CryptoValue{},
},
nil,
),
),
),
@@ -549,6 +770,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
FromName: "name",
Host: "host:587",
User: "user",
PlainAuth: &PlainAuth{},
},
},
res: res{
@@ -587,7 +809,10 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
"",
"host:587",
"user",
&crypto.CryptoValue{},
&instance.PlainAuth{
Password: &crypto.CryptoValue{},
},
nil,
),
),
),
@@ -602,6 +827,8 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
"[email protected]",
"host2:587",
"user2",
&instance.PlainAuth{},
nil,
),
),
),
@@ -617,6 +844,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
ReplyToAddress: "[email protected]",
Host: "host2:587",
User: "user2",
PlainAuth: &PlainAuth{},
},
},
res: res{
@@ -640,7 +868,9 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
FromName: "name",
Host: "host",
User: "user",
Password: "password",
PlainAuth: &PlainAuth{
Password: "password",
},
},
},
res: res{
@@ -664,7 +894,9 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
FromName: "name",
Host: " ",
User: "user",
Password: "password",
PlainAuth: &PlainAuth{
Password: "password",
},
},
},
res: res{
@@ -703,7 +935,10 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
"",
"host:587",
"user",
&crypto.CryptoValue{},
&instance.PlainAuth{
Password: &crypto.CryptoValue{},
},
nil,
),
),
),
@@ -718,6 +953,8 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
"[email protected]",
"[2001:db8::1]:2525",
"user2",
&instance.PlainAuth{},
nil,
),
),
),
@@ -733,6 +970,193 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
ReplyToAddress: "[email protected]",
Host: "[2001:db8::1]:2525",
User: "user2",
PlainAuth: &PlainAuth{},
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
},
},
{
name: "change auth from plain to xoauth2, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewDomainAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"domain.ch",
false,
),
),
eventFromEventPusher(
instance.NewDomainPolicyAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
true, true, true,
),
),
eventFromEventPusher(
instance.NewSMTPConfigAddedEvent(
context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"ID",
"",
true,
"[email protected]",
"name",
"",
"host:587",
"user",
&instance.PlainAuth{},
nil,
),
),
),
expectPush(
newSMTPConfigChangedEvent(
context.Background(),
"ID",
"test",
false,
"[email protected]",
"name2",
"[email protected]",
"host2:587",
"user2",
nil,
&instance.XOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scope"},
ClientCredentials: &instance.XOAuth2ClientCredentials{
ClientId: "client-id",
ClientSecret: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
},
},
),
),
),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
smtp: &ChangeSMTPConfig{
ResourceOwner: "INSTANCE",
ID: "ID",
Description: "test",
Tls: false,
From: "[email protected]",
FromName: "name2",
ReplyToAddress: "[email protected]",
Host: "host2:587",
User: "user2",
XOAuth2Auth: &XOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scope"},
ClientCredentialsAuth: &OAuth2ClientCredentials{
ClientId: "client-id",
ClientSecret: "password",
},
},
},
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
},
},
{
name: "change auth from xoauth2 to plain, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewDomainAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"domain.ch",
false,
),
),
eventFromEventPusher(
instance.NewDomainPolicyAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
true, true, true,
),
),
eventFromEventPusher(
instance.NewSMTPConfigAddedEvent(
context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"ID",
"",
true,
"[email protected]",
"name",
"",
"host:587",
"user2",
nil,
&instance.XOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scope"},
ClientCredentials: &instance.XOAuth2ClientCredentials{
ClientId: "client-id",
ClientSecret: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("client-secret"),
},
},
},
),
),
),
expectPush(
newSMTPConfigChangedEvent(
context.Background(),
"ID",
"test",
false,
"[email protected]",
"name2",
"[email protected]",
"host2:587",
"user",
&instance.PlainAuth{
Password: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
},
nil,
),
),
),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
smtp: &ChangeSMTPConfig{
ResourceOwner: "INSTANCE",
ID: "ID",
Description: "test",
Tls: false,
From: "[email protected]",
FromName: "name2",
ReplyToAddress: "[email protected]",
Host: "host2:587",
User: "user",
PlainAuth: &PlainAuth{
Password: "password",
},
},
},
res: res{
@@ -745,7 +1169,8 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore(t),
eventstore: tt.fields.eventstore(t),
smtpEncryption: tt.fields.alg,
}
err := r.ChangeSMTPConfig(context.Background(), tt.args.smtp)
if tt.res.err == nil {
@@ -842,7 +1267,10 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) {
"",
"host:587",
"user",
&crypto.CryptoValue{},
&instance.PlainAuth{
Password: &crypto.CryptoValue{},
},
nil,
),
),
eventFromEventPusher(
@@ -1261,7 +1689,10 @@ func TestCommandSide_ActivateSMTPConfig(t *testing.T) {
"",
"host:587",
"user",
&crypto.CryptoValue{},
&instance.PlainAuth{
Password: &crypto.CryptoValue{},
},
nil,
),
),
),
@@ -1302,7 +1733,10 @@ func TestCommandSide_ActivateSMTPConfig(t *testing.T) {
"",
"host:587",
"user",
&crypto.CryptoValue{},
&instance.PlainAuth{
Password: &crypto.CryptoValue{},
},
nil,
),
),
eventFromEventPusher(
@@ -1466,7 +1900,10 @@ func TestCommandSide_DeactivateSMTPConfig(t *testing.T) {
"",
"host:587",
"user",
&crypto.CryptoValue{},
&instance.PlainAuth{
Password: &crypto.CryptoValue{},
},
nil,
),
),
eventFromEventPusher(
@@ -1513,7 +1950,10 @@ func TestCommandSide_DeactivateSMTPConfig(t *testing.T) {
"",
"host:587",
"user",
&crypto.CryptoValue{},
&instance.PlainAuth{
Password: &crypto.CryptoValue{},
},
nil,
),
),
eventFromEventPusher(
@@ -1692,7 +2132,10 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) {
"",
"host:587",
"user",
&crypto.CryptoValue{},
&instance.PlainAuth{
Password: &crypto.CryptoValue{},
},
nil,
),
),
),
@@ -1837,9 +2280,11 @@ func TestCommandSide_TestSMTPConfig(t *testing.T) {
From: "test@example,com",
FromName: "Test",
SMTP: smtp.SMTP{
User: "user",
Password: "",
Host: "example.com:2525",
PlainAuth: &smtp.PlainAuthConfig{
User: "user",
Password: "",
},
Host: "example.com:2525",
},
},
},
@@ -1863,9 +2308,11 @@ func TestCommandSide_TestSMTPConfig(t *testing.T) {
From: "test@example,com",
FromName: "Test",
SMTP: smtp.SMTP{
User: "user",
Password: "",
Host: "example.com:2525",
PlainAuth: &smtp.PlainAuthConfig{
User: "user",
Password: "",
},
Host: "example.com:2525",
},
},
},
@@ -1874,7 +2321,7 @@ func TestCommandSide_TestSMTPConfig(t *testing.T) {
},
},
{
name: "valid new smtp config, wrong auth, ok",
name: "valid new smtp config, wrong plain auth, ok",
fields: fields{
eventstore: expectEventstore(),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
@@ -1887,9 +2334,11 @@ func TestCommandSide_TestSMTPConfig(t *testing.T) {
From: "[email protected]",
FromName: "Test",
SMTP: smtp.SMTP{
User: "user",
Password: "password",
Host: "mail.smtp2go.com:2525",
PlainAuth: &smtp.PlainAuthConfig{
User: "user",
Password: "password",
},
Host: "mail.smtp2go.com:2525",
},
},
},
@@ -1914,11 +2363,103 @@ func TestCommandSide_TestSMTPConfig(t *testing.T) {
"",
"mail.smtp2go.com:2525",
"user",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
&instance.PlainAuth{
Password: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
},
nil,
),
),
),
),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
instanceID: "INSTANCE",
email: "email",
id: "ID",
config: smtp.Config{
From: "[email protected]",
FromName: "Test",
SMTP: smtp.SMTP{
PlainAuth: &smtp.PlainAuthConfig{
User: "user",
Password: "",
},
Host: "mail.smtp2go.com:2525",
},
},
},
res: res{
err: zerrors.IsInternal,
},
},
{
name: "valid new smtp config, wrong xoauth2 auth, ok",
fields: fields{
eventstore: expectEventstore(),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
instanceID: "INSTANCE",
email: "email",
config: smtp.Config{
From: "[email protected]",
FromName: "Test",
SMTP: smtp.SMTP{
XOAuth2Auth: &smtp.XOAuth2AuthConfig{
User: "user",
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scope"},
ClientCredentialsAuth: &smtp.OAuth2ClientCredentials{
ClientId: "client-id",
ClientSecret: "client-secret",
},
},
Host: "mail.smtp2go.com:2525",
},
},
},
res: res{
err: zerrors.IsInternal,
},
},
{
name: "valid smtp config using stored password, wrong auth, ok",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
instance.NewSMTPConfigAddedEvent(
context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"ID",
"test",
true,
"from",
"name",
"",
"mail.smtp2go.com:2525",
"user",
nil,
&instance.XOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scope"},
ClientCredentials: &instance.XOAuth2ClientCredentials{
ClientId: "client-id",
ClientSecret: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
},
},
),
),
@@ -1935,9 +2476,15 @@ func TestCommandSide_TestSMTPConfig(t *testing.T) {
From: "[email protected]",
FromName: "Test",
SMTP: smtp.SMTP{
User: "user",
Password: "",
Host: "mail.smtp2go.com:2525",
XOAuth2Auth: &smtp.XOAuth2AuthConfig{
User: "user",
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scope"},
ClientCredentialsAuth: &smtp.OAuth2ClientCredentials{
ClientId: "client-id",
},
},
Host: "mail.smtp2go.com:2525",
},
},
},
@@ -2032,7 +2579,7 @@ func TestCommandSide_TestSMTPConfigById(t *testing.T) {
},
},
{
name: "valid smtp config, wrong auth, ok",
name: "valid smtp config, wrong plain auth, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
@@ -2049,11 +2596,62 @@ func TestCommandSide_TestSMTPConfigById(t *testing.T) {
"",
"mail.smtp2go.com:2525",
"user",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
&instance.PlainAuth{
Password: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("password"),
},
},
nil,
),
),
),
),
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
id: "ID",
instanceID: "INSTANCE",
email: "[email protected]",
},
res: res{
err: zerrors.IsInternal,
},
},
{
name: "valid smtp config, wrong xoauth2 auth, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
instance.NewSMTPConfigAddedEvent(
context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"ID",
"test",
true,
"from",
"name",
"",
"mail.smtp2go.com:2525",
"user",
nil,
&instance.XOAuth2Auth{
TokenEndpoint: "auth.example.com/token",
Scopes: []string{"scope"},
ClientCredentials: &instance.XOAuth2ClientCredentials{
ClientId: "client-id",
ClientSecret: &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("client-secret"),
},
},
},
),
),
@@ -2089,7 +2687,7 @@ func TestCommandSide_TestSMTPConfigById(t *testing.T) {
}
}
func newSMTPConfigChangedEvent(ctx context.Context, id, description string, tls bool, fromAddress, fromName, replyTo, host, user string) *instance.SMTPConfigChangedEvent {
func newSMTPConfigChangedEvent(ctx context.Context, id, description string, tls bool, fromAddress, fromName, replyTo, host, user string, plainAuth *instance.PlainAuth, xoauth2Auth *instance.XOAuth2Auth) *instance.SMTPConfigChangedEvent {
changes := []instance.SMTPConfigChanges{
instance.ChangeSMTPConfigDescription(description),
instance.ChangeSMTPConfigTLS(tls),
@@ -2099,6 +2697,23 @@ func newSMTPConfigChangedEvent(ctx context.Context, id, description string, tls
instance.ChangeSMTPConfigSMTPHost(host),
instance.ChangeSMTPConfigSMTPUser(user),
}
if plainAuth != nil {
changes = append(changes,
instance.ChangeSMTPConfigSMTPPassword(plainAuth.Password),
)
}
if xoauth2Auth != nil {
changes = append(changes,
instance.ChangeSMTPConfigXOAuth2TokenEndpoint(xoauth2Auth.TokenEndpoint),
instance.ChangeSMTPConfigXOAuth2Scopes(xoauth2Auth.Scopes),
)
if xoauth2Auth.ClientCredentials != nil {
changes = append(changes,
instance.ChangeSMTPConfigXOAuth2ClientCredentialsClientId(xoauth2Auth.ClientCredentials.ClientId),
instance.ChangeSMTPConfigXOAuth2ClientCredentialsClientSecret(xoauth2Auth.ClientCredentials.ClientSecret),
)
}
}
event, _ := instance.NewSMTPConfigChangeEvent(ctx,
&instance.NewAggregate("INSTANCE").Aggregate,
id,
+24 -11
View File
@@ -31,20 +31,33 @@ func EnsureInstanceFeature(t *testing.T, ctx context.Context, instance *Instance
}
type RelationalTableFeatureMatrix struct {
State string
FeatureSet *feature.SetInstanceFeaturesRequest
Name string
Inst *Instance
InstOwner context.Context
}
// TODO(IAM-Marco): Once we have gotten rid of eventstore, this can be removed
func RelationalTablesEnableMatrix() []RelationalTableFeatureMatrix {
func RelationalTablesEnableMatrix(t *testing.T, ctx context.Context, sysAuthZ context.Context) []RelationalTableFeatureMatrix {
return []RelationalTableFeatureMatrix{
{
State: "when relational tables are disabled",
FeatureSet: &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(false)},
},
{
State: "when relational tables are enabled",
FeatureSet: &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(true)},
},
func() RelationalTableFeatureMatrix {
inst := NewInstance(sysAuthZ)
instOwner := inst.WithAuthorizationToken(ctx, UserTypeIAMOwner)
return RelationalTableFeatureMatrix{
Name: "when relational tables are disabled",
Inst: inst,
InstOwner: instOwner,
}
}(),
func() RelationalTableFeatureMatrix {
inst := NewInstance(sysAuthZ)
instOwner := inst.WithAuthorizationToken(ctx, UserTypeIAMOwner)
_, err := inst.Client.FeatureV2.SetInstanceFeatures(instOwner, &feature.SetInstanceFeaturesRequest{EnableRelationalTables: gu.Ptr(false)})
require.NoError(t, err)
return RelationalTableFeatureMatrix{
Name: "when relational tables are enabled",
Inst: inst,
InstOwner: instOwner,
}
}(),
}
}
+15 -7
View File
@@ -148,14 +148,22 @@ func (smtpConfig SMTP) getSMTPClientWithStartTls(host string) (*smtp.Client, err
}
func (smtpConfig SMTP) smtpAuth(client *smtp.Client, host string) error {
if !smtpConfig.HasAuth() {
if smtpConfig.XOAuth2Auth != nil {
err := client.Auth(XOAuth2Auth(*smtpConfig.XOAuth2Auth, host))
if err != nil {
return zerrors.ThrowInternal(err, "EMAIL-av88SB", "Errors.SMTP.CouldNotXOAuth2")
}
return nil
}
// Auth
err := client.Auth(PlainOrLoginAuth(smtpConfig.User, smtpConfig.Password, host))
if err != nil {
return zerrors.ThrowInternal(err, "EMAIL-s9kfs", "Errors.SMTP.CouldNotAuth")
if smtpConfig.PlainAuth != nil {
err := client.Auth(PlainOrLoginAuth(*smtpConfig.PlainAuth, host))
if err != nil {
return zerrors.ThrowInternal(err, "EMAIL-s9kfs", "Errors.SMTP.CouldNotAuth")
}
return nil
}
return nil
}
@@ -176,11 +184,11 @@ func TestConfiguration(cfg *Config, testEmail string) error {
}
if err := client.Mail(cfg.From); err != nil {
return zerrors.ThrowInternal(err, "EMAIL-s3is3", "Errors.SMTP.CouldNotSetSender")
return zerrors.ThrowInternal(err, "EMAIL-s5is5", "Errors.SMTP.CouldNotSetSender")
}
if err := client.Rcpt(testEmail); err != nil {
return zerrors.ThrowInternal(err, "EMAIL-s4is4", "Errors.SMTP.CouldNotSetRecipient")
return zerrors.ThrowInternal(err, "EMAIL-s6is6", "Errors.SMTP.CouldNotSetRecipient")
}
// Open data connection
+3 -13
View File
@@ -9,19 +9,9 @@ type Config struct {
}
type SMTP struct {
Host string
User string
Password string
}
// HasAuth reports whether SMTP authentication should be attempted.
//
// It returns true when a User is configured, regardless of whether Password
// is set. This behavior is intentional to support SMTP servers that allow
// authentication without a password. For servers that require a password,
// this means authentication may still be attempted with an empty password.
func (smtp *SMTP) HasAuth() bool {
return smtp.User != ""
Host string
PlainAuth *PlainAuthConfig
XOAuth2Auth *XOAuth2AuthConfig
}
type ConfigHTTP struct {
@@ -0,0 +1,41 @@
package smtp
import (
"net/smtp"
"slices"
"github.com/zitadel/zitadel/internal/zerrors"
)
type GenericAuth struct {
Host string
PlainOrLogin *plainOrLoginAuth
XOAuth2 *xoauth2Auth
selectedMethod smtp.Auth
}
func (a *GenericAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
if server.Name != a.Host {
return "", nil, zerrors.ThrowInternal(nil, "SMTP-BtXgkX", "wrong host name")
}
switch {
case slices.Contains(server.Auth, "XOAUTH2") && a.XOAuth2 != nil:
a.selectedMethod = a.XOAuth2
case slices.Contains(server.Auth, "PLAIN") && a.PlainOrLogin != nil:
a.selectedMethod = a.PlainOrLogin
case slices.Contains(server.Auth, "LOGIN") && a.PlainOrLogin != nil:
a.selectedMethod = a.PlainOrLogin
default:
return "", nil, zerrors.ThrowInternalf(nil, "SMTP-3mBHTz", "no supported auth method found (server supports: %v)", server.Auth)
}
return a.selectedMethod.Start(server)
}
func (a *GenericAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if a.selectedMethod == nil {
return nil, zerrors.ThrowInternal(nil, "SMTP-G99DUr", "no auth method selected")
}
return a.selectedMethod.Next(fromServer, more)
}
@@ -11,17 +11,24 @@ import (
// golang net/smtp SMTP AUTH LOGIN or PLAIN Auth Handler
// Reference: https://gist.github.com/andelf/5118732?permalink_comment_id=4825669#gistcomment-4825669
func PlainOrLoginAuth(username, password, host string) smtp.Auth {
return &plainOrLoginAuth{username: username, password: password, host: host}
func PlainOrLoginAuth(config PlainAuthConfig, host string) smtp.Auth {
return &plainOrLoginAuth{
host: host,
config: config,
}
}
type plainOrLoginAuth struct {
username string
password string
host string
config PlainAuthConfig
authMethod string
}
type PlainAuthConfig struct {
User string
Password string
}
func (a *plainOrLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
if server.Name != a.host {
return "", nil, zerrors.ThrowInternal(nil, "SMTP-RRi75", "wrong host name")
@@ -31,7 +38,7 @@ func (a *plainOrLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error
return a.authMethod, nil, nil
} else {
a.authMethod = "PLAIN"
resp := []byte("\x00" + a.username + "\x00" + a.password)
resp := []byte("\x00" + a.config.User + "\x00" + a.config.Password)
return a.authMethod, resp, nil
}
}
@@ -48,9 +55,9 @@ func (a *plainOrLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
switch {
case bytes.Equal(fromServer, []byte("Username:")):
return []byte(a.username), nil
return []byte(a.config.User), nil
case bytes.Equal(fromServer, []byte("Password:")):
return []byte(a.password), nil
return []byte(a.config.Password), nil
default:
return nil, zerrors.ThrowInternal(nil, "SMTP-HjW21", "unexpected server challenge")
}
@@ -0,0 +1,96 @@
package smtp
import (
"context"
"hash/fnv"
"net/smtp"
lru "github.com/hashicorp/golang-lru/v2"
"golang.org/x/oauth2"
"golang.org/x/oauth2/clientcredentials"
"github.com/zitadel/zitadel/internal/zerrors"
)
var tokenSourceCache *lru.TwoQueueCache[string, oauth2.TokenSource]
func init() {
var err error
tokenSourceCache, err = lru.New2Q[string, oauth2.TokenSource](100)
if err != nil {
panic(err)
}
}
type XOAuth2AuthConfig struct {
User string
TokenEndpoint string
Scopes []string
ClientCredentialsAuth *OAuth2ClientCredentials
}
type OAuth2ClientCredentials struct {
ClientId string
ClientSecret string
}
func (cfg XOAuth2AuthConfig) Hash() string {
hash := fnv.New128()
hash.Write([]byte(cfg.User))
hash.Write([]byte(cfg.TokenEndpoint))
for _, s := range cfg.Scopes {
hash.Write([]byte(s))
}
if cfg.ClientCredentialsAuth != nil {
hash.Write([]byte(cfg.ClientCredentialsAuth.ClientId))
hash.Write([]byte(cfg.ClientCredentialsAuth.ClientSecret))
}
return string(hash.Sum(nil))
}
type xoauth2Auth struct {
host string
config XOAuth2AuthConfig
}
func XOAuth2Auth(config XOAuth2AuthConfig, host string) smtp.Auth {
return &xoauth2Auth{
host: host,
config: config,
}
}
func (a *xoauth2Auth) Start(server *smtp.ServerInfo) (string, []byte, error) {
if server.Name != a.host {
return "", nil, zerrors.ThrowInternal(nil, "SMTP-eRJLyi", "wrong host name")
}
hash := a.config.Hash()
tokenSource, ok := tokenSourceCache.Get(hash)
if !ok {
if a.config.ClientCredentialsAuth != nil {
config := &clientcredentials.Config{
ClientID: a.config.ClientCredentialsAuth.ClientId,
ClientSecret: a.config.ClientCredentialsAuth.ClientSecret,
Scopes: a.config.Scopes,
TokenURL: a.config.TokenEndpoint,
}
tokenSource = config.TokenSource(context.Background())
tokenSourceCache.Add(hash, tokenSource)
}
}
token, err := tokenSource.Token()
if err != nil {
return "", nil, zerrors.ThrowInternal(err, "SMTP-fjHcJu", "Failed to get token to connect with smtp server")
}
resp := []byte("user=" + a.config.User + "\x01" + "auth=Bearer " + token.AccessToken + "\x01\x01")
return "XOAUTH2", resp, nil
}
func (a *xoauth2Auth) Next(_ []byte, more bool) ([]byte, error) {
if !more {
return nil, nil
}
return nil, zerrors.ThrowInternal(nil, "SMTP-Pqsrj9", "unexpected server challenge for XOAUTH2 auth method")
}
+62 -22
View File
@@ -9,6 +9,7 @@ import (
"github.com/zitadel/zitadel/internal/notification/channels/email"
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
"github.com/zitadel/zitadel/internal/notification/channels/webhook"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors"
)
@@ -22,30 +23,11 @@ func (n *NotificationQueries) GetActiveEmailConfig(ctx context.Context) (*email.
ID: config.ID,
Description: config.Description,
}
if config.SMTPConfig != nil {
var password string
if config.SMTPConfig.Password != nil {
password, err = crypto.DecryptString(config.SMTPConfig.Password, n.SMTPPasswordCrypto)
if err != nil {
return nil, err
}
}
return &email.Config{
ProviderConfig: provider,
SMTPConfig: &smtp.Config{
From: config.SMTPConfig.SenderAddress,
FromName: config.SMTPConfig.SenderName,
ReplyToAddress: config.SMTPConfig.ReplyToAddress,
Tls: config.SMTPConfig.TLS,
SMTP: smtp.SMTP{
Host: config.SMTPConfig.Host,
User: config.SMTPConfig.User,
Password: password,
},
},
}, nil
if config.SMTPConfig != nil {
return smtpToEmailConfig(config.SMTPConfig, provider, n.SMTPPasswordCrypto)
}
if config.HTTPConfig != nil {
return &email.Config{
ProviderConfig: provider,
@@ -59,3 +41,61 @@ func (n *NotificationQueries) GetActiveEmailConfig(ctx context.Context) (*email.
}
return nil, zerrors.ThrowNotFound(err, "QUERY-KPQleOckOV", "Errors.SMTPConfig.NotFound")
}
func smtpToEmailConfig(qs *query.SMTP, provider *email.Provider, passCrypto crypto.EncryptionAlgorithm) (*email.Config, error) {
config := &email.Config{
ProviderConfig: provider,
SMTPConfig: &smtp.Config{
From: qs.SenderAddress,
FromName: qs.SenderName,
ReplyToAddress: qs.ReplyToAddress,
Tls: qs.TLS,
SMTP: smtp.SMTP{
Host: qs.Host,
},
},
}
if qs.XOAuth2Auth != nil {
config.SMTPConfig.SMTP.XOAuth2Auth = &smtp.XOAuth2AuthConfig{
User: qs.User,
TokenEndpoint: qs.XOAuth2Auth.TokenEndpoint,
Scopes: qs.XOAuth2Auth.Scopes,
}
if qs.XOAuth2Auth.ClientCredentials != nil {
clientSecret, err := crypto.DecryptString(qs.XOAuth2Auth.ClientCredentials.ClientSecret, passCrypto)
if err != nil {
return nil, err
}
config.SMTPConfig.SMTP.XOAuth2Auth.ClientCredentialsAuth = &smtp.OAuth2ClientCredentials{
ClientId: qs.XOAuth2Auth.ClientCredentials.ClientId,
ClientSecret: clientSecret,
}
}
}
if qs.PlainAuth != nil {
config.SMTPConfig.SMTP.PlainAuth = &smtp.PlainAuthConfig{
User: qs.User,
}
if qs.PlainAuth.Password != nil {
password, err := crypto.DecryptString(qs.PlainAuth.Password, passCrypto)
if err != nil {
return nil, err
}
config.SMTPConfig.SMTP.PlainAuth.Password = password
}
}
// if no auth is configured but there is a user, use plain auth without a password
if qs.User != "" &&
config.SMTPConfig.SMTP.PlainAuth == nil &&
config.SMTPConfig.SMTP.XOAuth2Auth == nil {
config.SMTPConfig.SMTP.PlainAuth = &smtp.PlainAuthConfig{
User: qs.User,
}
}
return config, nil
}
@@ -45,11 +45,13 @@ func TestNotificationQueries_GetActiveEmailConfig(t *testing.T) {
ReplyToAddress: "[email protected]",
Host: "mail.com",
User: "mail-user",
Password: &crypto.CryptoValue{
CryptoType: 0,
Algorithm: cryptAlg,
KeyID: keyId,
Crypted: encryptedPwd,
PlainAuth: &query.PlainAuth{
Password: &crypto.CryptoValue{
CryptoType: 0,
Algorithm: cryptAlg,
KeyID: keyId,
Crypted: encryptedPwd,
},
},
},
},
@@ -60,9 +62,11 @@ func TestNotificationQueries_GetActiveEmailConfig(t *testing.T) {
},
SMTPConfig: &smtp.Config{
SMTP: smtp.SMTP{
Host: "mail.com",
User: "mail-user",
Password: pwd,
Host: "mail.com",
PlainAuth: &smtp.PlainAuthConfig{
User: "mail-user",
Password: pwd,
},
},
Tls: true,
From: "[email protected]",
@@ -92,7 +96,9 @@ func TestNotificationQueries_GetActiveEmailConfig(t *testing.T) {
SMTPConfig: &smtp.Config{
SMTP: smtp.SMTP{
Host: "mail.com",
User: "mail-user",
PlainAuth: &smtp.PlainAuthConfig{
User: "mail-user",
},
},
Tls: true,
From: "[email protected]",
@@ -443,9 +443,11 @@ func newNotificationWorker(t *testing.T, ctrl *gomock.Controller, queries *mock.
},
SMTPConfig: &smtp.Config{
SMTP: smtp.SMTP{
Host: "host",
User: "user",
Password: "password",
Host: "host",
PlainAuth: &smtp.PlainAuthConfig{
User: "user",
Password: "password",
},
},
Tls: true,
From: "from",
@@ -1982,9 +1982,11 @@ func newUserNotifierLegacy(t *testing.T, ctrl *gomock.Controller, queries *mock.
},
SMTPConfig: &smtp.Config{
SMTP: smtp.SMTP{
Host: "host",
User: "user",
Password: "password",
Host: "host",
PlainAuth: &smtp.PlainAuthConfig{
User: "user",
Password: "password",
},
},
Tls: true,
From: "from",
+88 -25
View File
@@ -11,7 +11,7 @@ import (
)
const (
SMTPConfigProjectionTable = "projections.smtp_configs5"
SMTPConfigProjectionTable = "projections.smtp_configs6"
SMTPConfigTable = SMTPConfigProjectionTable + "_" + smtpConfigSMTPTableSuffix
SMTPConfigHTTPTable = SMTPConfigProjectionTable + "_" + smtpConfigHTTPTableSuffix
@@ -25,16 +25,20 @@ const (
SMTPConfigColumnState = "state"
SMTPConfigColumnDescription = "description"
smtpConfigSMTPTableSuffix = "smtp"
SMTPConfigSMTPColumnInstanceID = "instance_id"
SMTPConfigSMTPColumnID = "id"
SMTPConfigSMTPColumnTLS = "tls"
SMTPConfigSMTPColumnSenderAddress = "sender_address"
SMTPConfigSMTPColumnSenderName = "sender_name"
SMTPConfigSMTPColumnReplyToAddress = "reply_to_address"
SMTPConfigSMTPColumnHost = "host"
SMTPConfigSMTPColumnUser = "username"
SMTPConfigSMTPColumnPassword = "password"
smtpConfigSMTPTableSuffix = "smtp"
SMTPConfigSMTPColumnInstanceID = "instance_id"
SMTPConfigSMTPColumnID = "id"
SMTPConfigSMTPColumnTLS = "tls"
SMTPConfigSMTPColumnSenderAddress = "sender_address"
SMTPConfigSMTPColumnSenderName = "sender_name"
SMTPConfigSMTPColumnReplyToAddress = "reply_to_address"
SMTPConfigSMTPColumnHost = "host"
SMTPConfigSMTPColumnUser = "username"
SMTPConfigSMTPColumnPlainAuthPassword = "password"
SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientId = "xoauth2auth_client_credentials_client_id"
SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientSecret = "xoauth2auth_client_credentials_client_secret"
SMTPConfigSMTPColumnXOAuth2AuthTokenEndpoint = "xoauth2auth_token_endpoint"
SMTPConfigSMTPColumnXOAuth2AuthScope = "xoauth2auth_scope"
smtpConfigHTTPTableSuffix = "http"
SMTPConfigHTTPColumnInstanceID = "instance_id"
@@ -77,7 +81,11 @@ func (*smtpConfigProjection) Init() *old_handler.Check {
handler.NewColumn(SMTPConfigSMTPColumnReplyToAddress, handler.ColumnTypeText),
handler.NewColumn(SMTPConfigSMTPColumnHost, handler.ColumnTypeText),
handler.NewColumn(SMTPConfigSMTPColumnUser, handler.ColumnTypeText),
handler.NewColumn(SMTPConfigSMTPColumnPassword, handler.ColumnTypeJSONB, handler.Nullable()),
handler.NewColumn(SMTPConfigSMTPColumnPlainAuthPassword, handler.ColumnTypeJSONB, handler.Nullable()),
handler.NewColumn(SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientId, handler.ColumnTypeText, handler.Nullable()),
handler.NewColumn(SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientSecret, handler.ColumnTypeJSONB, handler.Nullable()),
handler.NewColumn(SMTPConfigSMTPColumnXOAuth2AuthTokenEndpoint, handler.ColumnTypeText, handler.Nullable()),
handler.NewColumn(SMTPConfigSMTPColumnXOAuth2AuthScope, handler.ColumnTypeTextArray, handler.Nullable()),
},
handler.NewPrimaryKey(SMTPConfigSMTPColumnInstanceID, SMTPConfigSMTPColumnID),
smtpConfigSMTPTableSuffix,
@@ -155,6 +163,39 @@ func (p *smtpConfigProjection) reduceSMTPConfigAdded(event eventstore.Event) (*h
state = domain.SMTPConfigStateActive
}
columns := []handler.Column{
handler.NewCol(SMTPConfigSMTPColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(SMTPConfigSMTPColumnID, getSMTPConfigID(e.ID, e.Aggregate())),
handler.NewCol(SMTPConfigSMTPColumnTLS, e.TLS),
handler.NewCol(SMTPConfigSMTPColumnSenderAddress, e.SenderAddress),
handler.NewCol(SMTPConfigSMTPColumnSenderName, e.SenderName),
handler.NewCol(SMTPConfigSMTPColumnReplyToAddress, e.ReplyToAddress),
handler.NewCol(SMTPConfigSMTPColumnHost, e.Host),
handler.NewCol(SMTPConfigSMTPColumnUser, e.User),
}
if e.PlainAuth != nil {
columns = append(columns, removeXoauth()...)
columns = append(columns, handler.NewCol(SMTPConfigSMTPColumnPlainAuthPassword, e.PlainAuth.Password))
} else if e.Password != nil {
columns = append(columns, removeXoauth()...)
columns = append(columns, handler.NewCol(SMTPConfigSMTPColumnPlainAuthPassword, e.Password))
}
if e.XOAuth2Auth != nil {
columns = append(columns, removePlainAuth()...)
columns = append(columns,
handler.NewCol(SMTPConfigSMTPColumnXOAuth2AuthTokenEndpoint, e.XOAuth2Auth.TokenEndpoint),
handler.NewCol(SMTPConfigSMTPColumnXOAuth2AuthScope, e.XOAuth2Auth.Scopes),
)
if e.XOAuth2Auth.ClientCredentials != nil {
columns = append(columns,
handler.NewCol(SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientId, e.XOAuth2Auth.ClientCredentials.ClientId),
handler.NewCol(SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientSecret, e.XOAuth2Auth.ClientCredentials.ClientSecret),
)
}
}
return handler.NewMultiStatement(
e,
handler.AddCreateStatement(
@@ -171,22 +212,24 @@ func (p *smtpConfigProjection) reduceSMTPConfigAdded(event eventstore.Event) (*h
},
),
handler.AddCreateStatement(
[]handler.Column{
handler.NewCol(SMTPConfigSMTPColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(SMTPConfigSMTPColumnID, getSMTPConfigID(e.ID, e.Aggregate())),
handler.NewCol(SMTPConfigSMTPColumnTLS, e.TLS),
handler.NewCol(SMTPConfigSMTPColumnSenderAddress, e.SenderAddress),
handler.NewCol(SMTPConfigSMTPColumnSenderName, e.SenderName),
handler.NewCol(SMTPConfigSMTPColumnReplyToAddress, e.ReplyToAddress),
handler.NewCol(SMTPConfigSMTPColumnHost, e.Host),
handler.NewCol(SMTPConfigSMTPColumnUser, e.User),
handler.NewCol(SMTPConfigSMTPColumnPassword, e.Password),
},
columns,
handler.WithTableSuffix(smtpConfigSMTPTableSuffix),
),
), nil
}
func removeXoauth() []handler.Column {
return []handler.Column{
handler.NewCol(SMTPConfigSMTPColumnXOAuth2AuthTokenEndpoint, nil),
handler.NewCol(SMTPConfigSMTPColumnXOAuth2AuthScope, nil),
handler.NewCol(SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientId, nil),
handler.NewCol(SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientSecret, nil),
}
}
func removePlainAuth() []handler.Column {
return []handler.Column{handler.NewCol(SMTPConfigSMTPColumnPlainAuthPassword, nil)}
}
func (p *smtpConfigProjection) reduceSMTPConfigHTTPAdded(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*instance.SMTPConfigHTTPAddedEvent](event)
if err != nil {
@@ -307,7 +350,27 @@ func (p *smtpConfigProjection) reduceSMTPConfigChanged(event eventstore.Event) (
smtpColumns = append(smtpColumns, handler.NewCol(SMTPConfigSMTPColumnUser, *e.User))
}
if e.Password != nil {
smtpColumns = append(smtpColumns, handler.NewCol(SMTPConfigSMTPColumnPassword, *e.Password))
smtpColumns = append(smtpColumns, handler.NewCol(SMTPConfigSMTPColumnPlainAuthPassword, *e.Password))
}
if !e.PlainAuth.IsEmpty() {
smtpColumns = append(smtpColumns,
handler.NewCol(SMTPConfigSMTPColumnPlainAuthPassword, e.PlainAuth.Password),
)
}
if !e.XOAuth2Auth.IsEmpty() {
smtpColumns = append(smtpColumns,
handler.NewCol(SMTPConfigSMTPColumnXOAuth2AuthTokenEndpoint, e.XOAuth2Auth.TokenEndpoint),
handler.NewCol(SMTPConfigSMTPColumnXOAuth2AuthScope, e.XOAuth2Auth.Scopes),
)
if !e.XOAuth2Auth.ClientCredentials.IsEmpty() {
smtpColumns = append(smtpColumns,
handler.NewCol(SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientId, e.XOAuth2Auth.ClientCredentials.ClientId),
handler.NewCol(SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientSecret, e.XOAuth2Auth.ClientCredentials.ClientSecret),
)
}
}
if len(smtpColumns) > 0 {
stmts = append(stmts, handler.AddUpdateStatement(
@@ -333,7 +396,7 @@ func (p *smtpConfigProjection) reduceSMTPConfigPasswordChanged(event eventstore.
e,
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(SMTPConfigSMTPColumnPassword, e.Password),
handler.NewCol(SMTPConfigSMTPColumnPlainAuthPassword, e.Password),
},
[]handler.Condition{
handler.NewCond(SMTPConfigSMTPColumnID, getSMTPConfigID(e.ID, e.Aggregate())),
+35 -32
View File
@@ -49,7 +49,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -59,7 +59,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.smtp_configs5_smtp SET (tls, sender_address, sender_name, reply_to_address, host, username) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)",
expectedStmt: "UPDATE projections.smtp_configs6_smtp SET (tls, sender_address, sender_name, reply_to_address, host, username) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)",
expectedArgs: []interface{}{
true,
"sender",
@@ -105,7 +105,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -115,7 +115,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.smtp_configs5_smtp SET (tls, sender_address, sender_name, reply_to_address, host, username) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)",
expectedStmt: "UPDATE projections.smtp_configs6_smtp SET (tls, sender_address, sender_name, reply_to_address, host, username) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)",
expectedArgs: []interface{}{
true,
"sender",
@@ -155,7 +155,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -192,7 +192,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -201,7 +201,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.smtp_configs5_smtp SET sender_address = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedStmt: "UPDATE projections.smtp_configs6_smtp SET sender_address = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
"sender",
"config-id",
@@ -238,7 +238,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -248,7 +248,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.smtp_configs5_http SET (endpoint, signing_key) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedStmt: "UPDATE projections.smtp_configs6_http SET (endpoint, signing_key) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
"endpoint",
anyArg{},
@@ -284,7 +284,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -321,7 +321,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -330,7 +330,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.smtp_configs5_http SET endpoint = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedStmt: "UPDATE projections.smtp_configs6_http SET endpoint = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
"endpoint",
"config-id",
@@ -340,7 +340,8 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
},
},
},
}, {
},
{
name: "reduceSMTPConfigHTTPChanged, signing key",
args: args{
event: getEvent(
@@ -364,7 +365,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -373,7 +374,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.smtp_configs5_http SET signing_key = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedStmt: "UPDATE projections.smtp_configs6_http SET signing_key = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
anyArg{},
"config-id",
@@ -416,7 +417,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.smtp_configs5 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedStmt: "INSERT INTO projections.smtp_configs6 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{
anyArg{},
anyArg{},
@@ -430,7 +431,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "INSERT INTO projections.smtp_configs5_smtp (instance_id, id, tls, sender_address, sender_name, reply_to_address, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedStmt: "INSERT INTO projections.smtp_configs6_smtp (instance_id, id, tls, sender_address, sender_name, reply_to_address, host, username, xoauth2auth_token_endpoint, xoauth2auth_scope, xoauth2auth_client_credentials_client_id, xoauth2auth_client_credentials_client_secret, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)",
expectedArgs: []interface{}{
"instance-id",
"ro-id",
@@ -440,6 +441,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
"reply-to",
"host",
"user",
nil, nil, nil, nil,
anyArg{},
},
},
@@ -481,7 +483,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.smtp_configs5 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedStmt: "INSERT INTO projections.smtp_configs6 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{
anyArg{},
anyArg{},
@@ -495,7 +497,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "INSERT INTO projections.smtp_configs5_smtp (instance_id, id, tls, sender_address, sender_name, reply_to_address, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedStmt: "INSERT INTO projections.smtp_configs6_smtp (instance_id, id, tls, sender_address, sender_name, reply_to_address, host, username, xoauth2auth_token_endpoint, xoauth2auth_scope, xoauth2auth_client_credentials_client_id, xoauth2auth_client_credentials_client_secret, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)",
expectedArgs: []interface{}{
"instance-id",
"config-id",
@@ -505,6 +507,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
"reply-to",
"host",
"user",
nil, nil, nil, nil,
anyArg{},
},
},
@@ -538,7 +541,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.smtp_configs5 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedStmt: "INSERT INTO projections.smtp_configs6 (creation_date, change_date, instance_id, resource_owner, aggregate_id, id, sequence, state, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{
anyArg{},
anyArg{},
@@ -552,7 +555,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "INSERT INTO projections.smtp_configs5_http (instance_id, id, endpoint, signing_key) VALUES ($1, $2, $3, $4)",
expectedStmt: "INSERT INTO projections.smtp_configs6_http (instance_id, id, endpoint, signing_key) VALUES ($1, $2, $3, $4)",
expectedArgs: []interface{}{
"instance-id",
"config-id",
@@ -585,7 +588,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (NOT (id = $4)) AND (state = $5) AND (instance_id = $6)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (NOT (id = $4)) AND (state = $5) AND (instance_id = $6)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -596,7 +599,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -629,7 +632,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (NOT (id = $4)) AND (state = $5) AND (instance_id = $6)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (NOT (id = $4)) AND (state = $5) AND (instance_id = $6)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -640,7 +643,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -674,7 +677,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -707,7 +710,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence, state) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -747,7 +750,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.smtp_configs5_smtp SET password = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedStmt: "UPDATE projections.smtp_configs6_smtp SET password = $1 WHERE (id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
anyArg{},
"config-id",
@@ -755,7 +758,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
},
},
{
expectedStmt: "UPDATE projections.smtp_configs5 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedStmt: "UPDATE projections.smtp_configs6 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
@@ -788,7 +791,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.smtp_configs5 WHERE (id = $1) AND (instance_id = $2)",
expectedStmt: "DELETE FROM projections.smtp_configs6 WHERE (id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{
"config-id",
"instance-id",
@@ -818,7 +821,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.smtp_configs5 WHERE (id = $1) AND (instance_id = $2)",
expectedStmt: "DELETE FROM projections.smtp_configs6 WHERE (id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{
"ro-id",
"instance-id",
@@ -845,7 +848,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.smtp_configs5 WHERE (instance_id = $1)",
expectedStmt: "DELETE FROM projections.smtp_configs6 WHERE (instance_id = $1)",
expectedArgs: []interface{}{
"agg-id",
},
+109 -16
View File
@@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"errors"
"strings"
"time"
sq "github.com/Masterminds/squirrel"
@@ -100,8 +101,24 @@ var (
name: projection.SMTPConfigSMTPColumnUser,
table: smtpConfigsSMTPTable,
}
SMTPConfigSMTPColumnPassword = Column{
name: projection.SMTPConfigSMTPColumnPassword,
SMTPConfigSMTPColumnPlainAuthPassword = Column{
name: projection.SMTPConfigSMTPColumnPlainAuthPassword,
table: smtpConfigsSMTPTable,
}
SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientId = Column{
name: projection.SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientId,
table: smtpConfigsSMTPTable,
}
SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientSecret = Column{
name: projection.SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientSecret,
table: smtpConfigsSMTPTable,
}
SMTPConfigSMTPColumnXOAuth2AuthTokenEndpoint = Column{
name: projection.SMTPConfigSMTPColumnXOAuth2AuthTokenEndpoint,
table: smtpConfigsSMTPTable,
}
SMTPConfigSMTPColumnXOAuth2AuthScope = Column{
name: projection.SMTPConfigSMTPColumnXOAuth2AuthScope,
table: smtpConfigsSMTPTable,
}
@@ -156,7 +173,35 @@ type SMTP struct {
ReplyToAddress string
Host string
User string
Password *crypto.CryptoValue
PlainAuth *PlainAuth
XOAuth2Auth *XOAuth2Auth
}
type PlainAuth struct {
Password *crypto.CryptoValue
}
func (a PlainAuth) isEmpty() bool {
return a.Password == nil
}
type XOAuth2Auth struct {
TokenEndpoint string
Scopes []string
ClientCredentials *XOAuthClientCredentials
}
func (a XOAuth2Auth) isEmpty() bool {
return a.ClientCredentials == nil && a.TokenEndpoint == "" && len(a.Scopes) == 0
}
type XOAuthClientCredentials struct {
ClientId string
ClientSecret *crypto.CryptoValue
}
func (a XOAuthClientCredentials) isEmpty() bool {
return a.ClientId == "" && a.ClientSecret == nil
}
func (q *Queries) SMTPConfigActive(ctx context.Context, resourceOwner string) (config *SMTPConfig, err error) {
@@ -229,7 +274,11 @@ func prepareSMTPConfigQuery() (sq.SelectBuilder, func(*sql.Row) (*SMTPConfig, er
SMTPConfigSMTPColumnReplyToAddress.identifier(),
SMTPConfigSMTPColumnHost.identifier(),
SMTPConfigSMTPColumnUser.identifier(),
SMTPConfigSMTPColumnPassword.identifier(),
SMTPConfigSMTPColumnPlainAuthPassword.identifier(),
SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientId.identifier(),
SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientSecret.identifier(),
SMTPConfigSMTPColumnXOAuth2AuthTokenEndpoint.identifier(),
SMTPConfigSMTPColumnXOAuth2AuthScope.identifier(),
SMTPConfigHTTPColumnID.identifier(),
SMTPConfigHTTPColumnEndpoint.identifier(),
@@ -260,7 +309,11 @@ func prepareSMTPConfigQuery() (sq.SelectBuilder, func(*sql.Row) (*SMTPConfig, er
&smtpConfig.replyToAddress,
&smtpConfig.host,
&smtpConfig.user,
&smtpConfig.password,
&smtpConfig.plainAuthPassword,
&smtpConfig.xoauth2AuthClientCredentialsClientId,
&smtpConfig.xoauth2AuthClientClientCredentialsSecret,
&smtpConfig.xoauth2AuthTokenEndpoint,
&smtpConfig.xoauth2AuthScope,
&httpConfig.id,
&httpConfig.endpoint,
&httpConfig.signingKey,
@@ -296,7 +349,11 @@ func prepareSMTPConfigsQuery() (sq.SelectBuilder, func(*sql.Rows) (*SMTPConfigs,
SMTPConfigSMTPColumnReplyToAddress.identifier(),
SMTPConfigSMTPColumnHost.identifier(),
SMTPConfigSMTPColumnUser.identifier(),
SMTPConfigSMTPColumnPassword.identifier(),
SMTPConfigSMTPColumnPlainAuthPassword.identifier(),
SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientId.identifier(),
SMTPConfigSMTPColumnXOAuth2AuthClientCredentialsClientSecret.identifier(),
SMTPConfigSMTPColumnXOAuth2AuthTokenEndpoint.identifier(),
SMTPConfigSMTPColumnXOAuth2AuthScope.identifier(),
SMTPConfigHTTPColumnID.identifier(),
SMTPConfigHTTPColumnEndpoint.identifier(),
@@ -330,7 +387,11 @@ func prepareSMTPConfigsQuery() (sq.SelectBuilder, func(*sql.Rows) (*SMTPConfigs,
&smtpConfig.replyToAddress,
&smtpConfig.host,
&smtpConfig.user,
&smtpConfig.password,
&smtpConfig.plainAuthPassword,
&smtpConfig.xoauth2AuthClientCredentialsClientId,
&smtpConfig.xoauth2AuthClientClientCredentialsSecret,
&smtpConfig.xoauth2AuthTokenEndpoint,
&smtpConfig.xoauth2AuthScope,
&httpConfig.id,
&httpConfig.endpoint,
&httpConfig.signingKey,
@@ -379,20 +440,51 @@ func (q *Queries) SearchSMTPConfigs(ctx context.Context, queries *SMTPConfigsSea
}
type sqlSmtpConfig struct {
id sql.NullString
tls sql.NullBool
senderAddress sql.NullString
senderName sql.NullString
replyToAddress sql.NullString
host sql.NullString
user sql.NullString
password *crypto.CryptoValue
id sql.NullString
tls sql.NullBool
senderAddress sql.NullString
senderName sql.NullString
replyToAddress sql.NullString
host sql.NullString
user sql.NullString
plainAuthPassword *crypto.CryptoValue
xoauth2AuthClientCredentialsClientId sql.NullString
xoauth2AuthClientClientCredentialsSecret *crypto.CryptoValue
xoauth2AuthTokenEndpoint sql.NullString
xoauth2AuthScope sql.NullString
}
func (c sqlSmtpConfig) set(smtpConfig *SMTPConfig) {
if !c.id.Valid {
return
}
plainAuth := &PlainAuth{
Password: c.plainAuthPassword,
}
if plainAuth.isEmpty() {
plainAuth = nil
}
scopes := strings.Split(c.xoauth2AuthScope.String, " ")
if len(scopes) == 1 && scopes[0] == "" {
scopes = nil
}
xoauth2Auth := &XOAuth2Auth{
TokenEndpoint: c.xoauth2AuthTokenEndpoint.String,
Scopes: scopes,
ClientCredentials: &XOAuthClientCredentials{
ClientId: c.xoauth2AuthClientCredentialsClientId.String,
ClientSecret: c.xoauth2AuthClientClientCredentialsSecret,
},
}
if xoauth2Auth.ClientCredentials.isEmpty() {
xoauth2Auth.ClientCredentials = nil
}
if xoauth2Auth.isEmpty() {
xoauth2Auth = nil
}
smtpConfig.SMTPConfig = &SMTP{
TLS: c.tls.Bool,
SenderAddress: c.senderAddress.String,
@@ -400,7 +492,8 @@ func (c sqlSmtpConfig) set(smtpConfig *SMTPConfig) {
ReplyToAddress: c.replyToAddress.String,
Host: c.host.String,
User: c.user.String,
Password: c.password,
PlainAuth: plainAuth,
XOAuth2Auth: xoauth2Auth,
}
}
+104 -48
View File
@@ -14,49 +14,57 @@ import (
)
var (
prepareSMTPConfigStmt = `SELECT projections.smtp_configs5.creation_date,` +
` projections.smtp_configs5.change_date,` +
` projections.smtp_configs5.resource_owner,` +
` projections.smtp_configs5.sequence,` +
` projections.smtp_configs5.id,` +
` projections.smtp_configs5.state,` +
` projections.smtp_configs5.description,` +
` projections.smtp_configs5_smtp.id,` +
` projections.smtp_configs5_smtp.tls,` +
` projections.smtp_configs5_smtp.sender_address,` +
` projections.smtp_configs5_smtp.sender_name,` +
` projections.smtp_configs5_smtp.reply_to_address,` +
` projections.smtp_configs5_smtp.host,` +
` projections.smtp_configs5_smtp.username,` +
` projections.smtp_configs5_smtp.password,` +
` projections.smtp_configs5_http.id,` +
` projections.smtp_configs5_http.endpoint,` +
` projections.smtp_configs5_http.signing_key` +
` FROM projections.smtp_configs5` +
` LEFT JOIN projections.smtp_configs5_smtp ON projections.smtp_configs5.id = projections.smtp_configs5_smtp.id AND projections.smtp_configs5.instance_id = projections.smtp_configs5_smtp.instance_id` +
` LEFT JOIN projections.smtp_configs5_http ON projections.smtp_configs5.id = projections.smtp_configs5_http.id AND projections.smtp_configs5.instance_id = projections.smtp_configs5_http.instance_id`
prepareSMTPConfigsStmt = `SELECT projections.smtp_configs5.creation_date,` +
` projections.smtp_configs5.change_date,` +
` projections.smtp_configs5.resource_owner,` +
` projections.smtp_configs5.sequence,` +
` projections.smtp_configs5.id,` +
` projections.smtp_configs5.state,` +
` projections.smtp_configs5.description,` +
` projections.smtp_configs5_smtp.id,` +
` projections.smtp_configs5_smtp.tls,` +
` projections.smtp_configs5_smtp.sender_address,` +
` projections.smtp_configs5_smtp.sender_name,` +
` projections.smtp_configs5_smtp.reply_to_address,` +
` projections.smtp_configs5_smtp.host,` +
` projections.smtp_configs5_smtp.username,` +
` projections.smtp_configs5_smtp.password,` +
` projections.smtp_configs5_http.id,` +
` projections.smtp_configs5_http.endpoint,` +
` projections.smtp_configs5_http.signing_key,` +
prepareSMTPConfigStmt = `SELECT projections.smtp_configs6.creation_date,` +
` projections.smtp_configs6.change_date,` +
` projections.smtp_configs6.resource_owner,` +
` projections.smtp_configs6.sequence,` +
` projections.smtp_configs6.id,` +
` projections.smtp_configs6.state,` +
` projections.smtp_configs6.description,` +
` projections.smtp_configs6_smtp.id,` +
` projections.smtp_configs6_smtp.tls,` +
` projections.smtp_configs6_smtp.sender_address,` +
` projections.smtp_configs6_smtp.sender_name,` +
` projections.smtp_configs6_smtp.reply_to_address,` +
` projections.smtp_configs6_smtp.host,` +
` projections.smtp_configs6_smtp.username,` +
` projections.smtp_configs6_smtp.password,` +
` projections.smtp_configs6_smtp.xoauth2auth_client_credentials_client_id,` +
` projections.smtp_configs6_smtp.xoauth2auth_client_credentials_client_secret,` +
` projections.smtp_configs6_smtp.xoauth2auth_token_endpoint,` +
` projections.smtp_configs6_smtp.xoauth2auth_scope,` +
` projections.smtp_configs6_http.id,` +
` projections.smtp_configs6_http.endpoint,` +
` projections.smtp_configs6_http.signing_key` +
` FROM projections.smtp_configs6` +
` LEFT JOIN projections.smtp_configs6_smtp ON projections.smtp_configs6.id = projections.smtp_configs6_smtp.id AND projections.smtp_configs6.instance_id = projections.smtp_configs6_smtp.instance_id` +
` LEFT JOIN projections.smtp_configs6_http ON projections.smtp_configs6.id = projections.smtp_configs6_http.id AND projections.smtp_configs6.instance_id = projections.smtp_configs6_http.instance_id`
prepareSMTPConfigsStmt = `SELECT projections.smtp_configs6.creation_date,` +
` projections.smtp_configs6.change_date,` +
` projections.smtp_configs6.resource_owner,` +
` projections.smtp_configs6.sequence,` +
` projections.smtp_configs6.id,` +
` projections.smtp_configs6.state,` +
` projections.smtp_configs6.description,` +
` projections.smtp_configs6_smtp.id,` +
` projections.smtp_configs6_smtp.tls,` +
` projections.smtp_configs6_smtp.sender_address,` +
` projections.smtp_configs6_smtp.sender_name,` +
` projections.smtp_configs6_smtp.reply_to_address,` +
` projections.smtp_configs6_smtp.host,` +
` projections.smtp_configs6_smtp.username,` +
` projections.smtp_configs6_smtp.password,` +
` projections.smtp_configs6_smtp.xoauth2auth_client_credentials_client_id,` +
` projections.smtp_configs6_smtp.xoauth2auth_client_credentials_client_secret,` +
` projections.smtp_configs6_smtp.xoauth2auth_token_endpoint,` +
` projections.smtp_configs6_smtp.xoauth2auth_scope,` +
` projections.smtp_configs6_http.id,` +
` projections.smtp_configs6_http.endpoint,` +
` projections.smtp_configs6_http.signing_key,` +
` COUNT(*) OVER ()` +
` FROM projections.smtp_configs5` +
` LEFT JOIN projections.smtp_configs5_smtp ON projections.smtp_configs5.id = projections.smtp_configs5_smtp.id AND projections.smtp_configs5.instance_id = projections.smtp_configs5_smtp.instance_id` +
` LEFT JOIN projections.smtp_configs5_http ON projections.smtp_configs5.id = projections.smtp_configs5_http.id AND projections.smtp_configs5.instance_id = projections.smtp_configs5_http.instance_id`
` FROM projections.smtp_configs6` +
` LEFT JOIN projections.smtp_configs6_smtp ON projections.smtp_configs6.id = projections.smtp_configs6_smtp.id AND projections.smtp_configs6.instance_id = projections.smtp_configs6_smtp.instance_id` +
` LEFT JOIN projections.smtp_configs6_http ON projections.smtp_configs6.id = projections.smtp_configs6_http.id AND projections.smtp_configs6.instance_id = projections.smtp_configs6_http.instance_id`
prepareSMTPConfigCols = []string{
"creation_date",
@@ -74,6 +82,10 @@ var (
"smtp_host",
"smtp_user",
"smtp_password",
"xoauth2auth_client_credentials_client_id",
"xoauth2auth_client_credentials_client_secret",
"xoauth2auth_token_endpoint",
"xoauth2auth_scope",
"id",
"endpoint",
"signing_key",
@@ -136,6 +148,10 @@ func Test_SMTPConfigPrepares(t *testing.T) {
nil,
nil,
nil,
nil,
nil,
nil,
nil,
},
),
},
@@ -151,7 +167,9 @@ func Test_SMTPConfigPrepares(t *testing.T) {
ReplyToAddress: "reply-to",
Host: "host",
User: "user",
Password: &crypto.CryptoValue{},
PlainAuth: &PlainAuth{
Password: &crypto.CryptoValue{},
},
},
ID: "2232323",
State: domain.SMTPConfigStateActive,
@@ -181,6 +199,10 @@ func Test_SMTPConfigPrepares(t *testing.T) {
nil,
nil,
nil,
nil,
nil,
nil,
nil,
"2232323",
"endpoint",
&crypto.CryptoValue{
@@ -237,6 +259,10 @@ func Test_SMTPConfigPrepares(t *testing.T) {
nil,
nil,
nil,
nil,
nil,
nil,
nil,
},
),
},
@@ -252,7 +278,9 @@ func Test_SMTPConfigPrepares(t *testing.T) {
ReplyToAddress: "reply-to2",
Host: "host2",
User: "user2",
Password: &crypto.CryptoValue{},
PlainAuth: &PlainAuth{
Password: &crypto.CryptoValue{},
},
},
ID: "44442323",
State: domain.SMTPConfigStateInactive,
@@ -285,6 +313,10 @@ func Test_SMTPConfigPrepares(t *testing.T) {
nil,
nil,
nil,
nil,
nil,
nil,
nil,
},
),
},
@@ -300,7 +332,9 @@ func Test_SMTPConfigPrepares(t *testing.T) {
ReplyToAddress: "reply-to3",
Host: "host3",
User: "user3",
Password: &crypto.CryptoValue{},
PlainAuth: &PlainAuth{
Password: &crypto.CryptoValue{},
},
},
ID: "23234444",
State: domain.SMTPConfigStateInactive,
@@ -383,6 +417,10 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
nil,
nil,
nil,
nil,
nil,
nil,
nil,
},
},
),
@@ -404,7 +442,9 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
ReplyToAddress: "reply-to",
Host: "host",
User: "user",
Password: &crypto.CryptoValue{},
PlainAuth: &PlainAuth{
Password: &crypto.CryptoValue{},
},
},
ID: "2232323",
State: domain.SMTPConfigStateActive,
@@ -437,6 +477,10 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
nil,
nil,
nil,
nil,
nil,
nil,
nil,
"2232323",
"endpoint",
&crypto.CryptoValue{
@@ -465,6 +509,10 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
nil,
nil,
nil,
nil,
nil,
nil,
nil,
},
{
testNow,
@@ -485,6 +533,10 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
nil,
nil,
nil,
nil,
nil,
nil,
nil,
},
},
),
@@ -524,7 +576,9 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
ReplyToAddress: "reply-to2",
Host: "host2",
User: "user2",
Password: &crypto.CryptoValue{},
PlainAuth: &PlainAuth{
Password: &crypto.CryptoValue{},
},
},
ID: "44442323",
State: domain.SMTPConfigStateInactive,
@@ -542,7 +596,9 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
ReplyToAddress: "reply-to3",
Host: "host3",
User: "user3",
Password: &crypto.CryptoValue{},
PlainAuth: &PlainAuth{
Password: &crypto.CryptoValue{},
},
},
ID: "23234444",
State: domain.SMTPConfigStateInactive,
+77 -3
View File
@@ -33,6 +33,23 @@ type SMTPConfigAddedEvent struct {
Host string `json:"host,omitempty"`
User string `json:"user,omitempty"`
Password *crypto.CryptoValue `json:"password,omitempty"`
PlainAuth *PlainAuth `json:"plainAuth,omitempty"`
XOAuth2Auth *XOAuth2Auth `json:"xoauth2Auth,omitempty"`
}
type XOAuth2Auth struct {
TokenEndpoint string `json:"tokenEndpoint,omitempty"`
Scopes []string `json:"scopes,omitempty"`
ClientCredentials *XOAuth2ClientCredentials `json:"clientCredentials,omitempty"`
}
type XOAuth2ClientCredentials struct {
ClientId string `json:"clientId,omitempty"`
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
}
type PlainAuth struct {
Password *crypto.CryptoValue `json:"password,omitempty"`
}
func NewSMTPConfigAddedEvent(
@@ -43,9 +60,10 @@ func NewSMTPConfigAddedEvent(
senderAddress,
senderName,
replyToAddress,
host,
host string,
user string,
password *crypto.CryptoValue,
plainAuth *PlainAuth,
xoauth2Auth *XOAuth2Auth,
) *SMTPConfigAddedEvent {
return &SMTPConfigAddedEvent{
BaseEvent: eventstore.NewBaseEventForPush(
@@ -61,9 +79,11 @@ func NewSMTPConfigAddedEvent(
ReplyToAddress: replyToAddress,
Host: host,
User: user,
Password: password,
PlainAuth: plainAuth,
XOAuth2Auth: xoauth2Auth,
}
}
func (e *SMTPConfigAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
@@ -87,6 +107,35 @@ type SMTPConfigChangedEvent struct {
Host *string `json:"host,omitempty"`
User *string `json:"user,omitempty"`
Password *crypto.CryptoValue `json:"password,omitempty"`
PlainAuth PlainAuthChanged `json:"plainAuth,omitempty"`
XOAuth2Auth XOAuth2AuthChanged `json:"xoauth2Auth,omitempty"`
}
type XOAuth2AuthChanged struct {
TokenEndpoint *string `json:"tokenEndpoint,omitempty"`
Scopes []string `json:"scopes,omitempty"`
ClientCredentials XOAuth2ClientCredentialsChanged `json:"clientCredentials,omitempty"`
}
func (c XOAuth2AuthChanged) IsEmpty() bool {
return c.ClientCredentials.IsEmpty() && c.TokenEndpoint == nil && len(c.Scopes) == 0
}
type XOAuth2ClientCredentialsChanged struct {
ClientId *string `json:"clientId,omitempty"`
ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
}
func (c XOAuth2ClientCredentialsChanged) IsEmpty() bool {
return c.ClientId == nil && c.ClientSecret == nil
}
type PlainAuthChanged struct {
Password *crypto.CryptoValue `json:"password,omitempty"`
}
func (c PlainAuthChanged) IsEmpty() bool {
return c.Password == nil
}
func (e *SMTPConfigChangedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
@@ -177,6 +226,31 @@ func ChangeSMTPConfigSMTPUser(smtpUser string) func(event *SMTPConfigChangedEven
func ChangeSMTPConfigSMTPPassword(password *crypto.CryptoValue) func(event *SMTPConfigChangedEvent) {
return func(e *SMTPConfigChangedEvent) {
e.Password = password
e.PlainAuth.Password = password
}
}
func ChangeSMTPConfigXOAuth2ClientCredentialsClientId(clientId string) func(event *SMTPConfigChangedEvent) {
return func(e *SMTPConfigChangedEvent) {
e.XOAuth2Auth.ClientCredentials.ClientId = &clientId
}
}
func ChangeSMTPConfigXOAuth2ClientCredentialsClientSecret(clientSecret *crypto.CryptoValue) func(event *SMTPConfigChangedEvent) {
return func(e *SMTPConfigChangedEvent) {
e.XOAuth2Auth.ClientCredentials.ClientSecret = clientSecret
}
}
func ChangeSMTPConfigXOAuth2TokenEndpoint(tokenEndpoint string) func(event *SMTPConfigChangedEvent) {
return func(e *SMTPConfigChangedEvent) {
e.XOAuth2Auth.TokenEndpoint = &tokenEndpoint
}
}
func ChangeSMTPConfigXOAuth2Scopes(scopes []string) func(event *SMTPConfigChangedEvent) {
return func(e *SMTPConfigChangedEvent) {
e.XOAuth2Auth.Scopes = scopes
}
}
+84
View File
@@ -4944,6 +4944,7 @@ message AddEmailProviderSMTPRequest {
example: "\"197f0117-529e-443d-bf6c-0292dd9a02b7\"";
}
];
// Deprecated: use [Auth] oneof instead.
string password = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"this-is-my-password\"";
@@ -4965,6 +4966,78 @@ message AddEmailProviderSMTPRequest {
max_length: 200;
}
];
oneof Auth {
SMTPNoAuth none = 9;
SMTPPlainAuth plain = 10;
SMTPXOAuth2Auth xoauth2 = 11;
}
}
// SMTPNoAuth can be used when no authentication is required for the SMTP connection.
message SMTPNoAuth {}
// SMTPPlainAuth adds plain authentication to the smtp connection. The username of the parent configuration is used in
// combination with the password in this message.
message SMTPPlainAuth {
string password = 1 [
(validate.rules).string = {min_len: 0, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"this-is-my-client-secret\"";
}
];
}
// SMTPXOAuth2Auth adds XOAut2 authentication to the smtp connection. The username of the parent configuration is used
// in combination with the other parameters of this message.
message SMTPXOAuth2Auth {
// token_endpoint is the endpoint from which a token will be requested according to the oauth2 specification.
string token_endpoint = 1 [
(validate.rules).string = {min_len: 0, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"http://auth.example.com/token\"";
min_length: 0;
max_length: 200;
}
];
// scopes is a list of scopes which should be requested from the authorization service. To authorize a token to
// use the smtp functionality, the server requires a specific scope. These scopes can be found in the documentation
// of the provider.
repeated string scopes = 2 [
(google.api.field_behavior) = REQUIRED,
(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"https://mail.example.com\"]";
}
];
// OAuth2Type is a oneof used to switch between the different types of oauth2 authorization flows.
oneof OAuth2Type {
ClientCredentials client_credentials = 3;
}
// ClientCredentials authenticates the SMTP client using Oauth 2.0 Client Credentials grant.
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
message ClientCredentials {
string client_id = 1 [
(validate.rules).string = {min_len: 0, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"197f0117-529e-443d-bf6c-0292dd9a02b7\"";
min_length: 0;
max_length: 200;
}
];
string client_secret = 2 [
(validate.rules).string = {min_len: 0, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"this-is-my-client-secret\"";
min_length: 0;
max_length: 200;
}
];
}
}
message AddEmailProviderSMTPResponse {
@@ -5029,6 +5102,11 @@ message UpdateEmailProviderSMTPRequest {
}
];
string id = 9 [(validate.rules).string = {min_len: 1, max_len: 100}];
oneof Auth {
SMTPNoAuth none = 10;
SMTPPlainAuth plain = 11;
SMTPXOAuth2Auth xoauth2 = 12;
}
}
message UpdateEmailProviderSMTPResponse {
@@ -5221,6 +5299,12 @@ message TestEmailProviderSMTPRequest {
example: "\"267191369515139464\"";
}
];
oneof Auth {
option (validate.required) = true;
SMTPNoAuth none = 9;
SMTPPlainAuth plain = 10;
SMTPXOAuth2Auth xoauth2 = 11;
}
}
// This is an empty response
+45
View File
@@ -140,6 +140,51 @@ message EmailProviderSMTP {
example: "\"replyto@m.zitadel.cloud\"";
}
];
oneof Auth {
SMTPNoAuth none = 9;
SMTPPlainAuth plain = 10;
SMTPXOAuth2Auth xoauth2 = 11;
}
}
// SMTPNoAuth can be used when no authentication is required for the SMTP connection.
message SMTPNoAuth {}
// SMTPPlainAuth adds plain authentication to the smtp connection. The username of the parent configuration is used in
// combination with the password in this message.
message SMTPPlainAuth {}
// SMTPXOAuth2Auth adds XOAut2 authentication to the smtp connection. The username of the parent configuration is used
// in combination with the other parameters of this message.
message SMTPXOAuth2Auth {
// token_endpoint is the endpoint from which a token will be requested according to the oauth2 specification.
string token_endpoint = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"http://auth.example.com/token\"";
}
];
// scopes is a list of scopes which should be requested from the authorization service. To authorize a token to
// use the smtp functionality, the server requires a specific scope. These scopes can be found in the documentation
// of the provider.
repeated string scopes = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"https://mail.example.com\"]";
}
];
// OAuth2Type is a oneof used to switch between the different types of oauth2 authorization flows.
oneof OAuth2Type {
ClientCredentials client_credentials = 3;
}
// ClientCredentials authenticates the SMTP client using Oauth 2.0 Client Credentials grant.
// https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
message ClientCredentials {
string client_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"197f0117-529e-443d-bf6c-0292dd9a02b7\"";
}
];
}
}
message EmailProviderHTTP {