Other: Linter fixes after bumping linter version

This commit is contained in:
James Houlahan
2022-10-11 18:04:39 +02:00
parent 4a5c411665
commit 14a578f319
23 changed files with 273 additions and 206 deletions

View File

@ -218,10 +218,10 @@ change-copyright-year:
./utils/missing_license.sh change-year ./utils/missing_license.sh change-year
test: gofiles test: gofiles
go test -v -failfast -count=1 -p=1 -coverprofile=/tmp/coverage.out -run=${TESTRUN} ./internal/... ./pkg/... go test -v -coverprofile=/tmp/coverage.out -run=${TESTRUN} ./internal/... ./pkg/...
test-integration: gofiles test-integration: gofiles
go test -v -failfast -count=1 -p=1 github.com/ProtonMail/proton-bridge/v2/tests go test -v github.com/ProtonMail/proton-bridge/v2/tests
bench: bench:
go test -run '^$$' -bench=. -memprofile bench_mem.pprof -cpuprofile bench_cpu.pprof ./internal/store go test -run '^$$' -bench=. -memprofile bench_mem.pprof -cpuprofile bench_cpu.pprof ./internal/store

2
go.mod
View File

@ -38,7 +38,7 @@ require (
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.0
github.com/urfave/cli/v2 v2.16.3 github.com/urfave/cli/v2 v2.16.3
gitlab.protontech.ch/go/liteapi v0.33.2-0.20221011093920-6c0cf0847bcf gitlab.protontech.ch/go/liteapi v0.33.2-0.20221011164043-97f5d601ba2b
golang.org/x/exp v0.0.0-20220921164117-439092de6870 golang.org/x/exp v0.0.0-20220921164117-439092de6870
golang.org/x/net v0.1.0 golang.org/x/net v0.1.0
golang.org/x/sys v0.1.0 golang.org/x/sys v0.1.0

4
go.sum
View File

@ -397,8 +397,8 @@ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsr
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0= github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0=
github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA=
gitlab.protontech.ch/go/liteapi v0.33.2-0.20221011093920-6c0cf0847bcf h1:WBUv+vl0zTc+VEWied2YDv/HmLwjAMxqsqzNBoT7d4Y= gitlab.protontech.ch/go/liteapi v0.33.2-0.20221011164043-97f5d601ba2b h1:9bTndevIV9WTSbRsoLXmLj8bycla6O3KU7fFzEV09n0=
gitlab.protontech.ch/go/liteapi v0.33.2-0.20221011093920-6c0cf0847bcf/go.mod h1:NfsxXn1T81sz0gHnxuAfyCI4Agzm5UWVRyEtdQSch/4= gitlab.protontech.ch/go/liteapi v0.33.2-0.20221011164043-97f5d601ba2b/go.mod h1:NfsxXn1T81sz0gHnxuAfyCI4Agzm5UWVRyEtdQSch/4=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=

View File

@ -1,3 +1,4 @@
// Package bridge implements the Bridge, which acts as the backend to the UI.
package bridge package bridge
import ( import (
@ -59,7 +60,7 @@ type Bridge struct {
updateCheckCh chan struct{} updateCheckCh chan struct{}
// focusService is used to raise the bridge window when needed. // focusService is used to raise the bridge window when needed.
focusService *focus.FocusService focusService *focus.Service
// autostarter is the bridge's autostarter. // autostarter is the bridge's autostarter.
autostarter Autostarter autostarter Autostarter
@ -70,36 +71,30 @@ type Bridge struct {
// errors contains errors encountered during startup. // errors contains errors encountered during startup.
errors []error errors []error
// These control the bridge's IMAP and SMTP logging behaviour.
logIMAPClient bool
logIMAPServer bool
logSMTP bool
// stopCh is used to stop ongoing goroutines when the bridge is closed. // stopCh is used to stop ongoing goroutines when the bridge is closed.
stopCh chan struct{} stopCh chan struct{}
logIMAPClientCommands bool
logIMAPServerCommands bool
logSMTPCommands bool
} }
// New creates a new bridge. // New creates a new bridge.
func New( func New(
apiURL string, // the URL of the API to use apiURL string, // the URL of the API to use
locator Locator, // the locator to provide paths to store data locator Locator, // the locator to provide paths to store data
vault *vault.Vault, // the bridge's encrypted data store vault *vault.Vault, // the bridge's encrypted data store
identifier Identifier, // the identifier to keep track of the user agent identifier Identifier, // the identifier to keep track of the user agent
tlsReporter TLSReporter, // the TLS reporter to report TLS errors tlsReporter TLSReporter, // the TLS reporter to report TLS errors
roundTripper http.RoundTripper, // the round tripper to use for API requests roundTripper http.RoundTripper, // the round tripper to use for API requests
proxyCtl ProxyController, // the DoH controller proxyCtl ProxyController, // the DoH controller
autostarter Autostarter, // the autostarter to manage autostart settings autostarter Autostarter, // the autostarter to manage autostart settings
updater Updater, // the updater to fetch and install updates updater Updater, // the updater to fetch and install updates
curVersion *semver.Version, // the current version of the bridge curVersion *semver.Version, // the current version of the bridge
logIMAPClientCommands bool, logIMAPClient, logIMAPServer bool, // whether to log IMAP client/server activity
logIMAPServerCommands bool, logSMTP bool, // whether to log SMTP activity
logSMTPCommands bool,
) (*Bridge, error) { ) (*Bridge, error) {
if vault.GetProxyAllowed() {
proxyCtl.AllowProxy()
} else {
proxyCtl.DisallowProxy()
}
cookieJar, err := cookies.NewCookieJar(vault) cookieJar, err := cookies.NewCookieJar(vault)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create cookie jar: %w", err) return nil, fmt.Errorf("failed to create cookie jar: %w", err)
@ -117,25 +112,19 @@ func New(
return nil, fmt.Errorf("failed to load TLS config: %w", err) return nil, fmt.Errorf("failed to load TLS config: %w", err)
} }
// TODO: Handle case that the gluon directory is missing!
gluonDir, err := getGluonDir(vault) gluonDir, err := getGluonDir(vault)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get Gluon directory: %w", err) return nil, fmt.Errorf("failed to get Gluon directory: %w", err)
} }
imapServer, err := newIMAPServer(gluonDir, curVersion, tlsConfig, logIMAPClientCommands, logIMAPServerCommands)
if err != nil {
return nil, fmt.Errorf("failed to create IMAP server: %w", err)
}
smtpBackend, err := newSMTPBackend() smtpBackend, err := newSMTPBackend()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create SMTP backend: %w", err) return nil, fmt.Errorf("failed to create SMTP backend: %w", err)
} }
smtpServer, err := newSMTPServer(smtpBackend, tlsConfig, logSMTPCommands) imapServer, err := newIMAPServer(gluonDir, curVersion, tlsConfig, logIMAPClient, logIMAPServer)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create SMTP server: %w", err) return nil, fmt.Errorf("failed to create IMAP server: %w", err)
} }
focusService, err := focus.NewService() focusService, err := focus.NewService()
@ -143,7 +132,49 @@ func New(
return nil, fmt.Errorf("failed to create focus service: %w", err) return nil, fmt.Errorf("failed to create focus service: %w", err)
} }
bridge := &Bridge{ bridge := newBridge(
vault,
api,
cookieJar,
proxyCtl,
identifier,
tlsConfig,
imapServer,
smtpBackend,
updater,
curVersion,
focusService,
autostarter,
locator,
logIMAPClient,
logIMAPServer,
logSMTP,
)
if err := bridge.init(tlsReporter); err != nil {
return nil, fmt.Errorf("failed to initialize bridge: %w", err)
}
return bridge, nil
}
func newBridge(
vault *vault.Vault,
api *liteapi.Manager,
cookieJar *cookies.Jar,
proxyCtl ProxyController,
identifier Identifier,
tlsConfig *tls.Config,
imapServer *gluon.Server,
smtpBackend *smtpBackend,
updater Updater,
curVersion *semver.Version,
focusService *focus.Service,
autostarter Autostarter,
locator Locator,
logIMAPClient, logIMAPServer, logSMTP bool,
) *Bridge {
return &Bridge{
vault: vault, vault: vault,
users: make(map[string]*user.User), users: make(map[string]*user.User),
@ -154,7 +185,7 @@ func New(
tlsConfig: tlsConfig, tlsConfig: tlsConfig,
imapServer: imapServer, imapServer: imapServer,
smtpServer: smtpServer, smtpServer: newSMTPServer(smtpBackend, tlsConfig, logSMTP),
smtpBackend: smtpBackend, smtpBackend: smtpBackend,
updater: updater, updater: updater,
@ -165,14 +196,22 @@ func New(
autostarter: autostarter, autostarter: autostarter,
locator: locator, locator: locator,
stopCh: make(chan struct{}), logIMAPClient: logIMAPClient,
logIMAPServer: logIMAPServer,
logSMTP: logSMTP,
logIMAPClientCommands: logIMAPClientCommands, stopCh: make(chan struct{}),
logIMAPServerCommands: logIMAPServerCommands, }
logSMTPCommands: logSMTPCommands, }
func (bridge *Bridge) init(tlsReporter TLSReporter) error {
if bridge.vault.GetProxyAllowed() {
bridge.proxyCtl.AllowProxy()
} else {
bridge.proxyCtl.DisallowProxy()
} }
api.AddStatusObserver(func(status liteapi.Status) { bridge.api.AddStatusObserver(func(status liteapi.Status) {
switch { switch {
case status == liteapi.StatusUp: case status == liteapi.StatusUp:
go bridge.onStatusUp() go bridge.onStatusUp()
@ -182,17 +221,17 @@ func New(
} }
}) })
api.AddErrorHandler(liteapi.AppVersionBadCode, func() { bridge.api.AddErrorHandler(liteapi.AppVersionBadCode, func() {
bridge.publish(events.UpdateForced{}) bridge.publish(events.UpdateForced{})
}) })
api.AddPreRequestHook(func(_ *resty.Client, req *resty.Request) error { bridge.api.AddPreRequestHook(func(_ *resty.Client, req *resty.Request) error {
req.SetHeader("User-Agent", bridge.identifier.GetUserAgent()) req.SetHeader("User-Agent", bridge.identifier.GetUserAgent())
return nil return nil
}) })
if err := bridge.loadUsers(); err != nil { if err := bridge.loadUsers(); err != nil {
return nil, fmt.Errorf("failed to load users: %w", err) return fmt.Errorf("failed to load users: %w", err)
} }
go func() { go func() {
@ -202,13 +241,13 @@ func New(
}() }()
go func() { go func() {
for range focusService.GetRaiseCh() { for range bridge.focusService.GetRaiseCh() {
bridge.publish(events.Raise{}) bridge.publish(events.Raise{})
} }
}() }()
go func() { go func() {
for event := range imapServer.AddWatcher() { for event := range bridge.imapServer.AddWatcher() {
bridge.handleIMAPEvent(event) bridge.handleIMAPEvent(event)
} }
}() }()
@ -225,7 +264,7 @@ func New(
bridge.PushError(ErrWatchUpdates) bridge.PushError(ErrWatchUpdates)
} }
return bridge, nil return nil
} }
// GetEvents returns a channel of events of the given type. // GetEvents returns a channel of events of the given type.
@ -363,6 +402,7 @@ func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) {
return nil, err return nil, err
} }
// TODO: Do we have to set MinVersion to tls.VersionTLS12?
return &tls.Config{ return &tls.Config{
Certificates: []tls.Certificate{cert}, Certificates: []tls.Certificate{cert},
}, nil }, nil

View File

@ -45,7 +45,7 @@ func init() {
func TestBridge_ConnStatus(t *testing.T) { func TestBridge_ConnStatus(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Get a stream of connection status events. // Get a stream of connection status events.
eventCh, done := bridge.GetEvents(events.ConnStatusUp{}, events.ConnStatusDown{}) eventCh, done := bridge.GetEvents(events.ConnStatusUp{}, events.ConnStatusDown{})
defer done() defer done()
@ -76,7 +76,7 @@ func TestBridge_ConnStatus(t *testing.T) {
func TestBridge_TLSIssue(t *testing.T) { func TestBridge_TLSIssue(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Get a stream of TLS issue events. // Get a stream of TLS issue events.
tlsEventCh, done := bridge.GetEvents(events.TLSIssue{}) tlsEventCh, done := bridge.GetEvents(events.TLSIssue{})
defer done() defer done()
@ -94,7 +94,7 @@ func TestBridge_TLSIssue(t *testing.T) {
func TestBridge_Focus(t *testing.T) { func TestBridge_Focus(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Get a stream of TLS issue events. // Get a stream of TLS issue events.
raiseCh, done := bridge.GetEvents(events.Raise{}) raiseCh, done := bridge.GetEvents(events.Raise{})
defer done() defer done()
@ -116,7 +116,7 @@ func TestBridge_UserAgent(t *testing.T) {
calls = append(calls, call) calls = append(calls, call)
}) })
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Set the platform to something other than the default. // Set the platform to something other than the default.
bridge.SetCurrentPlatform("platform") bridge.SetCurrentPlatform("platform")
@ -148,13 +148,13 @@ func TestBridge_Cookies(t *testing.T) {
}) })
// Start bridge and add a user so that API assigns us a session ID via cookie. // Start bridge and add a user so that API assigns us a session ID via cookie.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
_, err := bridge.LoginUser(context.Background(), username, password, nil, nil) _, err := bridge.LoginUser(context.Background(), username, password, nil, nil)
require.NoError(t, err) require.NoError(t, err)
}) })
// Start bridge again and check that it uses the same session ID. // Start bridge again and check that it uses the same session ID.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// ... // ...
}) })
@ -167,7 +167,7 @@ func TestBridge_Cookies(t *testing.T) {
func TestBridge_CheckUpdate(t *testing.T) { func TestBridge_CheckUpdate(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Disable autoupdate for this test. // Disable autoupdate for this test.
require.NoError(t, bridge.SetAutoUpdate(false)) require.NoError(t, bridge.SetAutoUpdate(false))
@ -206,7 +206,7 @@ func TestBridge_CheckUpdate(t *testing.T) {
func TestBridge_AutoUpdate(t *testing.T) { func TestBridge_AutoUpdate(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Enable autoupdate for this test. // Enable autoupdate for this test.
require.NoError(t, bridge.SetAutoUpdate(true)) require.NoError(t, bridge.SetAutoUpdate(true))
@ -234,7 +234,7 @@ func TestBridge_AutoUpdate(t *testing.T) {
func TestBridge_ManualUpdate(t *testing.T) { func TestBridge_ManualUpdate(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Disable autoupdate for this test. // Disable autoupdate for this test.
require.NoError(t, bridge.SetAutoUpdate(false)) require.NoError(t, bridge.SetAutoUpdate(false))
@ -263,7 +263,7 @@ func TestBridge_ManualUpdate(t *testing.T) {
func TestBridge_ForceUpdate(t *testing.T) { func TestBridge_ForceUpdate(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Get a stream of update events. // Get a stream of update events.
updateCh, done := bridge.GetEvents(events.UpdateForced{}) updateCh, done := bridge.GetEvents(events.UpdateForced{})
defer done() defer done()
@ -286,7 +286,7 @@ func TestBridge_BadVaultKey(t *testing.T) {
var userID string var userID string
// Login a user. // Login a user.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
newUserID, err := bridge.LoginUser(context.Background(), username, password, nil, nil) newUserID, err := bridge.LoginUser(context.Background(), username, password, nil, nil)
require.NoError(t, err) require.NoError(t, err)
@ -294,17 +294,17 @@ func TestBridge_BadVaultKey(t *testing.T) {
}) })
// Start bridge with the correct vault key -- it should load the users correctly. // Start bridge with the correct vault key -- it should load the users correctly.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
require.ElementsMatch(t, []string{userID}, bridge.GetUserIDs()) require.ElementsMatch(t, []string{userID}, bridge.GetUserIDs())
}) })
// Start bridge with a bad vault key, the vault will be wiped and bridge will show no users. // Start bridge with a bad vault key, the vault will be wiped and bridge will show no users.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, []byte("bad"), func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, []byte("bad"), func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
require.Empty(t, bridge.GetUserIDs()) require.Empty(t, bridge.GetUserIDs())
}) })
// Start bridge with a nil vault key, the vault will be wiped and bridge will show no users. // Start bridge with a nil vault key, the vault will be wiped and bridge will show no users.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, nil, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, nil, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
require.Empty(t, bridge.GetUserIDs()) require.Empty(t, bridge.GetUserIDs())
}) })
}) })
@ -314,12 +314,12 @@ func TestBridge_MissingGluonDir(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) {
var gluonDir string var gluonDir string
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
_, err := bridge.LoginUser(context.Background(), username, password, nil, nil) _, err := bridge.LoginUser(context.Background(), username, password, nil, nil)
require.NoError(t, err) require.NoError(t, err)
// Move the gluon dir. // Move the gluon dir.
bridge.SetGluonDir(ctx, t.TempDir()) require.NoError(t, bridge.SetGluonDir(ctx, t.TempDir()))
// Get the gluon dir. // Get the gluon dir.
gluonDir = bridge.GetGluonDir() gluonDir = bridge.GetGluonDir()
@ -329,7 +329,7 @@ func TestBridge_MissingGluonDir(t *testing.T) {
require.NoError(t, os.RemoveAll(gluonDir)) require.NoError(t, os.RemoveAll(gluonDir))
// Bridge starts but can't find the gluon dir; there should be no error. // Bridge starts but can't find the gluon dir; there should be no error.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// ... // ...
}) })
}) })
@ -337,30 +337,12 @@ func TestBridge_MissingGluonDir(t *testing.T) {
// withTLSEnv creates the full test environment and runs the tests. // withTLSEnv creates the full test environment and runs the tests.
func withTLSEnv(t *testing.T, tests func(context.Context, *server.Server, *liteapi.NetCtl, bridge.Locator, []byte)) { func withTLSEnv(t *testing.T, tests func(context.Context, *server.Server, *liteapi.NetCtl, bridge.Locator, []byte)) {
// Create test API.
server := server.NewTLS() server := server.NewTLS()
defer server.Close() defer server.Close()
// Add test user. withEnv(t, server, func(ctx context.Context, netCtl *liteapi.NetCtl, locator bridge.Locator, vaultKey []byte) {
_, _, err := server.CreateUser(username, username+"@pm.me", password) tests(ctx, server, netCtl, locator, vaultKey)
require.NoError(t, err) })
// Generate a random vault key.
vaultKey, err := crypto.RandomToken(32)
require.NoError(t, err)
// Create a context used for the test.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create a net controller so we can simulate network connectivity issues.
netCtl := liteapi.NewNetCtl()
// Create a locations object to provide temporary locations for bridge data during the test.
locations := locations.New(bridge.NewTestLocationsProvider(t.TempDir()), "config-name")
// Run the tests.
tests(ctx, server, netCtl, locations, vaultKey)
} }
// withEnv creates the full test environment and runs the tests. // withEnv creates the full test environment and runs the tests.
@ -389,8 +371,8 @@ func withEnv(t *testing.T, server *server.Server, tests func(context.Context, *l
// withBridge creates a new bridge which points to the given API URL and uses the given keychain, and closes it when done. // withBridge creates a new bridge which points to the given API URL and uses the given keychain, and closes it when done.
func withBridge( func withBridge(
t *testing.T,
ctx context.Context, ctx context.Context,
t *testing.T,
apiURL string, apiURL string,
netCtl *liteapi.NetCtl, netCtl *liteapi.NetCtl,
locator bridge.Locator, locator bridge.Locator,
@ -435,7 +417,7 @@ func withBridge(
require.NoError(t, err) require.NoError(t, err)
// Close the bridge when done. // Close the bridge when done.
defer bridge.Close(ctx) defer func() { require.NoError(t, bridge.Close(ctx)) }()
// Use the bridge. // Use the bridge.
tests(bridge, mocks) tests(bridge, mocks)

View File

@ -31,7 +31,7 @@ import (
) )
const ( const (
MaxAttachmentSize = 7 * (1 << 20) // MaxAttachmentSize 7 MB total size of all attachments. MaxTotalAttachmentSize = 7 * (1 << 20)
MaxCompressedFilesCount = 6 MaxCompressedFilesCount = 6
) )
@ -166,7 +166,7 @@ func zipFiles(filenames []string) (io.Reader, error) {
return nil, nil return nil, nil
} }
buf := newLimitedBuffer(MaxAttachmentSize) buf := newLimitedBuffer(MaxTotalAttachmentSize)
w := zip.NewWriter(buf) w := zip.NewWriter(buf)
defer w.Close() //nolint:errcheck defer w.Close() //nolint:errcheck

View File

@ -39,17 +39,17 @@ func move(from, to string) error {
return err return err
} }
f, err := os.Open(from) f, err := os.Open(from) // nolint:gosec
if err != nil { if err != nil {
return err return err
} }
defer f.Close() defer func() { _ = f.Close() }()
c, err := os.Create(to) c, err := os.Create(to) // nolint:gosec
if err != nil { if err != nil {
return err return err
} }
defer c.Close() defer func() { _ = f.Close() }()
if err := os.Chmod(to, 0600); err != nil { if err := os.Chmod(to, 0600); err != nil {
return err return err

View File

@ -63,7 +63,7 @@ func (bridge *Bridge) serveIMAP() error {
return nil return nil
} }
func (bridge *Bridge) restartIMAP(ctx context.Context) error { func (bridge *Bridge) restartIMAP() error {
if err := bridge.imapListener.Close(); err != nil { if err := bridge.imapListener.Close(); err != nil {
logrus.WithError(err).Warn("Failed to close IMAP listener") logrus.WithError(err).Warn("Failed to close IMAP listener")
} }
@ -73,11 +73,11 @@ func (bridge *Bridge) restartIMAP(ctx context.Context) error {
func (bridge *Bridge) closeIMAP(ctx context.Context) error { func (bridge *Bridge) closeIMAP(ctx context.Context) error {
if err := bridge.imapServer.Close(ctx); err != nil { if err := bridge.imapServer.Close(ctx); err != nil {
logrus.WithError(err).Warn("Failed to close IMAP server") return fmt.Errorf("failed to close IMAP server: %w", err)
} }
if err := bridge.imapListener.Close(); err != nil { if err := bridge.imapListener.Close(); err != nil {
logrus.WithError(err).Warn("Failed to close IMAP listener") return fmt.Errorf("failed to close IMAP listener: %w", err)
} }
return nil return nil
@ -98,11 +98,18 @@ func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
} }
func getGluonDir(encVault *vault.Vault) (string, error) { func getGluonDir(encVault *vault.Vault) (string, error) {
empty, err := isEmpty(encVault.GetGluonDir()) empty, exists, err := isEmpty(encVault.GetGluonDir())
if err != nil { if err != nil {
return "", fmt.Errorf("failed to check if gluon dir is empty: %w", err) return "", fmt.Errorf("failed to check if gluon dir is empty: %w", err)
} }
// TODO: Handle case that the gluon directory is missing and we can't create it!
if !exists {
if err := os.MkdirAll(encVault.GetGluonDir(), 0700); err != nil {
return "", fmt.Errorf("failed to create gluon dir: %w", err)
}
}
if empty { if empty {
if err := encVault.ForUser(func(user *vault.User) error { if err := encVault.ForUser(func(user *vault.User) error {
return user.ClearSyncStatus() return user.ClearSyncStatus()
@ -114,27 +121,33 @@ func getGluonDir(encVault *vault.Vault) (string, error) {
return encVault.GetGluonDir(), nil return encVault.GetGluonDir(), nil
} }
func newIMAPServer(gluonDir string, version *semver.Version, tlsConfig *tls.Config, logIMAPCommandsClient, logIMAPCommandsServer bool) (*gluon.Server, error) { func newIMAPServer(
var imapClientLog io.Writer gluonDir string,
var imapServerLog io.Writer version *semver.Version,
tlsConfig *tls.Config,
if logIMAPCommandsClient || logIMAPCommandsServer { logClient, logServer bool,
) (*gluon.Server, error) {
if logClient || logServer {
log := logrus.WithField("protocol", "IMAP") log := logrus.WithField("protocol", "IMAP")
log.Warning("================================================") log.Warning("================================================")
log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA") log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
log.Warning("================================================") log.Warning("================================================")
} }
if logIMAPCommandsClient { var imapClientLog io.Writer
if logClient {
imapClientLog = logging.NewIMAPLogger() imapClientLog = logging.NewIMAPLogger()
} else { } else {
imapClientLog = io.Discard imapClientLog = io.Discard
} }
if logIMAPCommandsServer { var imapServerLog io.Writer
if logServer {
imapServerLog = logging.NewIMAPLogger() imapServerLog = logging.NewIMAPLogger()
} else { } else {
imapClientLog = io.Discard imapServerLog = io.Discard
} }
imapServer, err := gluon.New( imapServer, err := gluon.New(
@ -160,19 +173,21 @@ func newIMAPServer(gluonDir string, version *semver.Version, tlsConfig *tls.Conf
return imapServer, nil return imapServer, nil
} }
func isEmpty(dir string) (bool, error) { // isEmpty returns whether the given directory is empty.
// If the directory does not exist, the second return value is false.
func isEmpty(dir string) (bool, bool, error) {
if _, err := os.Stat(dir); err != nil { if _, err := os.Stat(dir); err != nil {
if !errors.Is(err, fs.ErrNotExist) { if !errors.Is(err, fs.ErrNotExist) {
return false, fmt.Errorf("failed to stat %s: %w", dir, err) return false, false, fmt.Errorf("failed to stat %s: %w", dir, err)
} }
return true, nil return true, false, nil
} }
entries, err := os.ReadDir(dir) entries, err := os.ReadDir(dir)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to read dir %s: %w", dir, err) return false, false, fmt.Errorf("failed to read dir %s: %w", dir, err)
} }
return len(entries) == 0, nil return len(entries) == 0, true, nil
} }

View File

@ -94,10 +94,10 @@ func (testUpdater *TestUpdater) SetLatestVersion(version, minAuto *semver.Versio
} }
} }
func (updater *TestUpdater) GetVersionInfo(downloader updater.Downloader, channel updater.Channel) (updater.VersionInfo, error) { func (testUpdater *TestUpdater) GetVersionInfo(downloader updater.Downloader, channel updater.Channel) (updater.VersionInfo, error) {
return updater.latest, nil return testUpdater.latest, nil
} }
func (updater *TestUpdater) InstallUpdate(downloader updater.Downloader, update updater.VersionInfo) error { func (testUpdater *TestUpdater) InstallUpdate(downloader updater.Downloader, update updater.VersionInfo) error {
return nil return nil
} }

View File

@ -40,7 +40,7 @@ func (bridge *Bridge) SetIMAPPort(newPort int) error {
return err return err
} }
return bridge.restartIMAP(context.Background()) return bridge.restartIMAP()
} }
func (bridge *Bridge) GetIMAPSSL() bool { func (bridge *Bridge) GetIMAPSSL() bool {
@ -56,7 +56,7 @@ func (bridge *Bridge) SetIMAPSSL(newSSL bool) error {
return err return err
} }
return bridge.restartIMAP(context.Background()) return bridge.restartIMAP()
} }
func (bridge *Bridge) GetSMTPPort() int { func (bridge *Bridge) GetSMTPPort() int {
@ -112,7 +112,7 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error
return fmt.Errorf("failed to set new gluon dir: %w", err) return fmt.Errorf("failed to set new gluon dir: %w", err)
} }
imapServer, err := newIMAPServer(bridge.vault.GetGluonDir(), bridge.curVersion, bridge.tlsConfig, bridge.logIMAPClientCommands, bridge.logIMAPServerCommands) imapServer, err := newIMAPServer(bridge.vault.GetGluonDir(), bridge.curVersion, bridge.tlsConfig, bridge.logIMAPClient, bridge.logIMAPServer)
if err != nil { if err != nil {
return fmt.Errorf("failed to create new IMAP server: %w", err) return fmt.Errorf("failed to create new IMAP server: %w", err)
} }
@ -193,7 +193,7 @@ func (bridge *Bridge) SetAutoUpdate(autoUpdate bool) error {
} }
func (bridge *Bridge) GetUpdateChannel() updater.Channel { func (bridge *Bridge) GetUpdateChannel() updater.Channel {
return updater.Channel(bridge.vault.GetUpdateChannel()) return bridge.vault.GetUpdateChannel()
} }
func (bridge *Bridge) SetUpdateChannel(channel updater.Channel) error { func (bridge *Bridge) SetUpdateChannel(channel updater.Channel) error {

View File

@ -13,7 +13,7 @@ import (
func TestBridge_Settings_GluonDir(t *testing.T) { func TestBridge_Settings_GluonDir(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Create a user. // Create a user.
_, err := bridge.LoginUser(context.Background(), username, password, nil, nil) _, err := bridge.LoginUser(context.Background(), username, password, nil, nil)
require.NoError(t, err) require.NoError(t, err)
@ -36,7 +36,7 @@ func TestBridge_Settings_GluonDir(t *testing.T) {
func TestBridge_Settings_IMAPPort(t *testing.T) { func TestBridge_Settings_IMAPPort(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
curPort := bridge.GetIMAPPort() curPort := bridge.GetIMAPPort()
// Set the port to 1144. // Set the port to 1144.
@ -53,7 +53,7 @@ func TestBridge_Settings_IMAPPort(t *testing.T) {
func TestBridge_Settings_IMAPSSL(t *testing.T) { func TestBridge_Settings_IMAPSSL(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// By default, IMAP SSL is disabled. // By default, IMAP SSL is disabled.
require.False(t, bridge.GetIMAPSSL()) require.False(t, bridge.GetIMAPSSL())
@ -68,7 +68,7 @@ func TestBridge_Settings_IMAPSSL(t *testing.T) {
func TestBridge_Settings_SMTPPort(t *testing.T) { func TestBridge_Settings_SMTPPort(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
curPort := bridge.GetSMTPPort() curPort := bridge.GetSMTPPort()
// Set the port to 1024. // Set the port to 1024.
@ -79,14 +79,13 @@ func TestBridge_Settings_SMTPPort(t *testing.T) {
// Assert that it has changed. // Assert that it has changed.
require.NotEqual(t, curPort, bridge.GetSMTPPort()) require.NotEqual(t, curPort, bridge.GetSMTPPort())
}) })
}) })
} }
func TestBridge_Settings_SMTPSSL(t *testing.T) { func TestBridge_Settings_SMTPSSL(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// By default, SMTP SSL is disabled. // By default, SMTP SSL is disabled.
require.False(t, bridge.GetSMTPSSL()) require.False(t, bridge.GetSMTPSSL())
@ -101,7 +100,7 @@ func TestBridge_Settings_SMTPSSL(t *testing.T) {
func TestBridge_Settings_Proxy(t *testing.T) { func TestBridge_Settings_Proxy(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// By default, proxy is allowed. // By default, proxy is allowed.
require.True(t, bridge.GetProxyAllowed()) require.True(t, bridge.GetProxyAllowed())
@ -117,7 +116,7 @@ func TestBridge_Settings_Proxy(t *testing.T) {
func TestBridge_Settings_Autostart(t *testing.T) { func TestBridge_Settings_Autostart(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// By default, autostart is disabled. // By default, autostart is disabled.
require.False(t, bridge.GetAutostart()) require.False(t, bridge.GetAutostart())
@ -133,7 +132,7 @@ func TestBridge_Settings_Autostart(t *testing.T) {
func TestBridge_Settings_FirstStart(t *testing.T) { func TestBridge_Settings_FirstStart(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// By default, first start is true. // By default, first start is true.
require.True(t, bridge.GetFirstStart()) require.True(t, bridge.GetFirstStart())
@ -148,7 +147,7 @@ func TestBridge_Settings_FirstStart(t *testing.T) {
func TestBridge_Settings_FirstStartGUI(t *testing.T) { func TestBridge_Settings_FirstStartGUI(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// By default, first start is true. // By default, first start is true.
require.True(t, bridge.GetFirstStartGUI()) require.True(t, bridge.GetFirstStartGUI())

View File

@ -3,10 +3,11 @@ package bridge
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"github.com/ProtonMail/proton-bridge/v2/internal/logging"
"net" "net"
"strconv" "strconv"
"github.com/ProtonMail/proton-bridge/v2/internal/logging"
"github.com/ProtonMail/proton-bridge/v2/internal/constants" "github.com/ProtonMail/proton-bridge/v2/internal/constants"
"github.com/emersion/go-sasl" "github.com/emersion/go-sasl"
"github.com/emersion/go-smtp" "github.com/emersion/go-smtp"
@ -49,12 +50,7 @@ func (bridge *Bridge) restartSMTP() error {
return err return err
} }
smtpServer, err := newSMTPServer(bridge.smtpBackend, bridge.tlsConfig, bridge.logSMTPCommands) bridge.smtpServer = newSMTPServer(bridge.smtpBackend, bridge.tlsConfig, bridge.logSMTP)
if err != nil {
return err
}
bridge.smtpServer = smtpServer
return bridge.serveSMTP() return bridge.serveSMTP()
} }
@ -69,7 +65,7 @@ func (bridge *Bridge) closeSMTP() error {
return nil return nil
} }
func newSMTPServer(smtpBackend *smtpBackend, tlsConfig *tls.Config, shouldLog bool) (*smtp.Server, error) { func newSMTPServer(smtpBackend *smtpBackend, tlsConfig *tls.Config, shouldLog bool) *smtp.Server {
smtpServer := smtp.NewServer(smtpBackend) smtpServer := smtp.NewServer(smtpBackend)
smtpServer.TLSConfig = tlsConfig smtpServer.TLSConfig = tlsConfig
@ -99,5 +95,5 @@ func newSMTPServer(smtpBackend *smtpBackend, tlsConfig *tls.Config, shouldLog bo
}) })
}) })
return smtpServer, nil return smtpServer
} }

View File

@ -44,18 +44,18 @@ func TestBridge_Sync(t *testing.T) {
}) })
// The initial user should be fully synced. // The initial user should be fully synced.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
syncCh, done := bridge.GetEvents(events.SyncFinished{}) syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
defer done() defer done()
userID, err := bridge.LoginUser(ctx, "imap", password, nil, nil) userID, err := bridge.LoginUser(ctx, "imap", password, nil, nil)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, userID, (<-syncCh).(events.SyncFinished).UserID) require.Equal(t, userID, (<-syncCh).UserID)
}) })
// If we then connect an IMAP client, it should see all the messages. // If we then connect an IMAP client, it should see all the messages.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
info, err := bridge.GetUserInfo(userID) info, err := bridge.GetUserInfo(userID)
require.NoError(t, err) require.NoError(t, err)
require.True(t, info.Connected) require.True(t, info.Connected)
@ -63,7 +63,7 @@ func TestBridge_Sync(t *testing.T) {
client, err := client.Dial(fmt.Sprintf(":%v", bridge.GetIMAPPort())) client, err := client.Dial(fmt.Sprintf(":%v", bridge.GetIMAPPort()))
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass))) require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass)))
defer client.Logout() defer func() { _ = client.Logout() }()
status, err := client.Select(`Folders/folder`, false) status, err := client.Select(`Folders/folder`, false)
require.NoError(t, err) require.NoError(t, err)
@ -71,7 +71,7 @@ func TestBridge_Sync(t *testing.T) {
}) })
// Now let's remove the user and simulate a network error. // Now let's remove the user and simulate a network error.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
require.NoError(t, bridge.DeleteUser(ctx, userID)) require.NoError(t, bridge.DeleteUser(ctx, userID))
}) })
@ -79,14 +79,14 @@ func TestBridge_Sync(t *testing.T) {
netCtl.SetReadLimit(2 * read / 3) netCtl.SetReadLimit(2 * read / 3)
// Login the user; its sync should fail. // Login the user; its sync should fail.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
syncCh, done := bridge.GetEvents(events.SyncFailed{}) syncCh, done := chToType[events.Event, events.SyncFailed](bridge.GetEvents(events.SyncFailed{}))
defer done() defer done()
userID, err := bridge.LoginUser(ctx, "imap", password, nil, nil) userID, err := bridge.LoginUser(ctx, "imap", password, nil, nil)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, userID, (<-syncCh).(events.SyncFailed).UserID) require.Equal(t, userID, (<-syncCh).UserID)
info, err := bridge.GetUserInfo(userID) info, err := bridge.GetUserInfo(userID)
require.NoError(t, err) require.NoError(t, err)
@ -95,7 +95,7 @@ func TestBridge_Sync(t *testing.T) {
client, err := client.Dial(fmt.Sprintf(":%v", bridge.GetIMAPPort())) client, err := client.Dial(fmt.Sprintf(":%v", bridge.GetIMAPPort()))
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass))) require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass)))
defer client.Logout() defer func() { _ = client.Logout() }()
status, err := client.Select(`Folders/folder`, false) status, err := client.Select(`Folders/folder`, false)
require.NoError(t, err) require.NoError(t, err)
@ -107,11 +107,11 @@ func TestBridge_Sync(t *testing.T) {
// Login the user; its sync should now finish. // Login the user; its sync should now finish.
// If we then connect an IMAP client, it should eventually see all the messages. // If we then connect an IMAP client, it should eventually see all the messages.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
syncCh, done := bridge.GetEvents(events.SyncFinished{}) syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
defer done() defer done()
require.Equal(t, userID, (<-syncCh).(events.SyncFinished).UserID) require.Equal(t, userID, (<-syncCh).UserID)
info, err := bridge.GetUserInfo(userID) info, err := bridge.GetUserInfo(userID)
require.NoError(t, err) require.NoError(t, err)
@ -120,7 +120,7 @@ func TestBridge_Sync(t *testing.T) {
client, err := client.Dial(fmt.Sprintf(":%v", bridge.GetIMAPPort())) client, err := client.Dial(fmt.Sprintf(":%v", bridge.GetIMAPPort()))
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass))) require.NoError(t, client.Login("imap@pm.me", string(info.BridgePass)))
defer client.Logout() defer func() { _ = client.Logout() }()
status, err := client.Select(`Folders/folder`, false) status, err := client.Select(`Folders/folder`, false)
require.NoError(t, err) require.NoError(t, err)
@ -128,3 +128,22 @@ func TestBridge_Sync(t *testing.T) {
}) })
}) })
} }
func chToType[In, Out any](inCh <-chan In, done func()) (<-chan Out, func()) {
outCh := make(chan Out)
go func() {
defer close(outCh)
for in := range inCh {
out, ok := any(in).(Out)
if !ok {
panic(fmt.Sprintf("unexpected type %T", in))
}
outCh <- out
}
}()
return outCh, done
}

View File

@ -13,6 +13,10 @@ func (bridge *Bridge) CheckForUpdates() {
} }
func (bridge *Bridge) watchForUpdates() error { func (bridge *Bridge) watchForUpdates() error {
if _, err := bridge.updater.GetVersionInfo(bridge.api, bridge.vault.GetUpdateChannel()); err != nil {
return err
}
ticker := time.NewTicker(constants.UpdateCheckInterval) ticker := time.NewTicker(constants.UpdateCheckInterval)
go func() { go func() {
@ -22,7 +26,10 @@ func (bridge *Bridge) watchForUpdates() error {
return return
case <-bridge.updateCheckCh: case <-bridge.updateCheckCh:
// ...
case <-ticker.C: case <-ticker.C:
// ...
} }
version, err := bridge.updater.GetVersionInfo(bridge.api, bridge.vault.GetUpdateChannel()) version, err := bridge.updater.GetVersionInfo(bridge.api, bridge.vault.GetUpdateChannel())

View File

@ -73,7 +73,7 @@ func (bridge *Bridge) handleUserAddressCreated(ctx context.Context, user *user.U
} }
// TODO: Handle addresses that have been disabled! // TODO: Handle addresses that have been disabled!
func (bridge *Bridge) handleUserAddressUpdated(ctx context.Context, user *user.User, event events.UserAddressUpdated) error { func (bridge *Bridge) handleUserAddressUpdated(_ context.Context, user *user.User, _ events.UserAddressUpdated) error {
switch user.GetAddressMode() { switch user.GetAddressMode() {
case vault.CombinedMode: case vault.CombinedMode:
return fmt.Errorf("not implemented") return fmt.Errorf("not implemented")

View File

@ -15,12 +15,12 @@ import (
func TestBridge_WithoutUsers(t *testing.T) { func TestBridge_WithoutUsers(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
require.Empty(t, bridge.GetUserIDs()) require.Empty(t, bridge.GetUserIDs())
require.Empty(t, getConnectedUserIDs(t, bridge)) require.Empty(t, getConnectedUserIDs(t, bridge))
}) })
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
require.Empty(t, bridge.GetUserIDs()) require.Empty(t, bridge.GetUserIDs())
require.Empty(t, getConnectedUserIDs(t, bridge)) require.Empty(t, getConnectedUserIDs(t, bridge))
}) })
@ -29,7 +29,7 @@ func TestBridge_WithoutUsers(t *testing.T) {
func TestBridge_Login(t *testing.T) { func TestBridge_Login(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Login the user. // Login the user.
userID, err := bridge.LoginUser(ctx, username, password, nil, nil) userID, err := bridge.LoginUser(ctx, username, password, nil, nil)
require.NoError(t, err) require.NoError(t, err)
@ -43,7 +43,7 @@ func TestBridge_Login(t *testing.T) {
func TestBridge_LoginLogoutLogin(t *testing.T) { func TestBridge_LoginLogoutLogin(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Login the user. // Login the user.
userID := must(bridge.LoginUser(ctx, username, password, nil, nil)) userID := must(bridge.LoginUser(ctx, username, password, nil, nil))
@ -71,7 +71,7 @@ func TestBridge_LoginLogoutLogin(t *testing.T) {
func TestBridge_LoginDeleteLogin(t *testing.T) { func TestBridge_LoginDeleteLogin(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Login the user. // Login the user.
userID := must(bridge.LoginUser(ctx, username, password, nil, nil)) userID := must(bridge.LoginUser(ctx, username, password, nil, nil))
@ -99,7 +99,7 @@ func TestBridge_LoginDeleteLogin(t *testing.T) {
func TestBridge_LoginDeauthLogin(t *testing.T) { func TestBridge_LoginDeauthLogin(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Login the user. // Login the user.
userID := must(bridge.LoginUser(ctx, username, password, nil, nil)) userID := must(bridge.LoginUser(ctx, username, password, nil, nil))
@ -135,7 +135,7 @@ func TestBridge_LoginExpireLogin(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
s.SetAuthLife(authLife) s.SetAuthLife(authLife)
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Login the user. Its auth will only be valid for a short time. // Login the user. Its auth will only be valid for a short time.
userID := must(bridge.LoginUser(ctx, username, password, nil, nil)) userID := must(bridge.LoginUser(ctx, username, password, nil, nil))
@ -153,7 +153,7 @@ func TestBridge_FailToLoad(t *testing.T) {
var userID string var userID string
// Login the user. // Login the user.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userID = must(bridge.LoginUser(ctx, username, password, nil, nil)) userID = must(bridge.LoginUser(ctx, username, password, nil, nil))
}) })
@ -161,7 +161,7 @@ func TestBridge_FailToLoad(t *testing.T) {
require.NoError(t, s.RevokeUser(userID)) require.NoError(t, s.RevokeUser(userID))
// When bridge starts, the user will not be logged in. // When bridge starts, the user will not be logged in.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
require.Equal(t, []string{userID}, bridge.GetUserIDs()) require.Equal(t, []string{userID}, bridge.GetUserIDs())
require.Empty(t, getConnectedUserIDs(t, bridge)) require.Empty(t, getConnectedUserIDs(t, bridge))
}) })
@ -173,7 +173,7 @@ func TestBridge_LoadWithoutInternet(t *testing.T) {
var userID string var userID string
// Login the user. // Login the user.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userID = must(bridge.LoginUser(ctx, username, password, nil, nil)) userID = must(bridge.LoginUser(ctx, username, password, nil, nil))
}) })
@ -181,7 +181,7 @@ func TestBridge_LoadWithoutInternet(t *testing.T) {
netCtl.Disable() netCtl.Disable()
// Start bridge without internet. // Start bridge without internet.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Initially, users are not connected. // Initially, users are not connected.
require.Equal(t, []string{userID}, bridge.GetUserIDs()) require.Equal(t, []string{userID}, bridge.GetUserIDs())
require.Empty(t, getConnectedUserIDs(t, bridge)) require.Empty(t, getConnectedUserIDs(t, bridge))
@ -203,11 +203,11 @@ func TestBridge_LoginRestart(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
var userID string var userID string
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userID = must(bridge.LoginUser(ctx, username, password, nil, nil)) userID = must(bridge.LoginUser(ctx, username, password, nil, nil))
}) })
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
require.Equal(t, []string{userID}, bridge.GetUserIDs()) require.Equal(t, []string{userID}, bridge.GetUserIDs())
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge)) require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
}) })
@ -218,7 +218,7 @@ func TestBridge_LoginLogoutRestart(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
var userID string var userID string
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Login the user. // Login the user.
userID = must(bridge.LoginUser(ctx, username, password, nil, nil)) userID = must(bridge.LoginUser(ctx, username, password, nil, nil))
@ -226,7 +226,7 @@ func TestBridge_LoginLogoutRestart(t *testing.T) {
require.NoError(t, bridge.LogoutUser(ctx, userID)) require.NoError(t, bridge.LogoutUser(ctx, userID))
}) })
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// The user is still disconnected. // The user is still disconnected.
require.Equal(t, []string{userID}, bridge.GetUserIDs()) require.Equal(t, []string{userID}, bridge.GetUserIDs())
require.Empty(t, getConnectedUserIDs(t, bridge)) require.Empty(t, getConnectedUserIDs(t, bridge))
@ -238,7 +238,7 @@ func TestBridge_LoginDeleteRestart(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
var userID string var userID string
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Login the user. // Login the user.
userID = must(bridge.LoginUser(ctx, username, password, nil, nil)) userID = must(bridge.LoginUser(ctx, username, password, nil, nil))
@ -246,7 +246,7 @@ func TestBridge_LoginDeleteRestart(t *testing.T) {
require.NoError(t, bridge.DeleteUser(ctx, userID)) require.NoError(t, bridge.DeleteUser(ctx, userID))
}) })
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// The user is still gone. // The user is still gone.
require.Empty(t, bridge.GetUserIDs()) require.Empty(t, bridge.GetUserIDs())
require.Empty(t, getConnectedUserIDs(t, bridge)) require.Empty(t, getConnectedUserIDs(t, bridge))
@ -263,7 +263,7 @@ func TestBridge_FailLoginRecover(t *testing.T) {
}) })
// Log the user in and record how much data was read. // Log the user in and record how much data was read.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userID := must(bridge.LoginUser(ctx, username, password, nil, nil)) userID := must(bridge.LoginUser(ctx, username, password, nil, nil))
require.NoError(t, bridge.LogoutUser(ctx, userID)) require.NoError(t, bridge.LogoutUser(ctx, userID))
}) })
@ -272,7 +272,7 @@ func TestBridge_FailLoginRecover(t *testing.T) {
netCtl.SetReadLimit(read / 2) netCtl.SetReadLimit(read / 2)
// We should fail to log the user in because we can't fully read its data. // We should fail to log the user in because we can't fully read its data.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
require.Error(t, getErr(bridge.LoginUser(ctx, username, password, nil, nil))) require.Error(t, getErr(bridge.LoginUser(ctx, username, password, nil, nil)))
// There should be no users. // There should be no users.
@ -283,7 +283,7 @@ func TestBridge_FailLoginRecover(t *testing.T) {
func TestBridge_FailLoadRecover(t *testing.T) { func TestBridge_FailLoadRecover(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
must(bridge.LoginUser(ctx, username, password, nil, nil)) must(bridge.LoginUser(ctx, username, password, nil, nil))
}) })
@ -294,7 +294,7 @@ func TestBridge_FailLoadRecover(t *testing.T) {
}) })
// Start bridge and record how much data was read. // Start bridge and record how much data was read.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// ... // ...
}) })
@ -302,7 +302,7 @@ func TestBridge_FailLoadRecover(t *testing.T) {
netCtl.SetReadLimit(read / 2) netCtl.SetReadLimit(read / 2)
// We should fail to load the user; it should be disconnected. // We should fail to load the user; it should be disconnected.
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
userIDs := bridge.GetUserIDs() userIDs := bridge.GetUserIDs()
require.False(t, must(bridge.GetUserInfo(userIDs[0])).Connected) require.False(t, must(bridge.GetUserInfo(userIDs[0])).Connected)
@ -316,7 +316,7 @@ func TestBridge_BridgePass(t *testing.T) {
var pass []byte var pass []byte
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Login the user. // Login the user.
userID = must(bridge.LoginUser(ctx, username, password, nil, nil)) userID = must(bridge.LoginUser(ctx, username, password, nil, nil))
@ -333,7 +333,7 @@ func TestBridge_BridgePass(t *testing.T) {
require.Equal(t, pass, pass) require.Equal(t, pass, pass)
}) })
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// The bridge should load the user. // The bridge should load the user.
require.Equal(t, []string{userID}, bridge.GetUserIDs()) require.Equal(t, []string{userID}, bridge.GetUserIDs())
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge)) require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
@ -346,7 +346,7 @@ func TestBridge_BridgePass(t *testing.T) {
func TestBridge_AddressMode(t *testing.T) { func TestBridge_AddressMode(t *testing.T) {
withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) { withTLSEnv(t, func(ctx context.Context, s *server.Server, netCtl *liteapi.NetCtl, locator bridge.Locator, storeKey []byte) {
withBridge(t, ctx, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// Login the user. // Login the user.
userID, err := bridge.LoginUser(ctx, username, password, nil, nil) userID, err := bridge.LoginUser(ctx, username, password, nil, nil)
require.NoError(t, err) require.NoError(t, err)

View File

@ -1,3 +1,4 @@
// Package proto provides the gRPC definition of the focus service.
package proto package proto
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative focus.proto //go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative focus.proto

View File

@ -1,3 +1,4 @@
// Package focus provides a gRPC service for raising the application.
package focus package focus
import ( import (
@ -14,10 +15,10 @@ import (
const Host = "127.0.0.1" const Host = "127.0.0.1"
// Port is the port to listen on. // Port is the port to listen on.
var Port = 1042 var Port = 1042 // nolint:gochecknoglobals
// FocusService is a gRPC service that can be used to raise the application. // Service is a gRPC service that can be used to raise the application.
type FocusService struct { type Service struct {
proto.UnimplementedFocusServer proto.UnimplementedFocusServer
server *grpc.Server server *grpc.Server
@ -27,13 +28,13 @@ type FocusService struct {
// NewService creates a new focus service. // NewService creates a new focus service.
// It listens on the local host and port 1042 (by default). // It listens on the local host and port 1042 (by default).
func NewService() (*FocusService, error) { func NewService() (*Service, error) {
listener, err := net.Listen("tcp", net.JoinHostPort(Host, fmt.Sprint(Port))) listener, err := net.Listen("tcp", net.JoinHostPort(Host, fmt.Sprint(Port)))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to listen: %w", err) return nil, fmt.Errorf("failed to listen: %w", err)
} }
service := &FocusService{ service := &Service{
server: grpc.NewServer(), server: grpc.NewServer(),
listener: listener, listener: listener,
raiseCh: make(chan struct{}, 1), raiseCh: make(chan struct{}, 1),
@ -51,18 +52,18 @@ func NewService() (*FocusService, error) {
} }
// Raise implements the gRPC FocusService interface; it raises the application. // Raise implements the gRPC FocusService interface; it raises the application.
func (service *FocusService) Raise(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { func (service *Service) Raise(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
service.raiseCh <- struct{}{} service.raiseCh <- struct{}{}
return &emptypb.Empty{}, nil return &emptypb.Empty{}, nil
} }
// GetRaiseCh returns a channel on which events are sent when the application should be raised. // GetRaiseCh returns a channel on which events are sent when the application should be raised.
func (service *FocusService) GetRaiseCh() <-chan struct{} { func (service *Service) GetRaiseCh() <-chan struct{} {
return service.raiseCh return service.raiseCh
} }
// Close closes the service. // Close closes the service.
func (service *FocusService) Close() { func (service *Service) Close() {
service.server.Stop() service.server.Stop()
close(service.raiseCh) close(service.raiseCh)
} }

View File

@ -2,46 +2,46 @@ package safe
import "sync" import "sync"
type Type[T any] struct { type Value[T any] struct {
data T data T
lock sync.RWMutex lock sync.RWMutex
} }
func NewType[T any](data T) *Type[T] { func NewValue[T any](data T) *Value[T] {
return &Type[T]{ return &Value[T]{
data: data, data: data,
} }
} }
func (s *Type[T]) Get(fn func(data T)) { func (s *Value[T]) Get(fn func(data T)) {
s.lock.RLock() s.lock.RLock()
defer s.lock.RUnlock() defer s.lock.RUnlock()
fn(s.data) fn(s.data)
} }
func (s *Type[T]) GetErr(fn func(data T) error) error { func (s *Value[T]) GetErr(fn func(data T) error) error {
s.lock.RLock() s.lock.RLock()
defer s.lock.RUnlock() defer s.lock.RUnlock()
return fn(s.data) return fn(s.data)
} }
func (s *Type[T]) Set(data T) { func (s *Value[T]) Set(data T) {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()
s.data = data s.data = data
} }
func GetType[T, Ret any](s *Type[T], fn func(data T) Ret) Ret { func GetType[T, Ret any](s *Value[T], fn func(data T) Ret) Ret {
s.lock.RLock() s.lock.RLock()
defer s.lock.RUnlock() defer s.lock.RUnlock()
return fn(s.data) return fn(s.data)
} }
func GetTypeErr[T, Ret any](s *Type[T], fn func(data T) (Ret, error)) (Ret, error) { func GetTypeErr[T, Ret any](s *Value[T], fn func(data T) (Ret, error)) (Ret, error) {
s.lock.RLock() s.lock.RLock()
defer s.lock.RUnlock() defer s.lock.RUnlock()

View File

@ -14,9 +14,9 @@ import (
) )
var ( var (
defaultFlags = imap.NewFlagSet(imap.FlagSeen, imap.FlagFlagged, imap.FlagDeleted) defaultFlags = imap.NewFlagSet(imap.FlagSeen, imap.FlagFlagged, imap.FlagDeleted) // nolint:gochecknoglobals
defaultPermanentFlags = imap.NewFlagSet(imap.FlagSeen, imap.FlagFlagged, imap.FlagDeleted) defaultPermanentFlags = imap.NewFlagSet(imap.FlagSeen, imap.FlagFlagged, imap.FlagDeleted) // nolint:gochecknoglobals
defaultAttributes = imap.NewFlagSet() defaultAttributes = imap.NewFlagSet() // nolint:gochecknoglobals
) )
const ( const (

View File

@ -75,7 +75,6 @@ func (session *smtpSession) Mail(from string, opts smtp.MailOptions) error {
logrus.Info("SMTP session mail") logrus.Info("SMTP session mail")
return session.apiAddrs.GetErr(func(apiAddrs []liteapi.Address) error { return session.apiAddrs.GetErr(func(apiAddrs []liteapi.Address) error {
switch { switch {
case opts.RequireTLS: case opts.RequireTLS:
return ErrNotImplemented return ErrNotImplemented

View File

@ -1,12 +1,20 @@
package user package user
import "reflect" import (
"fmt"
"reflect"
)
func mapTo[From, To any](from []From) []To { func mapTo[From, To any](from []From) []To {
to := make([]To, 0, len(from)) to := make([]To, 0, len(from))
for _, from := range from { for _, from := range from {
to = append(to, reflect.ValueOf(from).Convert(reflect.TypeOf(to).Elem()).Interface().(To)) val, ok := reflect.ValueOf(from).Convert(reflect.TypeOf(to).Elem()).Interface().(To)
if !ok {
panic(fmt.Sprintf("cannot convert %T to %T", from, *new(To)))
}
to = append(to, val)
} }
return to return to

View File

@ -22,8 +22,8 @@ import (
) )
var ( var (
EventPeriod = 20 * time.Second EventPeriod = 20 * time.Second // nolint:gochecknoglobals
EventJitter = 20 * time.Second EventJitter = 20 * time.Second // nolint:gochecknoglobals
) )
type User struct { type User struct {
@ -31,9 +31,9 @@ type User struct {
client *liteapi.Client client *liteapi.Client
eventCh *queue.QueuedChannel[events.Event] eventCh *queue.QueuedChannel[events.Event]
apiUser *safe.Type[liteapi.User] apiUser *safe.Value[liteapi.User]
apiAddrs *safe.Slice[liteapi.Address] apiAddrs *safe.Slice[liteapi.Address]
settings *safe.Type[liteapi.MailSettings] settings *safe.Value[liteapi.MailSettings]
userKR *crypto.KeyRing userKR *crypto.KeyRing
addrKRs map[string]*crypto.KeyRing addrKRs map[string]*crypto.KeyRing
@ -90,9 +90,9 @@ func New(ctx context.Context, encVault *vault.User, client *liteapi.Client, apiU
client: client, client: client,
eventCh: queue.NewQueuedChannel[events.Event](0, 0), eventCh: queue.NewQueuedChannel[events.Event](0, 0),
apiUser: safe.NewType(apiUser), apiUser: safe.NewValue(apiUser),
apiAddrs: safe.NewSlice(apiAddrs), apiAddrs: safe.NewSlice(apiAddrs),
settings: safe.NewType(settings), settings: safe.NewValue(settings),
userKR: userKR, userKR: userKR,
addrKRs: addrKRs, addrKRs: addrKRs,