Skip to content

Commit

Permalink
add file system based messaging
Browse files Browse the repository at this point in the history
  • Loading branch information
greenpau committed Apr 1, 2022
1 parent a6eaf95 commit d65b464
Show file tree
Hide file tree
Showing 11 changed files with 706 additions and 120 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,12 @@ clean:

qtest:
@echo "Perform quick tests ..."
@time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestNewConfig ./*.go
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestNewConfig ./*.go
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestNewServer ./*.go
@#time richgo test -v -coverprofile=.coverage/coverage.out internal/tag/*.go
@### time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestAuthorize ./pkg/authz/validator/...
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out -run TestAddProviders ./pkg/messaging/...
@time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/messaging/...
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/credentials/...
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/errors/...
@#time richgo test $(VERBOSE) $(TEST) -coverprofile=.coverage/coverage.out ./pkg/requests/...
Expand Down
3 changes: 3 additions & 0 deletions pkg/errors/messaging.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ const (

ErrMessagingProviderCredentialsWithPasswordless StandardError = "messaging provider config is both passwordless and has credentials"
ErrMessagingProviderAuthUnsupported StandardError = "messaging provider does not support AUTH extension"

ErrMessagingProviderSend StandardError = "messaging provider send error: %v"
ErrMessagingProviderDir StandardError = "messaging provider file dir error: %v"
)
19 changes: 19 additions & 0 deletions pkg/messaging/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
// Config represents a collection of various messaging providers.
type Config struct {
EmailProviders []*EmailProvider `json:"email_providers,omitempty" xml:"email_providers,omitempty" yaml:"email_providers,omitempty"`
FileProviders []*FileProvider `json:"file_providers,omitempty" xml:"file_providers,omitempty" yaml:"file_providers,omitempty"`
}

// Provider is an interface to work with messaging providers.
Expand All @@ -32,6 +33,7 @@ type Provider interface {
func (cfg *Config) Add(c Provider) error {
switch v := c.(type) {
case *EmailProvider:
case *FileProvider:
default:
return errors.ErrMessagingAddProviderConfigType.WithArgs(v)
}
Expand All @@ -43,6 +45,8 @@ func (cfg *Config) Add(c Provider) error {
switch v := c.(type) {
case *EmailProvider:
cfg.EmailProviders = append(cfg.EmailProviders, v)
case *FileProvider:
cfg.FileProviders = append(cfg.FileProviders, v)
}
return nil
}
Expand All @@ -54,6 +58,11 @@ func (cfg *Config) FindProvider(s string) bool {
return true
}
}
for _, p := range cfg.FileProviders {
if p.Name == s {
return true
}
}
return false
}

Expand All @@ -80,3 +89,13 @@ func (cfg *Config) ExtractEmailProvider(s string) *EmailProvider {
}
return nil
}

// ExtractFileProvider returns FileProvider by name.
func (cfg *Config) ExtractFileProvider(s string) *FileProvider {
for _, p := range cfg.FileProviders {
if p.Name == s {
return p
}
}
return nil
}
125 changes: 117 additions & 8 deletions pkg/messaging/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,36 @@
package messaging

import (
// "fmt"
"github.com/google/go-cmp/cmp"
"github.com/greenpau/go-authcrunch/internal/tests"
// "github.com/greenpau/go-authcrunch/pkg/errors"
"github.com/greenpau/go-authcrunch/pkg/errors"
"testing"
)

type dummyProvider struct {
}

func (p *dummyProvider) Validate() error {
return nil
}

func TestAddProviders(t *testing.T) {
tmpDir, err := tests.TempDir("TestAddMessagingProviders")
if err != nil {
t.Fatal(err)
}

testcases := []struct {
name string
entry Provider
want string
shouldErr bool
err error
name string
providerName string
entry Provider
want string
shouldErr bool
err error
}{
{
name: "test valid email",
name: "test valid email provider config",
providerName: "default",
entry: &EmailProvider{
Name: "default",
Address: "localhost",
Expand All @@ -51,6 +64,58 @@ func TestAddProviders(t *testing.T) {
]
}`,
},
{
name: "test valid email provider passwordless config",
providerName: "default",
entry: &EmailProvider{
Name: "default",
Address: "localhost",
Protocol: "smtp",
Passwordless: true,
SenderEmail: "root@localhost",
},
want: `{
"email_providers": [
{
"address": "localhost",
"name": "default",
"protocol": "smtp",
"passwordless": true,
"sender_email": "root@localhost"
}
]
}`,
},
{
name: "test valid file provider config",
providerName: "default",
entry: &FileProvider{
Name: "default",
RootDir: tmpDir,
},
want: `{
"file_providers": [
{
"name": "default",
"root_dir": "` + tmpDir + `"
}
]
}`,
},
{
name: "test invalid messaging provider config",
entry: &dummyProvider{},
shouldErr: true,
err: errors.ErrMessagingAddProviderConfigType.WithArgs(&dummyProvider{}),
},
{
name: "test file provider config without root directory",
entry: &FileProvider{
Name: "default",
},
shouldErr: true,
err: errors.ErrMessagingProviderKeyValueEmpty.WithArgs("root_dir"),
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
Expand All @@ -71,6 +136,50 @@ func TestAddProviders(t *testing.T) {
got := tests.Unpack(t, cfg)
want := tests.Unpack(t, tc.want)

if !cfg.FindProvider(tc.providerName) {
t.Fatalf("failed FindProvider with %q", tc.providerName)
}

switch tc.entry.(type) {
case *EmailProvider:
p := cfg.ExtractEmailProvider(tc.providerName)
if p == nil {
t.Fatalf("failed to extract %q file provider", tc.providerName)
}
providerCreds := cfg.FindProviderCredentials(tc.providerName)
switch providerCreds {
case "passwordless":
if !p.Passwordless {
t.Fatalf("provider credentials mismatch: %v, %v", providerCreds, p.Credentials)
}
case p.Credentials:
default:
t.Fatalf("provider credentials mismatch: %v, %v", providerCreds, p.Credentials)
}
case *FileProvider:
p := cfg.ExtractFileProvider(tc.providerName)
if p == nil {
t.Fatalf("failed to extract %q file provider", tc.providerName)
}
}

if tc.name == "test valid email provider config" {
if cfg.FindProvider("foobar") {
t.Fatal("unexpected success with FindProvider")
}

if cfg.ExtractEmailProvider("foo") != nil {
t.Fatal("unexpected success with ExtractEmailProvider")
}

if cfg.ExtractFileProvider("foo") != nil {
t.Fatal("unexpected success with ExtractEmailProvider")
}
if cfg.FindProviderCredentials("foo") != "" {
t.Fatal("unexpected success with FindProviderCredentials")
}
}

if diff := cmp.Diff(want, got); diff != "" {
t.Logf("JSON: %s", tests.UnpackJSON(t, got))
t.Errorf("Add() mismatch (-want +got):\n%s", diff)
Expand Down
111 changes: 0 additions & 111 deletions pkg/messaging/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,7 @@
package messaging

import (
"fmt"
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp"
"github.com/greenpau/go-authcrunch/pkg/credentials"
"github.com/greenpau/go-authcrunch/pkg/errors"
"github.com/greenpau/go-authcrunch/pkg/util"
"strings"
"time"
)

// EmailProvider represents email messaging provider.
Expand All @@ -38,94 +31,6 @@ type EmailProvider struct {
BlindCarbonCopy []string `json:"blind_carbon_copy,omitempty" xml:"blind_carbon_copy,omitempty" yaml:"blind_carbon_copy,omitempty"`
}

// Send sends an email message.
func (e *EmailProvider) Send(creds *credentials.Generic, rcpts []string, subj, body string) error {
dial := smtp.Dial
if e.Protocol == "smtps" {
dial = func(addr string) (*smtp.Client, error) {
return smtp.DialTLS(addr, nil)
}
}

c, err := dial(e.Address)
if err != nil {
return err
}
defer c.Close()

if found, _ := c.Extension("STARTTLS"); found {
if err := c.StartTLS(nil); err != nil {
return err
}
}

if !e.Passwordless && creds != nil {
if found, _ := c.Extension("AUTH"); !found {
return errors.ErrMessagingProviderAuthUnsupported
}
auth := sasl.NewPlainClient("", creds.Username, creds.Password)
if err := c.Auth(auth); err != nil {
return err
}
}

if err := c.Mail(e.SenderEmail, nil); err != nil {
return err
}

for _, rcpt := range rcpts {
if err := c.Rcpt(rcpt); err != nil {
return err
}
}

sender := e.SenderEmail
if e.SenderName != "" {
sender = `"` + e.SenderName + `" <` + e.SenderEmail + ">"
}

msg := "MIME-Version: 1.0\n"
msg += "Date: " + time.Now().Format(time.RFC1123Z) + "\n"
msg += "From: " + sender + "\n"
msg += "Subject: " + subj + "\n"
msg += "Thread-Topic: Account Registration." + "\n"
msg += "Message-ID: <" + util.GetRandomString(64) + "." + e.SenderEmail + ">" + "\n"
msg += `To: ` + strings.Join(rcpts, ", ") + "\n"

if len(e.BlindCarbonCopy) > 0 {
bccRcpts := dedupRcpt(rcpts, e.BlindCarbonCopy)
if len(bccRcpts) > 0 {
msg += "Bcc: " + strings.Join(bccRcpts, ", ") + "\n"
}
}

msg += "Content-Transfer-Encoding: quoted-printable" + "\n"
msg += `Content-Type: text/html; charset="utf-8"` + "\n"

msg += "\r\n" + body

// Write email subject body.
wc, err := c.Data()
if err != nil {
return err
}
_, err = fmt.Fprintf(wc, msg)
if err != nil {
return err
}

if err := wc.Close(); err != nil {
return err
}

// Close connection.
if err := c.Quit(); err != nil {
return err
}

return nil
}

// Validate validates EmailProvider configuration.
func (e *EmailProvider) Validate() error {
if e.Name == "" {
Expand Down Expand Up @@ -170,19 +75,3 @@ func (e *EmailProvider) Validate() error {
}
return nil
}

func dedupRcpt(arr1, arr2 []string) []string {
var output []string
m := make(map[string]interface{})
for _, s := range arr1 {
m[s] = true
}

for _, s := range arr2 {
if _, exists := m[s]; exists {
continue
}
output = append(output, s)
}
return output
}
Loading

0 comments on commit d65b464

Please sign in to comment.