feat: add http as smtp provider (#8545)

# Which Problems Are Solved

Send Email messages as a HTTP call to a relay, for own logic on handling
different Email providers

# How the Problems Are Solved

Create endpoints under Email provider to manage SMTP and HTTP in the
notification handlers.

# Additional Changes

Clean up old logic in command and query side to handle the general Email
providers with deactivate, activate and remove.

# Additional Context

Partially closes #8270

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Stefan Benz
2024-09-12 06:27:29 +02:00
committed by GitHub
parent d8a71d217c
commit 21c38b061d
28 changed files with 3575 additions and 1152 deletions

View File

@@ -0,0 +1,140 @@
package admin
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/object"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
)
func (s *Server) GetEmailProvider(ctx context.Context, req *admin_pb.GetEmailProviderRequest) (*admin_pb.GetEmailProviderResponse, error) {
smtp, err := s.query.SMTPConfigActive(ctx, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
return &admin_pb.GetEmailProviderResponse{
Config: emailProviderToProviderPb(smtp),
}, nil
}
func (s *Server) GetEmailProviderById(ctx context.Context, req *admin_pb.GetEmailProviderByIdRequest) (*admin_pb.GetEmailProviderByIdResponse, error) {
smtp, err := s.query.SMTPConfigByID(ctx, authz.GetInstance(ctx).InstanceID(), req.Id)
if err != nil {
return nil, err
}
return &admin_pb.GetEmailProviderByIdResponse{
Config: emailProviderToProviderPb(smtp),
}, nil
}
func (s *Server) AddEmailProviderSMTP(ctx context.Context, req *admin_pb.AddEmailProviderSMTPRequest) (*admin_pb.AddEmailProviderSMTPResponse, error) {
config := addEmailProviderSMTPToConfig(ctx, req)
if err := s.command.AddSMTPConfig(ctx, config); err != nil {
return nil, err
}
return &admin_pb.AddEmailProviderSMTPResponse{
Details: object.DomainToChangeDetailsPb(config.Details),
Id: config.ID,
}, nil
}
func (s *Server) UpdateEmailProviderSMTP(ctx context.Context, req *admin_pb.UpdateEmailProviderSMTPRequest) (*admin_pb.UpdateEmailProviderSMTPResponse, error) {
config := updateEmailProviderSMTPToConfig(ctx, req)
if err := s.command.ChangeSMTPConfig(ctx, config); err != nil {
return nil, err
}
return &admin_pb.UpdateEmailProviderSMTPResponse{
Details: object.DomainToChangeDetailsPb(config.Details),
}, nil
}
func (s *Server) AddEmailProviderHTTP(ctx context.Context, req *admin_pb.AddEmailProviderHTTPRequest) (*admin_pb.AddEmailProviderHTTPResponse, error) {
config := addEmailProviderHTTPToConfig(ctx, req)
if err := s.command.AddSMTPConfigHTTP(ctx, config); err != nil {
return nil, err
}
return &admin_pb.AddEmailProviderHTTPResponse{
Details: object.DomainToChangeDetailsPb(config.Details),
Id: config.ID,
}, nil
}
func (s *Server) UpdateEmailProviderHTTP(ctx context.Context, req *admin_pb.UpdateEmailProviderHTTPRequest) (*admin_pb.UpdateEmailProviderHTTPResponse, error) {
config := updateEmailProviderHTTPToConfig(ctx, req)
if err := s.command.ChangeSMTPConfigHTTP(ctx, config); err != nil {
return nil, err
}
return &admin_pb.UpdateEmailProviderHTTPResponse{
Details: object.DomainToChangeDetailsPb(config.Details),
}, nil
}
func (s *Server) RemoveEmailProvider(ctx context.Context, req *admin_pb.RemoveEmailProviderRequest) (*admin_pb.RemoveEmailProviderResponse, error) {
details, err := s.command.RemoveSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id)
if err != nil {
return nil, err
}
return &admin_pb.RemoveEmailProviderResponse{
Details: object.DomainToChangeDetailsPb(details),
}, nil
}
func (s *Server) UpdateEmailProviderSMTPPassword(ctx context.Context, req *admin_pb.UpdateEmailProviderSMTPPasswordRequest) (*admin_pb.UpdateEmailProviderSMTPPasswordResponse, error) {
details, err := s.command.ChangeSMTPConfigPassword(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, req.Password)
if err != nil {
return nil, err
}
return &admin_pb.UpdateEmailProviderSMTPPasswordResponse{
Details: object.DomainToChangeDetailsPb(details),
}, nil
}
func (s *Server) ListEmailProviders(ctx context.Context, req *admin_pb.ListEmailProvidersRequest) (*admin_pb.ListEmailProvidersResponse, error) {
queries, err := listEmailProvidersToModel(req)
if err != nil {
return nil, err
}
result, err := s.query.SearchSMTPConfigs(ctx, queries)
if err != nil {
return nil, err
}
return &admin_pb.ListEmailProvidersResponse{
Details: object.ToListDetails(result.Count, result.Sequence, result.LastRun),
Result: emailProvidersToPb(result.Configs),
}, nil
}
func (s *Server) ActivateEmailProvider(ctx context.Context, req *admin_pb.ActivateEmailProviderRequest) (*admin_pb.ActivateEmailProviderResponse, error) {
result, err := s.command.ActivateSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id)
if err != nil {
return nil, err
}
return &admin_pb.ActivateEmailProviderResponse{
Details: object.DomainToAddDetailsPb(result),
}, nil
}
func (s *Server) DeactivateEmailProvider(ctx context.Context, req *admin_pb.DeactivateEmailProviderRequest) (*admin_pb.DeactivateEmailProviderResponse, error) {
result, err := s.command.DeactivateSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id)
if err != nil {
return nil, err
}
return &admin_pb.DeactivateEmailProviderResponse{
Details: object.DomainToAddDetailsPb(result),
}, nil
}
func (s *Server) TestEmailProviderById(ctx context.Context, req *admin_pb.TestEmailProviderSMTPByIdRequest) (*admin_pb.TestEmailProviderSMTPByIdResponse, error) {
if err := s.command.TestSMTPConfigById(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, req.ReceiverAddress); err != nil {
return nil, err
}
return &admin_pb.TestEmailProviderSMTPByIdResponse{}, nil
}
func (s *Server) TestEmailProviderSMTP(ctx context.Context, req *admin_pb.TestEmailProviderSMTPRequest) (*admin_pb.TestEmailProviderSMTPResponse, error) {
if err := s.command.TestSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, req.ReceiverAddress, testEmailProviderSMTPToConfig(req)); err != nil {
return nil, err
}
return &admin_pb.TestEmailProviderSMTPResponse{}, nil
}

View File

@@ -0,0 +1,145 @@
package admin
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/object"
"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"
settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings"
)
func listEmailProvidersToModel(req *admin_pb.ListEmailProvidersRequest) (*query.SMTPConfigsSearchQueries, error) {
offset, limit, asc := object.ListQueryToModel(req.Query)
return &query.SMTPConfigsSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
},
}, nil
}
func emailProvidersToPb(configs []*query.SMTPConfig) []*settings_pb.EmailProvider {
c := make([]*settings_pb.EmailProvider, len(configs))
for i, config := range configs {
c[i] = emailProviderToProviderPb(config)
}
return c
}
func emailProviderToProviderPb(config *query.SMTPConfig) *settings_pb.EmailProvider {
return &settings_pb.EmailProvider{
Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner),
Id: config.ID,
Description: config.Description,
State: emailProviderStateToPb(config.State),
Config: emailProviderToPb(config),
}
}
func emailProviderStateToPb(state domain.SMTPConfigState) settings_pb.EmailProviderState {
switch state {
case domain.SMTPConfigStateUnspecified, domain.SMTPConfigStateRemoved:
return settings_pb.EmailProviderState_EMAIL_PROVIDER_STATE_UNSPECIFIED
case domain.SMTPConfigStateActive:
return settings_pb.EmailProviderState_EMAIL_PROVIDER_ACTIVE
case domain.SMTPConfigStateInactive:
return settings_pb.EmailProviderState_EMAIL_PROVIDER_INACTIVE
default:
return settings_pb.EmailProviderState_EMAIL_PROVIDER_STATE_UNSPECIFIED
}
}
func emailProviderToPb(config *query.SMTPConfig) settings_pb.EmailConfig {
if config.SMTPConfig != nil {
return smtpToPb(config.SMTPConfig)
}
if config.HTTPConfig != nil {
return httpToPb(config.HTTPConfig)
}
return nil
}
func httpToPb(http *query.HTTP) *settings_pb.EmailProvider_Http {
return &settings_pb.EmailProvider_Http{
Http: &settings_pb.EmailProviderHTTP{
Endpoint: http.Endpoint,
},
}
}
func smtpToPb(config *query.SMTP) *settings_pb.EmailProvider_Smtp {
return &settings_pb.EmailProvider_Smtp{
Smtp: &settings_pb.EmailProviderSMTP{
Tls: config.TLS,
Host: config.Host,
User: config.User,
SenderAddress: config.SenderAddress,
SenderName: config.SenderName,
},
}
}
func addEmailProviderSMTPToConfig(ctx context.Context, req *admin_pb.AddEmailProviderSMTPRequest) *command.AddSMTPConfig {
return &command.AddSMTPConfig{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
Description: req.Description,
Tls: req.Tls,
From: req.SenderAddress,
FromName: req.SenderName,
ReplyToAddress: req.ReplyToAddress,
Host: req.Host,
User: req.User,
Password: req.Password,
}
}
func updateEmailProviderSMTPToConfig(ctx context.Context, req *admin_pb.UpdateEmailProviderSMTPRequest) *command.ChangeSMTPConfig {
return &command.ChangeSMTPConfig{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
ID: req.Id,
Description: req.Description,
Tls: req.Tls,
From: req.SenderAddress,
FromName: req.SenderName,
ReplyToAddress: req.ReplyToAddress,
Host: req.Host,
User: req.User,
Password: req.Password,
}
}
func addEmailProviderHTTPToConfig(ctx context.Context, req *admin_pb.AddEmailProviderHTTPRequest) *command.AddSMTPConfigHTTP {
return &command.AddSMTPConfigHTTP{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
Description: req.Description,
Endpoint: req.Endpoint,
}
}
func updateEmailProviderHTTPToConfig(ctx context.Context, req *admin_pb.UpdateEmailProviderHTTPRequest) *command.ChangeSMTPConfigHTTP {
return &command.ChangeSMTPConfigHTTP{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
ID: req.Id,
Description: req.Description,
Endpoint: req.Endpoint,
}
}
func testEmailProviderSMTPToConfig(req *admin_pb.TestEmailProviderSMTPRequest) *smtp.Config {
return &smtp.Config{
Tls: req.Tls,
From: req.SenderAddress,
FromName: req.SenderName,
SMTP: smtp.SMTP{
Host: req.Host,
User: req.User,
Password: req.Password,
},
}
}

View File

@@ -1,14 +1,16 @@
package admin
import (
"context"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/object"
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
@@ -131,50 +133,51 @@ func SecretGeneratorTypeToDomain(generatorType settings_pb.SecretGeneratorType)
}
}
func AddSMTPToConfig(req *admin_pb.AddSMTPConfigRequest) *smtp.Config {
return &smtp.Config{
func addSMTPToConfig(ctx context.Context, req *admin_pb.AddSMTPConfigRequest) *command.AddSMTPConfig {
return &command.AddSMTPConfig{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
Description: req.Description,
Tls: req.Tls,
From: req.SenderAddress,
FromName: req.SenderName,
ReplyToAddress: req.ReplyToAddress,
SMTP: smtp.SMTP{
Host: req.Host,
User: req.User,
Password: req.Password,
},
Host: req.Host,
User: req.User,
Password: req.Password,
}
}
func UpdateSMTPToConfig(req *admin_pb.UpdateSMTPConfigRequest) *smtp.Config {
return &smtp.Config{
func updateSMTPToConfig(ctx context.Context, req *admin_pb.UpdateSMTPConfigRequest) *command.ChangeSMTPConfig {
return &command.ChangeSMTPConfig{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
ID: req.Id,
Description: req.Description,
Tls: req.Tls,
From: req.SenderAddress,
FromName: req.SenderName,
ReplyToAddress: req.ReplyToAddress,
SMTP: smtp.SMTP{
Host: req.Host,
User: req.User,
Password: req.Password,
},
Host: req.Host,
User: req.User,
Password: req.Password,
}
}
func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig {
mapped := &settings_pb.SMTPConfig{
Description: smtp.Description,
Tls: smtp.TLS,
SenderAddress: smtp.SenderAddress,
SenderName: smtp.SenderName,
ReplyToAddress: smtp.ReplyToAddress,
Host: smtp.Host,
User: smtp.User,
Details: obj_grpc.ToViewDetailsPb(smtp.Sequence, smtp.CreationDate, smtp.ChangeDate, smtp.ResourceOwner),
Id: smtp.ID,
State: settings_pb.SMTPConfigState(smtp.State),
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),
}
}
return mapped
return nil
}
func SecurityPolicyToPb(policy *query.SecurityPolicy) *settings_pb.SecurityPolicy {

View File

@@ -20,10 +20,7 @@ func (s *Server) GetSMTPConfig(ctx context.Context, req *admin_pb.GetSMTPConfigR
}
func (s *Server) GetSMTPConfigById(ctx context.Context, req *admin_pb.GetSMTPConfigByIdRequest) (*admin_pb.GetSMTPConfigByIdResponse, error) {
instanceID := authz.GetInstance(ctx).InstanceID()
resourceOwner := instanceID // Will be replaced when orgs have smtp configs
smtp, err := s.query.SMTPConfigByID(ctx, instanceID, resourceOwner, req.Id)
smtp, err := s.query.SMTPConfigByID(ctx, authz.GetInstance(ctx).InstanceID(), req.Id)
if err != nil {
return nil, err
}
@@ -33,29 +30,23 @@ func (s *Server) GetSMTPConfigById(ctx context.Context, req *admin_pb.GetSMTPCon
}
func (s *Server) AddSMTPConfig(ctx context.Context, req *admin_pb.AddSMTPConfigRequest) (*admin_pb.AddSMTPConfigResponse, error) {
id, details, err := s.command.AddSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), AddSMTPToConfig(req))
if err != nil {
config := addSMTPToConfig(ctx, req)
if err := s.command.AddSMTPConfig(ctx, config); err != nil {
return nil, err
}
return &admin_pb.AddSMTPConfigResponse{
Details: object.ChangeToDetailsPb(
details.Sequence,
details.EventDate,
details.ResourceOwner),
Id: id,
Details: object.DomainToChangeDetailsPb(config.Details),
Id: config.ID,
}, nil
}
func (s *Server) UpdateSMTPConfig(ctx context.Context, req *admin_pb.UpdateSMTPConfigRequest) (*admin_pb.UpdateSMTPConfigResponse, error) {
details, err := s.command.ChangeSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, UpdateSMTPToConfig(req))
if err != nil {
config := updateSMTPToConfig(ctx, req)
if err := s.command.ChangeSMTPConfig(ctx, config); err != nil {
return nil, err
}
return &admin_pb.UpdateSMTPConfigResponse{
Details: object.ChangeToDetailsPb(
details.Sequence,
details.EventDate,
details.ResourceOwner),
Details: object.DomainToChangeDetailsPb(config.Details),
}, nil
}
@@ -65,10 +56,7 @@ func (s *Server) RemoveSMTPConfig(ctx context.Context, req *admin_pb.RemoveSMTPC
return nil, err
}
return &admin_pb.RemoveSMTPConfigResponse{
Details: object.ChangeToDetailsPb(
details.Sequence,
details.EventDate,
details.ResourceOwner),
Details: object.DomainToChangeDetailsPb(details),
}, nil
}
@@ -78,10 +66,7 @@ func (s *Server) UpdateSMTPConfigPassword(ctx context.Context, req *admin_pb.Upd
return nil, err
}
return &admin_pb.UpdateSMTPConfigPasswordResponse{
Details: object.ChangeToDetailsPb(
details.Sequence,
details.EventDate,
details.ResourceOwner),
Details: object.DomainToChangeDetailsPb(details),
}, nil
}
@@ -101,19 +86,11 @@ func (s *Server) ListSMTPConfigs(ctx context.Context, req *admin_pb.ListSMTPConf
}
func (s *Server) ActivateSMTPConfig(ctx context.Context, req *admin_pb.ActivateSMTPConfigRequest) (*admin_pb.ActivateSMTPConfigResponse, error) {
// Get the ID of current SMTP active provider if any
currentActiveProviderID := ""
smtp, err := s.query.SMTPConfigActive(ctx, authz.GetInstance(ctx).InstanceID())
if err == nil {
currentActiveProviderID = smtp.ID
}
result, err := s.command.ActivateSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, currentActiveProviderID)
result, err := s.command.ActivateSMTPConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id)
if err != nil {
return nil, err
}
return &admin_pb.ActivateSMTPConfigResponse{
Details: object.DomainToAddDetailsPb(result),
}, nil

View File

@@ -2,6 +2,7 @@ package admin
import (
"github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings"
@@ -23,12 +24,12 @@ func SMTPConfigToProviderPb(config *query.SMTPConfig) *settings_pb.SMTPConfig {
Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner),
Id: config.ID,
Description: config.Description,
Tls: config.TLS,
Host: config.Host,
User: config.User,
State: settings_pb.SMTPConfigState(config.State),
SenderAddress: config.SenderAddress,
SenderName: config.SenderName,
Tls: config.SMTPConfig.TLS,
Host: config.SMTPConfig.Host,
User: config.SMTPConfig.User,
State: SMTPConfigStateToPb(config.State),
SenderAddress: config.SMTPConfig.SenderAddress,
SenderName: config.SMTPConfig.SenderName,
}
}
@@ -39,3 +40,16 @@ func SMTPConfigsToPb(configs []*query.SMTPConfig) []*settings_pb.SMTPConfig {
}
return c
}
func SMTPConfigStateToPb(state domain.SMTPConfigState) settings_pb.SMTPConfigState {
switch state {
case domain.SMTPConfigStateUnspecified, domain.SMTPConfigStateRemoved:
return settings_pb.SMTPConfigState_SMTP_CONFIG_STATE_UNSPECIFIED
case domain.SMTPConfigStateActive:
return settings_pb.SMTPConfigState_SMTP_CONFIG_ACTIVE
case domain.SMTPConfigStateInactive:
return settings_pb.SMTPConfigState_SMTP_CONFIG_INACTIVE
default:
return settings_pb.SMTPConfigState_SMTP_CONFIG_STATE_UNSPECIFIED
}
}