Add support for SMTP Configurations test
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<b-card
|
||||
class="shadow-sm"
|
||||
header-bg-variant="white"
|
||||
footer-bg-variant="white"
|
||||
class="shadow-sm"
|
||||
>
|
||||
<b-form
|
||||
@submit.prevent="submit()"
|
||||
@@ -34,6 +34,7 @@
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
:label="$t('user.label')"
|
||||
:description="$t('user.description')"
|
||||
@@ -45,6 +46,7 @@
|
||||
autocomplete="off"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
:label="$t('password.label')"
|
||||
:description="$t('password.description')"
|
||||
@@ -86,6 +88,7 @@
|
||||
{{ $t('tlsInsecure.label') }}
|
||||
</b-form-checkbox>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
:label="$t('tlsServerName.label')"
|
||||
:description="$t('tlsServerName.description')"
|
||||
@@ -106,10 +109,21 @@
|
||||
|
||||
<template #footer>
|
||||
<c-submit-button
|
||||
class="float-right"
|
||||
:disabled="disabled"
|
||||
:processing="processing"
|
||||
:success="success"
|
||||
variant="light"
|
||||
class="float-left"
|
||||
@submit="smtpConnectionCheck()"
|
||||
>
|
||||
{{ $t('testSmtpConfigs.button') }}
|
||||
</c-submit-button>
|
||||
|
||||
<c-submit-button
|
||||
:disabled="disabled"
|
||||
:processing="processing"
|
||||
:success="success"
|
||||
class="float-right"
|
||||
@submit="submit()"
|
||||
/>
|
||||
</template>
|
||||
@@ -172,6 +186,10 @@ export default {
|
||||
submit () {
|
||||
this.$emit('submit', this.server)
|
||||
},
|
||||
|
||||
smtpConnectionCheck () {
|
||||
this.$emit('smtpConnectionCheck', this.server)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
:success="auth.success"
|
||||
:disabled="!canManage"
|
||||
@submit="onEmailServerSubmit($event)"
|
||||
@smtpConnectionCheck="onSmtpConnectionCheck($event)"
|
||||
/>
|
||||
</b-container>
|
||||
</template>
|
||||
@@ -113,6 +114,41 @@ export default {
|
||||
this.external.processing = false
|
||||
})
|
||||
},
|
||||
|
||||
onSmtpConnectionCheck (server) {
|
||||
this.external.processing = true
|
||||
|
||||
// Append the list of recepient's email Addresses
|
||||
const recepients = []
|
||||
recepients.push(server.from)
|
||||
|
||||
this.$SystemAPI.smtpConfigurationCheckerCheck({
|
||||
host: server.host,
|
||||
port: parseInt(server.port),
|
||||
recipients: recepients,
|
||||
username: server.user,
|
||||
password: server.pass,
|
||||
tlsInsecure: server.tlsInsecure,
|
||||
tlsServerName: server.tlsServerName,
|
||||
})
|
||||
.then(response => {
|
||||
if (Object.values(response).every(resp => resp === '')) {
|
||||
this.animateSuccess('external')
|
||||
this.toastSuccess(this.$t('notification:settings.system.smtpCheck.success'))
|
||||
}
|
||||
|
||||
Object.keys(response).forEach(key => {
|
||||
if (response[key]) {
|
||||
this.toastWarning(`${key}: ${response[key]}`)
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(this.toastErrorHandler(this.$t('notification:settings.system.smtpCheck.error')))
|
||||
.finally(() => {
|
||||
this.external.processing = false
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -4522,4 +4522,39 @@ export default class System {
|
||||
return '/data-privacy/connection/'
|
||||
}
|
||||
|
||||
// Check SMTP server configuration settings
|
||||
async smtpConfigurationCheckerCheck (a: KV, extra: AxiosRequestConfig = {}): Promise<KV> {
|
||||
const {
|
||||
host,
|
||||
port,
|
||||
recipients,
|
||||
username,
|
||||
password,
|
||||
tlsInsecure,
|
||||
tlsServerName,
|
||||
} = (a as KV) || {}
|
||||
if (!host) {
|
||||
throw Error('field host is empty')
|
||||
}
|
||||
const cfg: AxiosRequestConfig = {
|
||||
...extra,
|
||||
method: 'post',
|
||||
url: this.smtpConfigurationCheckerCheckEndpoint(),
|
||||
}
|
||||
cfg.data = {
|
||||
host,
|
||||
port,
|
||||
recipients,
|
||||
username,
|
||||
password,
|
||||
tlsInsecure,
|
||||
tlsServerName,
|
||||
}
|
||||
return this.api().request(cfg).then(result => stdResolve(result))
|
||||
}
|
||||
|
||||
smtpConfigurationCheckerCheckEndpoint (): string {
|
||||
return '/smtp/configuration-checker/'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -235,6 +235,9 @@ settings:
|
||||
style:
|
||||
success: Auth background styles updated
|
||||
error: Auth background styles update failed
|
||||
smtpCheck:
|
||||
success: SMTP configuration check passed
|
||||
error: SMTP configuration check failed
|
||||
compose:
|
||||
fetch:
|
||||
error: Compose settings fetch failed
|
||||
|
||||
@@ -27,3 +27,6 @@ editor:
|
||||
tlsServerName:
|
||||
label: TLS Server name
|
||||
description: Optional, If SMTP server uses a different value than used for the server name.
|
||||
|
||||
testSmtpConfigs:
|
||||
button: Test SMTP Server
|
||||
|
||||
56
server/pkg/mail/check.go
Normal file
56
server/pkg/mail/check.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
hostCheckRE = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"
|
||||
)
|
||||
|
||||
// ConfigCheck dials and authenticates to an SMTP server.
|
||||
func ConfigCheck(host string, port uint, username string, password string, tlsConfig *tls.Config) (checkRes string) {
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
// Dial the tcp connection
|
||||
conn, err := net.DialTimeout("tcp", addr, 10*time.Second)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
conn.SetDeadline(time.Now().Add(10 * time.Second))
|
||||
|
||||
c, err := smtp.NewClient(conn, host)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("SMTP connection to %s timeout!", addr)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
// Check whether STARTTLS extension is supported
|
||||
ok, _ := c.Extension("STARTTLS")
|
||||
if ok {
|
||||
err = c.StartTLS(tlsConfig)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// Authentication
|
||||
auth := smtp.PlainAuth("", username, password, host)
|
||||
|
||||
err = c.Auth(auth)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func IsValidHost(host string) bool {
|
||||
hostCheck := regexp.MustCompile(hostCheckRE)
|
||||
return hostCheck.MatchString(host)
|
||||
}
|
||||
30
server/pkg/mail/check_test.go
Normal file
30
server/pkg/mail/check_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHostValidator(t *testing.T) {
|
||||
ttc := []struct {
|
||||
host string
|
||||
ok bool
|
||||
}{
|
||||
{"ç$€§az.com", false},
|
||||
{"@sendyy.com", false},
|
||||
{"qwertyuiop.com", true},
|
||||
{"test.foo.bar", true},
|
||||
{"10.10.10", true},
|
||||
{"192.10.10345", true},
|
||||
{"1rg.10ui.10", true},
|
||||
{"info .crust tech", false},
|
||||
{"info.crust.tech", true},
|
||||
{"crust-tech?", false},
|
||||
{"crust-tech", true},
|
||||
{"crust/tech", false},
|
||||
}
|
||||
|
||||
for _, tc := range ttc {
|
||||
require.True(t, IsValidHost(tc.host) == tc.ok, "Validation of %s should return %v", tc.host, tc.ok)
|
||||
}
|
||||
}
|
||||
17
server/provision/002_templates/healthcheck.yml
Normal file
17
server/provision/002_templates/healthcheck.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
templates:
|
||||
smtp_configuration_check_subject:
|
||||
type: text/plain
|
||||
meta:
|
||||
short: SMTP configuration check content
|
||||
template: SMTP connection check
|
||||
|
||||
smtp_configuration_check_content:
|
||||
type: text/html
|
||||
meta:
|
||||
short: SMTP configuration check content
|
||||
template: |-
|
||||
{{template "email_general_header" .}}
|
||||
<h2 style="color: #568ba2;text-align: center;">SMTP configurations check</h2>
|
||||
<p>Hello,</p>
|
||||
<p>Your SMTP configuration test passed</p>
|
||||
{{template "email_general_footer" .}}
|
||||
@@ -2256,3 +2256,21 @@ endpoints:
|
||||
required: false
|
||||
title: Exclude (0, default), include (1) or return only (2) deleted connections
|
||||
type: filter.State
|
||||
|
||||
- title: SMTP Configuration Checker
|
||||
entrypoint: smtpConfigurationChecker
|
||||
path: "/smtp"
|
||||
apis:
|
||||
- name: check
|
||||
method: POST
|
||||
title: Check SMTP server configuration settings
|
||||
path: "/configuration-checker/"
|
||||
parameters:
|
||||
post:
|
||||
- { name: host, type: string, title: SMTP server host name, required: true }
|
||||
- { name: port, type: uint, title: SMTP server port, }
|
||||
- { name: recipients, type: "[]string", title: List of recipients email addresses that should recieve test email }
|
||||
- { name: username, type: string, title: SMTP server authentication username }
|
||||
- { name: password, type: string, title: SMTP server authentication password }
|
||||
- { name: tlsInsecure, type: bool, title: TLS mode }
|
||||
- { name: tlsServerName, type: string, title: TLS server name }
|
||||
|
||||
57
server/system/rest/handlers/smtpConfigurationChecker.go
Normal file
57
server/system/rest/handlers/smtpConfigurationChecker.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package handlers
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
// Definitions file that controls how this file is generated:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/cortezaproject/corteza/server/system/rest/request"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
SmtpConfigurationCheckerAPI interface {
|
||||
Check(context.Context, *request.SmtpConfigurationCheckerCheck) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
SmtpConfigurationChecker struct {
|
||||
Check func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewSmtpConfigurationChecker(h SmtpConfigurationCheckerAPI) *SmtpConfigurationChecker {
|
||||
return &SmtpConfigurationChecker{
|
||||
Check: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewSmtpConfigurationCheckerCheck()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Check(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h SmtpConfigurationChecker) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Post("/smtp/configuration-checker/", h.Check)
|
||||
})
|
||||
}
|
||||
250
server/system/rest/request/smtpConfigurationChecker.go
Normal file
250
server/system/rest/request/smtpConfigurationChecker.go
Normal file
@@ -0,0 +1,250 @@
|
||||
package request
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
// Definitions file that controls how this file is generated:
|
||||
//
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// dummy vars to prevent
|
||||
// unused imports complain
|
||||
var (
|
||||
_ = chi.URLParam
|
||||
_ = multipart.ErrMessageTooLarge
|
||||
_ = payload.ParseUint64s
|
||||
_ = strings.ToLower
|
||||
_ = io.EOF
|
||||
_ = fmt.Errorf
|
||||
_ = json.NewEncoder
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
SmtpConfigurationCheckerCheck struct {
|
||||
// Host POST parameter
|
||||
//
|
||||
// SMTP server host name
|
||||
Host string
|
||||
|
||||
// Port POST parameter
|
||||
//
|
||||
// SMTP server port
|
||||
Port uint
|
||||
|
||||
// Recipients POST parameter
|
||||
//
|
||||
// List of recipients email addresses that should recieve test email
|
||||
Recipients []string
|
||||
|
||||
// Username POST parameter
|
||||
//
|
||||
// SMTP server authentication username
|
||||
Username string
|
||||
|
||||
// Password POST parameter
|
||||
//
|
||||
// SMTP server authentication password
|
||||
Password string
|
||||
|
||||
// TlsInsecure POST parameter
|
||||
//
|
||||
// TLS mode
|
||||
TlsInsecure bool
|
||||
|
||||
// TlsServerName POST parameter
|
||||
//
|
||||
// TLS server name
|
||||
TlsServerName string
|
||||
}
|
||||
)
|
||||
|
||||
// NewSmtpConfigurationCheckerCheck request
|
||||
func NewSmtpConfigurationCheckerCheck() *SmtpConfigurationCheckerCheck {
|
||||
return &SmtpConfigurationCheckerCheck{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r SmtpConfigurationCheckerCheck) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"host": r.Host,
|
||||
"port": r.Port,
|
||||
"recipients": r.Recipients,
|
||||
"username": r.Username,
|
||||
"password": r.Password,
|
||||
"tlsInsecure": r.TlsInsecure,
|
||||
"tlsServerName": r.TlsServerName,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r SmtpConfigurationCheckerCheck) GetHost() string {
|
||||
return r.Host
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r SmtpConfigurationCheckerCheck) GetPort() uint {
|
||||
return r.Port
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r SmtpConfigurationCheckerCheck) GetRecipients() []string {
|
||||
return r.Recipients
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r SmtpConfigurationCheckerCheck) GetUsername() string {
|
||||
return r.Username
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r SmtpConfigurationCheckerCheck) GetPassword() string {
|
||||
return r.Password
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r SmtpConfigurationCheckerCheck) GetTlsInsecure() bool {
|
||||
return r.TlsInsecure
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r SmtpConfigurationCheckerCheck) GetTlsServerName() string {
|
||||
return r.TlsServerName
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *SmtpConfigurationCheckerCheck) Fill(req *http.Request) (err error) {
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
|
||||
err = json.NewDecoder(req.Body).Decode(r)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return fmt.Errorf("error parsing http request body: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Caching 32MB to memory, the rest to disk
|
||||
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
|
||||
return err
|
||||
} else if err == nil {
|
||||
// Multipart params
|
||||
|
||||
if val, ok := req.MultipartForm.Value["host"]; ok && len(val) > 0 {
|
||||
r.Host, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["port"]; ok && len(val) > 0 {
|
||||
r.Port, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["username"]; ok && len(val) > 0 {
|
||||
r.Username, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["password"]; ok && len(val) > 0 {
|
||||
r.Password, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["tlsInsecure"]; ok && len(val) > 0 {
|
||||
r.TlsInsecure, err = payload.ParseBool(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["tlsServerName"]; ok && len(val) > 0 {
|
||||
r.TlsServerName, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if err = req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// POST params
|
||||
|
||||
if val, ok := req.Form["host"]; ok && len(val) > 0 {
|
||||
r.Host, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["port"]; ok && len(val) > 0 {
|
||||
r.Port, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
//if val, ok := req.Form["recipients[]"]; ok && len(val) > 0 {
|
||||
// r.Recipients, err = val, nil
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//}
|
||||
|
||||
if val, ok := req.Form["username"]; ok && len(val) > 0 {
|
||||
r.Username, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["password"]; ok && len(val) > 0 {
|
||||
r.Password, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["tlsInsecure"]; ok && len(val) > 0 {
|
||||
r.TlsInsecure, err = payload.ParseBool(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["tlsServerName"]; ok && len(val) > 0 {
|
||||
r.TlsServerName, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -51,6 +51,7 @@ func MountRoutes() func(r chi.Router) {
|
||||
// @todo move these two to dataPrivacy routes
|
||||
handlers.NewDataPrivacyRequest(DataPrivacyRequest{}.New()).MountRoutes(r)
|
||||
handlers.NewDataPrivacyRequestComment(DataPrivacyRequestComment{}.New()).MountRoutes(r)
|
||||
handlers.NewSmtpConfigurationChecker(SmtpConfigurationChecker{}.New()).MountRoutes(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
47
server/system/rest/smtp_configuration_checker.go
Normal file
47
server/system/rest/smtp_configuration_checker.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/system/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/system/service"
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
SmtpConfigurationChecker struct {
|
||||
svc smtpConfigurationCheckerService
|
||||
}
|
||||
|
||||
smtpConfigurationCheckerService interface {
|
||||
Check(context.Context, *types.SmtpConfiguration) (*types.SmtpCheckResult, error)
|
||||
}
|
||||
)
|
||||
|
||||
func (SmtpConfigurationChecker) New() *SmtpConfigurationChecker {
|
||||
return &SmtpConfigurationChecker{
|
||||
svc: service.DefaultSMTPChecker,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *SmtpConfigurationChecker) Check(ctx context.Context, r *request.SmtpConfigurationCheckerCheck) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
checkResults = &types.SmtpCheckResult{}
|
||||
smtp = &types.SmtpConfiguration{
|
||||
Host: r.Host,
|
||||
Port: r.Port,
|
||||
Recipients: r.Recipients,
|
||||
Username: r.Username,
|
||||
Password: r.Password,
|
||||
TLSInsecure: r.TlsInsecure,
|
||||
TLSServerName: r.TlsServerName,
|
||||
}
|
||||
)
|
||||
|
||||
checkResults, err = ctrl.svc.Check(ctx, smtp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return checkResults, nil
|
||||
}
|
||||
@@ -91,6 +91,7 @@ var (
|
||||
DefaultApigwProfiler *apigwProfiler
|
||||
DefaultReport *report
|
||||
DefaultDataPrivacy *dataPrivacy
|
||||
DefaultSMTPChecker *smtpConfigurationChecker
|
||||
|
||||
DefaultStatistics *statistics
|
||||
|
||||
@@ -216,6 +217,7 @@ func Initialize(ctx context.Context, log *zap.Logger, s store.Storer, ws websock
|
||||
DefaultApigwProfiler = Profiler()
|
||||
DefaultApigwFilter = Filter()
|
||||
DefaultDataPrivacy = DataPrivacy(DefaultStore, DefaultAccessControl, DefaultActionlog, eventbus.Service())
|
||||
DefaultSMTPChecker = SmtpConfigurationChecker(CurrentSettings, DefaultRenderer, DefaultAccessControl, c.Auth)
|
||||
|
||||
if err = initRoles(ctx, log.Named("rbac.roles"), c.RBAC, eventbus.Service(), rbac.Global()); err != nil {
|
||||
return err
|
||||
|
||||
207
server/system/service/smtp_configuration_checker.go
Normal file
207
server/system/service/smtp_configuration_checker.go
Normal file
@@ -0,0 +1,207 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
intAuth "github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/pkg/mail"
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
gomail "gopkg.in/mail.v2"
|
||||
htpl "html/template"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
smtpConfigurationChecker struct {
|
||||
settings *types.AppSettings
|
||||
ts TemplateService
|
||||
opt options.AuthOpt
|
||||
accessControl smtpCheckAccessController
|
||||
}
|
||||
|
||||
smtpCheckAccessController interface {
|
||||
CanManageSettings(context.Context) bool
|
||||
}
|
||||
)
|
||||
|
||||
func SmtpConfigurationChecker(s *types.AppSettings, ts TemplateService, ac accessController, opt options.AuthOpt) *smtpConfigurationChecker {
|
||||
return &smtpConfigurationChecker{
|
||||
settings: s,
|
||||
ts: ts,
|
||||
opt: opt,
|
||||
accessControl: ac,
|
||||
}
|
||||
}
|
||||
|
||||
// Check SMTP server configurations and send a test email
|
||||
// to recipients if they're provided
|
||||
func (svc smtpConfigurationChecker) Check(ctx context.Context, smtpConfigs *types.SmtpConfiguration) (checkResults *types.SmtpCheckResult, err error) {
|
||||
if !svc.accessControl.CanManageSettings(ctx) {
|
||||
return nil, fmt.Errorf("not allowed to check SMTP configurations")
|
||||
}
|
||||
|
||||
var (
|
||||
tlsConfig = &tls.Config{}
|
||||
)
|
||||
|
||||
checkResults = &types.SmtpCheckResult{}
|
||||
|
||||
if smtpConfigs.Port == 0 {
|
||||
smtpConfigs.Port = 25
|
||||
}
|
||||
|
||||
//check for validity of the host
|
||||
if !mail.IsValidHost(smtpConfigs.Host) {
|
||||
checkResults.Host = fmt.Sprintf("%s name is Invalid", smtpConfigs.Host)
|
||||
}
|
||||
|
||||
// Applying TLS
|
||||
tlsConfig = &tls.Config{ServerName: smtpConfigs.Host}
|
||||
|
||||
if smtpConfigs.TLSInsecure {
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
if smtpConfigs.TLSServerName != "" {
|
||||
tlsConfig.ServerName = smtpConfigs.TLSServerName
|
||||
}
|
||||
|
||||
checkResults.Server = mail.ConfigCheck(smtpConfigs.Host, smtpConfigs.Port, smtpConfigs.Username, smtpConfigs.Password, tlsConfig)
|
||||
|
||||
//send the email there are recipients
|
||||
if checkResults.Server == "" {
|
||||
if len(smtpConfigs.Recipients) != 0 {
|
||||
checkResults.Send, err = svc.smtpSend(ctx, smtpConfigs.Recipients)
|
||||
}
|
||||
}
|
||||
|
||||
return checkResults, err
|
||||
}
|
||||
|
||||
func (svc smtpConfigurationChecker) smtpSend(ctx context.Context, recipients []string) (expected string, err error) {
|
||||
var (
|
||||
ntf = mail.New()
|
||||
toHeader string
|
||||
// context with service user
|
||||
// we need this for retrieving & rendering email templates
|
||||
suCtx = intAuth.SetIdentityToContext(ctx, intAuth.ServiceUser())
|
||||
)
|
||||
|
||||
if err = svc.procEmailRecipients(ntf, "To", recipients); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
toHeader = strings.Join(recipients, ",")
|
||||
ntf.SetAddressHeader("To", toHeader, "")
|
||||
|
||||
st, ct, err := svc.findEmailTemplates(suCtx)
|
||||
// if we cannot find an email template
|
||||
if err != nil {
|
||||
ntf.SetHeader("Subject", "SMTP Configuration check")
|
||||
ntf.SetBody("text/html", "<h2 style=\"color: #568ba2;text-align: center;\">SMTP configurations check passed</h2>")
|
||||
|
||||
err = mail.Send(ntf)
|
||||
|
||||
if err != nil {
|
||||
return err.Error(), nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
subjectTmp, contentTmp, err := svc.procEmailTemplate(suCtx, st.ID, ct.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ntf.SetHeader("Subject", string(subjectTmp))
|
||||
ntf.SetBody("text/html", string(contentTmp))
|
||||
|
||||
err = mail.Send(ntf)
|
||||
|
||||
if err != nil {
|
||||
return err.Error(), nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (svc smtpConfigurationChecker) procEmailRecipients(m *gomail.Message, field string, recipients []string) (err error) {
|
||||
var (
|
||||
email string
|
||||
rcpt string
|
||||
)
|
||||
|
||||
if len(recipients) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for _, rcpt = range recipients {
|
||||
|
||||
email = strings.TrimSpace(rcpt)
|
||||
|
||||
// Validate email here
|
||||
if !mail.IsValidAddress(email) {
|
||||
return fmt.Errorf("invalid recipient email address %s", email)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
m.SetHeader(field, recipients...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// procEmailTemplate processes Email address template based on the template's subject ID and content ID
|
||||
func (svc smtpConfigurationChecker) procEmailTemplate(ctx context.Context, stId uint64, ctId uint64) (subjectTmp []byte, contentTmp []byte, err error) {
|
||||
// Prepare payload
|
||||
payload := map[string]interface{}{
|
||||
"Logo": htpl.URL(svc.settings.General.Mail.Logo),
|
||||
"BaseURL": svc.opt.BaseURL,
|
||||
}
|
||||
|
||||
// Render document
|
||||
subject, err := svc.ts.Render(ctx, stId, "text/plain", payload, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
content, err := svc.ts.Render(ctx, ctId, "text/plain", payload, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
subjectTmp, err = ioutil.ReadAll(subject)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
contentTmp, err = ioutil.ReadAll(content)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return subjectTmp, contentTmp, nil
|
||||
}
|
||||
|
||||
func (svc smtpConfigurationChecker) findEmailTemplates(ctx context.Context) (st *types.Template, ct *types.Template, err error) {
|
||||
var (
|
||||
hdl string
|
||||
)
|
||||
|
||||
hdl = "smtp_configuration_check_subject"
|
||||
st, err = svc.ts.FindByHandle(ctx, hdl)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
hdl = "smtp_configuration_check_content"
|
||||
ct, err = svc.ts.FindByHandle(ctx, hdl)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return st, ct, nil
|
||||
}
|
||||
21
server/system/types/smtp_configuration.go
Normal file
21
server/system/types/smtp_configuration.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package types
|
||||
|
||||
type (
|
||||
SmtpConfiguration struct {
|
||||
Host string
|
||||
Port uint
|
||||
Recipients []string
|
||||
Username string
|
||||
Password string
|
||||
TLSInsecure bool
|
||||
TLSServerName string
|
||||
}
|
||||
|
||||
// SmtpCheckResult represents the messages returned after SMTP Host validation,
|
||||
// SMTP Server configurations check and Send test email process
|
||||
SmtpCheckResult struct {
|
||||
Host string `json:"host"`
|
||||
Server string `json:"server"`
|
||||
Send string `json:"send"`
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user