diff --git a/Changelog.md b/Changelog.md index 49143f7e..c6fa083b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,18 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) +## [Bridge 1.8.6] James + +### Removed +* GODT-1187: Remove IMAP/SMTP blocking when no internet. + +### Changed +* GODT-1166: Reduce the number of auth for live test. + +### Fixed +* GODT-1193: Do not use message.Read permit non-UTF-8 charsets. + + ## [Bridge 1.8.5] James ### Fixed diff --git a/Makefile b/Makefile index a69a7b81..7fe0f7fd 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ TARGET_OS?=${GOOS} .PHONY: build build-ie build-nogui build-ie-nogui build-launcher build-launcher-ie versioner hasher # Keep version hardcoded so app build works also without Git repository. -BRIDGE_APP_VERSION?=1.8.5+git +BRIDGE_APP_VERSION?=1.8.6+git IE_APP_VERSION?=1.3.3+git APP_VERSION:=${BRIDGE_APP_VERSION} SRC_ICO:=logo.ico diff --git a/go.mod b/go.mod index f58d457e..a6383fe8 100644 --- a/go.mod +++ b/go.mod @@ -73,5 +73,6 @@ require ( replace ( github.com/docker/docker-credential-helpers => github.com/ProtonMail/docker-credential-helpers v1.1.0 github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac + github.com/emersion/go-message => github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753 github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57 ) diff --git a/go.sum b/go.sum index 17cf187b..3fc203fa 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac h1:2xU3QncAiS/W github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU= github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:5koQozTDELymYOyFbQ/VSubexAEXzDR8qGM5mO8GRdw= github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:795VPXcRUIQ9JyMNHP4el582VokQfippgjkQP3Gk0r0= +github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753 h1:I8IsYA297x0QLU80G5I6aLYUu3JYNSpo8j5fkXtFDW0= +github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4= github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4= github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4= github.com/ProtonMail/go-rfc5322 v0.8.0 h1:7emrf75n3CDIduQflx7aT1nJa5h/kGsiFKUYX/+IAkU= @@ -81,15 +83,11 @@ github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26 h1:FiSb8 github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26/go.mod h1:+gnnZx3Mg3MnCzZrv0eZdp5puxXQUgGT/6N6L7ShKfM= github.com/emersion/go-mbox v1.0.2 h1:tE/rT+lEugK9y0myEymCCHnwlZN04hlXPrbKkxRBA5I= github.com/emersion/go-mbox v1.0.2/go.mod h1:Yp9IVuuOYLEuMv4yjgDHvhb5mHOcYH6x92Oas3QqEZI= -github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY= -github.com/emersion/go-message v0.12.1-0.20201221184100-40c3f864532b h1:xYuhW6egTaCP+zjbUcfoy/Dr3ASdVPR9W7fmkHvZHPE= -github.com/emersion/go-message v0.12.1-0.20201221184100-40c3f864532b/go.mod h1:N1JWdZQ2WRUalmdHAX308CWBq747VJ8oUorFI3VCBwU= github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-smtp v0.14.0 h1:RYW203p+EcPjL8Z/ZpT9lZ6iOc8MG1MQzEx1UKEkXlA= github.com/emersion/go-smtp v0.14.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= -github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY= github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 h1:n9qx98xiS5V4x2WIpPC2rr9mUM5ri9r/YhCEKbhCHro= @@ -176,9 +174,6 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8= -github.com/martinlindhe/base36 v1.1.0 h1:cIwvvwYse/0+1CkUPYH5ZvVIYG3JrILmQEIbLuar02Y= -github.com/martinlindhe/base36 v1.1.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= diff --git a/internal/imap/server.go b/internal/imap/server.go index 7dfb3caf..a6cecd3c 100644 --- a/internal/imap/server.go +++ b/internal/imap/server.go @@ -23,13 +23,11 @@ import ( "io" "net" "strings" - "sync/atomic" "time" imapid "github.com/ProtonMail/go-imap-id" "github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/config/useragent" - "github.com/ProtonMail/proton-bridge/internal/events" "github.com/ProtonMail/proton-bridge/internal/imap/id" "github.com/ProtonMail/proton-bridge/internal/imap/idle" "github.com/ProtonMail/proton-bridge/internal/imap/uidplus" @@ -43,43 +41,58 @@ import ( "github.com/emersion/go-imap/backend" imapserver "github.com/emersion/go-imap/server" "github.com/emersion/go-sasl" - "github.com/sirupsen/logrus" ) -type imapServer struct { - panicHandler panicHandler - server *imapserver.Server - userAgent *useragent.UserAgent - eventListener listener.Listener - debugClient bool - debugServer bool - port int - isRunning atomic.Value +// Server takes care of IMAP listening serving. It implements serverutil.Server. +type Server struct { + panicHandler panicHandler + userAgent *useragent.UserAgent + debugClient bool + debugServer bool + port int + + server *imapserver.Server + controller serverutil.Controller } // NewIMAPServer constructs a new IMAP server configured with the given options. -func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, port int, tls *tls.Config, imapBackend backend.Backend, userAgent *useragent.UserAgent, eventListener listener.Listener) *imapServer { // nolint[golint] - s := imapserver.New(imapBackend) - s.Addr = fmt.Sprintf("%v:%v", bridge.Host, port) - s.TLSConfig = tls - s.AllowInsecureAuth = true - s.ErrorLog = newServerErrorLogger("server-imap") - s.AutoLogout = 30 * time.Minute - - if debugServer { - fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA") - log.Warning("================================================") - log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA") - log.Warning("================================================") +func NewIMAPServer( + panicHandler panicHandler, + debugClient, debugServer bool, + port int, + tls *tls.Config, + imapBackend backend.Backend, + userAgent *useragent.UserAgent, + eventListener listener.Listener, +) *Server { + server := &Server{ + panicHandler: panicHandler, + userAgent: userAgent, + debugClient: debugClient, + debugServer: debugServer, + port: port, } + server.server = newGoIMAPServer(tls, imapBackend, server.Address(), userAgent) + server.controller = serverutil.NewController(server, eventListener) + return server +} + +func newGoIMAPServer(tls *tls.Config, backend backend.Backend, address string, userAgent *useragent.UserAgent) *imapserver.Server { + server := imapserver.New(backend) + server.TLSConfig = tls + server.AllowInsecureAuth = true + server.ErrorLog = serverutil.NewServerErrorLogger(serverutil.IMAP) + server.AutoLogout = 30 * time.Minute + server.Addr = address + serverID := imapid.ID{ imapid.FieldName: "ProtonMail Bridge", imapid.FieldVendor: "Proton Technologies AG", imapid.FieldSupportURL: "https://protonmail.com/support", } - s.EnableAuth(sasl.Login, func(conn imapserver.Conn) sasl.Server { + server.EnableAuth(sasl.Login, func(conn imapserver.Conn) sasl.Server { return sasl.NewLoginServer(func(address, password string) error { user, err := conn.Server().Backend.Login(nil, address, password) if err != nil { @@ -93,7 +106,7 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por }) }) - s.Enable( + server.Enable( idle.NewExtension(), imapmove.NewExtension(), id.NewExtension(serverID, userAgent), @@ -103,87 +116,35 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por uidplus.NewExtension(), ) - server := &imapServer{ - panicHandler: panicHandler, - server: s, - userAgent: userAgent, - eventListener: eventListener, - debugClient: debugClient, - debugServer: debugServer, - port: port, - } - server.isRunning.Store(false) return server } -func (s *imapServer) HandlePanic() { s.panicHandler.HandlePanic() } -func (s *imapServer) IsRunning() bool { return s.isRunning.Load().(bool) } -func (s *imapServer) Port() int { return s.port } +// ListenAndServe will run server and all monitors. +func (s *Server) ListenAndServe() { s.controller.ListenAndServe() } -// ListenAndServe starts the server and keeps it on based on internet -// availability. -func (s *imapServer) ListenAndServe() { - serverutil.ListenAndServe(s, s.eventListener) -} +// Close turns off server and monitors. +func (s *Server) Close() { s.controller.Close() } -// ListenRetryAndServe will start listener. If port is occupied it will try -// again after coolDown time. Once listener is OK it will serve. -func (s *imapServer) ListenRetryAndServe(retries int, retryAfter time.Duration) { - if s.IsRunning() { - return - } - s.isRunning.Store(true) +// Implements serverutil.Server interface. - l := log.WithField("address", s.server.Addr) - l.Info("IMAP server is starting") - listener, err := net.Listen("tcp", s.server.Addr) - if err != nil { - s.isRunning.Store(false) - if retries > 0 { - l.WithError(err).WithField("retries", retries).Warn("IMAP listener failed") - time.Sleep(retryAfter) - s.ListenRetryAndServe(retries-1, retryAfter) - return - } +func (Server) Protocol() serverutil.Protocol { return serverutil.IMAP } +func (s *Server) UseSSL() bool { return false } +func (s *Server) Address() string { return fmt.Sprintf("%s:%d", bridge.Host, s.port) } +func (s *Server) TLSConfig() *tls.Config { return s.server.TLSConfig } +func (s *Server) HandlePanic() { s.panicHandler.HandlePanic() } - l.WithError(err).Error("IMAP listener failed") - s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error()) - return - } +func (s *Server) DebugServer() bool { return s.debugServer } +func (s *Server) DebugClient() bool { return s.debugClient } - err = s.server.Serve(&connListener{ - Listener: listener, - server: s, - userAgent: s.userAgent, - }) - // Serve returns error every time, even after closing the server. - // User shouldn't be notified about error if server shouldn't be running, - // but it should in case it was not closed by `s.Close()`. - if err != nil && s.IsRunning() { - s.isRunning.Store(false) - l.WithError(err).Error("IMAP server failed") - s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error()) - return - } - defer s.server.Close() //nolint[errcheck] +func (s *Server) SetLoggers(localDebug, remoteDebug io.Writer) { + s.server.Debug = imap.NewDebugWriter(localDebug, remoteDebug) - l.Info("IMAP server stopped") -} - -// Stops the server. -func (s *imapServer) Close() { - if !s.IsRunning() { - return - } - s.isRunning.Store(false) - - log.Info("Closing IMAP server") - if err := s.server.Close(); err != nil { - log.WithError(err).Error("Failed to close the connection") + if !s.userAgent.HasClient() { + s.userAgent.SetClient("UnknownClient", "0.0.1") } } -func (s *imapServer) DisconnectUser(address string) { +func (s *Server) DisconnectUser(address string) { log.Info("Disconnecting all open IMAP connections for ", address) s.server.ForEachConn(func(conn imapserver.Conn) { connUser := conn.Context().User @@ -195,60 +156,5 @@ func (s *imapServer) DisconnectUser(address string) { }) } -// connListener sets debug loggers on server containing fields with local -// and remote addresses right after new connection is accepted. -type connListener struct { - net.Listener - - server *imapServer - userAgent *useragent.UserAgent -} - -func (l *connListener) Accept() (net.Conn, error) { - conn, err := l.Listener.Accept() - - if err == nil && (l.server.debugServer || l.server.debugClient) { - debugLog := log - if addr := conn.LocalAddr(); addr != nil { - debugLog = debugLog.WithField("loc", addr.String()) - } - if addr := conn.RemoteAddr(); addr != nil { - debugLog = debugLog.WithField("rem", addr.String()) - } - - var localDebug, remoteDebug io.Writer - if l.server.debugServer { - localDebug = debugLog.WithField("pkg", "imap/server").WriterLevel(logrus.DebugLevel) - } - if l.server.debugClient { - remoteDebug = debugLog.WithField("pkg", "imap/client").WriterLevel(logrus.DebugLevel) - } - - l.server.server.Debug = imap.NewDebugWriter(localDebug, remoteDebug) - } - - if !l.userAgent.HasClient() { - l.userAgent.SetClient("UnknownClient", "0.0.1") - } - - return conn, err -} - -// serverErrorLogger implements go-imap/logger interface. -type serverErrorLogger struct { - tag string -} - -func newServerErrorLogger(tag string) *serverErrorLogger { - return &serverErrorLogger{tag} -} - -func (s *serverErrorLogger) Printf(format string, args ...interface{}) { - err := fmt.Sprintf(format, args...) - log.WithField("pkg", s.tag).Error(err) -} - -func (s *serverErrorLogger) Println(args ...interface{}) { - err := fmt.Sprintln(args...) - log.WithField("pkg", s.tag).Error(err) -} +func (s *Server) Serve(listener net.Listener) error { return s.server.Serve(listener) } +func (s *Server) StopServe() error { return s.server.Close() } diff --git a/internal/imap/server_test.go b/internal/imap/server_test.go deleted file mode 100644 index 1653cbd2..00000000 --- a/internal/imap/server_test.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2021 Proton Technologies AG -// -// This file is part of ProtonMail Bridge. -// -// ProtonMail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// ProtonMail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with ProtonMail Bridge. If not, see . - -package imap - -import ( - "fmt" - "testing" - - "github.com/ProtonMail/proton-bridge/internal/bridge" - "github.com/ProtonMail/proton-bridge/internal/config/useragent" - "github.com/ProtonMail/proton-bridge/internal/serverutil/mocks" - imapserver "github.com/emersion/go-imap/server" - - "github.com/stretchr/testify/require" -) - -func TestIMAPServerTurnOffAndOnAgain(t *testing.T) { - r := require.New(t) - ts := mocks.NewTestServer(12345) - - server := imapserver.New(nil) - server.Addr = fmt.Sprintf("%v:%v", bridge.Host, ts.WantPort) - - s := &imapServer{ - panicHandler: ts.PanicHandler, - server: server, - port: ts.WantPort, - eventListener: ts.EventListener, - userAgent: useragent.New(), - } - s.isRunning.Store(false) - - r.True(ts.IsPortFree()) - - go s.ListenAndServe() - ts.RunServerTests(r) -} diff --git a/internal/serverutil/controller.go b/internal/serverutil/controller.go new file mode 100644 index 00000000..c2ba1dca --- /dev/null +++ b/internal/serverutil/controller.go @@ -0,0 +1,117 @@ +// Copyright (c) 2021 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +package serverutil + +import ( + "crypto/tls" + "fmt" + "net" + + "github.com/ProtonMail/proton-bridge/internal/events" + "github.com/ProtonMail/proton-bridge/pkg/listener" + "github.com/sirupsen/logrus" +) + +// Controller will make sure that server is listening and serving and if needed +// users are disconnected. +type Controller interface { + ListenAndServe() + Close() +} + +// NewController return simple server controller. +func NewController(s Server, l listener.Listener) Controller { + log := logrus.WithField("pkg", "serverutil").WithField("protocol", s.Protocol()) + c := &controller{ + server: s, + signals: l, + log: log, + closeDisconnectUsers: make(chan void), + } + + if s.DebugServer() { + fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA") + log.Warning("================================================") + log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA") + log.Warning("================================================") + } + + return c +} + +type void struct{} + +type controller struct { + server Server + signals listener.Listener + log *logrus.Entry + + closeDisconnectUsers chan void +} + +func (c *controller) Close() { + c.closeDisconnectUsers <- void{} + if err := c.server.StopServe(); err != nil { + c.log.WithError(err).Error("Issue when closing server") + } +} + +// ListenAndServe starts the server and keeps it on based on internet +// availability. It also monitors and disconnect users if requested. +func (c *controller) ListenAndServe() { + go monitorDisconnectedUsers(c.server, c.signals, c.closeDisconnectUsers) + + defer c.server.HandlePanic() + + l := c.log.WithField("useSSL", c.server.UseSSL()). + WithField("address", c.server.Address()) + + var listener net.Listener + var err error + + if c.server.UseSSL() { + listener, err = tls.Listen("tcp", c.server.Address(), c.server.TLSConfig()) + } else { + listener, err = net.Listen("tcp", c.server.Address()) + } + + if err != nil { + l.WithError(err).Error("Cannot start listner.") + c.signals.Emit(events.ErrorEvent, string(c.server.Protocol())+" failed: "+err.Error()) + return + } + + // When starting the Bridge, we don't want to retry to notify user + // quickly about the issue. Very probably retry will not help anyway. + l.Info("Starting server") + err = c.server.Serve(&connListener{listener, c.server}) + l.WithError(err).Debug("GoSMTP not serving") +} + +func monitorDisconnectedUsers(s Server, l listener.Listener, done <-chan void) { + ch := make(chan string) + l.Add(events.CloseConnectionEvent, ch) + for { + select { + case <-done: + return + case address := <-ch: + s.DisconnectUser(address) + } + } +} diff --git a/internal/smtp/server_test.go b/internal/serverutil/error_logger.go similarity index 61% rename from internal/smtp/server_test.go rename to internal/serverutil/error_logger.go index 3342d21c..576f2d84 100644 --- a/internal/smtp/server_test.go +++ b/internal/serverutil/error_logger.go @@ -15,29 +15,25 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -package smtp +package serverutil import ( - "testing" - - "github.com/ProtonMail/proton-bridge/internal/serverutil/mocks" - - "github.com/stretchr/testify/require" + "github.com/sirupsen/logrus" ) -func TestSMTPServerTurnOffAndOnAgain(t *testing.T) { - r := require.New(t) - ts := mocks.NewTestServer(12342) - - s := &Server{ - panicHandler: ts.PanicHandler, - port: ts.WantPort, - eventListener: ts.EventListener, - } - s.isRunning.Store(false) - - r.True(ts.IsPortFree()) - - go s.ListenAndServe() - ts.RunServerTests(r) +// ServerErrorLogger implements go-imap/logger interface. +type ServerErrorLogger struct { + l *logrus.Entry +} + +func NewServerErrorLogger(protocol Protocol) *ServerErrorLogger { + return &ServerErrorLogger{l: logrus.WithField("protocol", protocol)} +} + +func (s *ServerErrorLogger) Printf(format string, args ...interface{}) { + s.l.Errorf(format, args...) +} + +func (s *ServerErrorLogger) Println(args ...interface{}) { + s.l.Errorln(args...) } diff --git a/internal/serverutil/listener.go b/internal/serverutil/listener.go new file mode 100644 index 00000000..53450205 --- /dev/null +++ b/internal/serverutil/listener.go @@ -0,0 +1,59 @@ +// Copyright (c) 2021 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +package serverutil + +import ( + "io" + "net" + + "github.com/sirupsen/logrus" +) + +// connListener sets debug loggers on server containing fields with local +// and remote addresses right after new connection is accepted. +type connListener struct { + net.Listener + + server Server +} + +func (l *connListener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + + if err == nil && (l.server.DebugServer() || l.server.DebugClient()) { + debugLog := logrus.WithField("pkg", l.server.Protocol()) + if addr := conn.LocalAddr(); addr != nil { + debugLog = debugLog.WithField("loc", addr.String()) + } + if addr := conn.RemoteAddr(); addr != nil { + debugLog = debugLog.WithField("rem", addr.String()) + } + + var localDebug, remoteDebug io.Writer + if l.server.DebugServer() { + localDebug = debugLog.WithField("comm", "server").WriterLevel(logrus.DebugLevel) + } + if l.server.DebugClient() { + remoteDebug = debugLog.WithField("comm", "client").WriterLevel(logrus.DebugLevel) + } + + l.server.SetLoggers(localDebug, remoteDebug) + } + + return conn, err +} diff --git a/internal/serverutil/mocks/server.go b/internal/serverutil/mocks/server.go deleted file mode 100644 index 8e60c33c..00000000 --- a/internal/serverutil/mocks/server.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright (c) 2021 Proton Technologies AG -// -// This file is part of ProtonMail Bridge. -// -// ProtonMail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// ProtonMail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with ProtonMail Bridge. If not, see . - -package mocks - -import ( - "fmt" - "net/http" - "sync/atomic" - "time" - - "github.com/ProtonMail/proton-bridge/internal/events" - "github.com/ProtonMail/proton-bridge/pkg/listener" - "github.com/ProtonMail/proton-bridge/pkg/ports" - "github.com/sirupsen/logrus" - "github.com/stretchr/testify/require" -) - -type DummyPanicHandler struct{} - -func (ph *DummyPanicHandler) HandlePanic() {} - -type TestServer struct { - PanicHandler *DummyPanicHandler - WantPort int - EventListener listener.Listener - - isRunning atomic.Value - srv *http.Server -} - -func NewTestServer(port int) *TestServer { - s := &TestServer{ - PanicHandler: &DummyPanicHandler{}, - EventListener: listener.New(), - WantPort: ports.FindFreePortFrom(port), - } - s.isRunning.Store(false) - return s -} - -func (s *TestServer) IsPortFree() bool { - return true -} - -func (s *TestServer) IsPortOccupied() bool { - return true -} - -func (s *TestServer) Emit(event string, try, iEvt int) int { - // Emit has separate go routine so it is needed to wait here to - // prevent event race condition. - time.Sleep(100 * time.Millisecond) - iEvt++ - s.EventListener.Emit(event, fmt.Sprintf("%d:%d", try, iEvt)) - return iEvt -} - -func (s *TestServer) HandlePanic() {} -func (s *TestServer) DisconnectUser(string) {} -func (s *TestServer) Port() int { return s.WantPort } -func (s *TestServer) IsRunning() bool { return s.isRunning.Load().(bool) } - -func (s *TestServer) ListenRetryAndServe(retries int, retryAfter time.Duration) { - if s.isRunning.Load().(bool) { - return - } - s.isRunning.Store(true) - - // There can be delay when starting server - time.Sleep(200 * time.Millisecond) - - s.srv = &http.Server{ - Addr: fmt.Sprintf("127.0.0.1:%d", s.WantPort), - } - - err := s.srv.ListenAndServe() - if err != nil { - s.isRunning.Store(false) - if retries > 0 { - time.Sleep(retryAfter) - s.ListenRetryAndServe(retries-1, retryAfter) - } - } - - if s.IsRunning() { - logrus.Error("Not serving but isRunning is true") - s.isRunning.Store(false) - } -} - -func (s *TestServer) Close() { - if !s.isRunning.Load().(bool) { - return - } - s.isRunning.Store(false) - - // There can be delay when stopping server - time.Sleep(200 * time.Millisecond) - if err := s.srv.Close(); err != nil { - logrus.WithError(err).Error("Closing dummy server") - } -} - -func (s *TestServer) RunServerTests(r *require.Assertions) { - // NOTE About choosing tick durations: - // In order to avoid ticks to synchronise and cause occasional race - // condition we choose the tick duration around 100ms but not exactly - // to have large common multiple. - r.Eventually(s.IsPortOccupied, 5*time.Second, 97*time.Millisecond) - - // There was an issue where second time we were not able to restore server. - for try := 0; try < 3; try++ { - i := s.Emit(events.InternetOffEvent, try, 0) - r.Eventually(s.IsPortFree, 10*time.Second, 99*time.Millisecond, "signal off try %d : %d", try, i) - - i = s.Emit(events.InternetOnEvent, try, i) - i = s.Emit(events.InternetOffEvent, try, i) - i = s.Emit(events.InternetOffEvent, try, i) - i = s.Emit(events.InternetOffEvent, try, i) - i = s.Emit(events.InternetOffEvent, try, i) - i = s.Emit(events.InternetOnEvent, try, i) - i = s.Emit(events.InternetOnEvent, try, i) - i = s.Emit(events.InternetOffEvent, try, i) - // Wait a bit longer if needed to process all events - r.Eventually(s.IsPortFree, 20*time.Second, 101*time.Millisecond, "again signal off number %d : %d", try, i) - - i = s.Emit(events.InternetOnEvent, try, i) - r.Eventually(s.IsPortOccupied, 10*time.Second, 103*time.Millisecond, "signal on number %d : %d", try, i) - - i = s.Emit(events.InternetOffEvent, try, i) - i = s.Emit(events.InternetOnEvent, try, i) - i = s.Emit(events.InternetOnEvent, try, i) - r.Eventually(s.IsPortOccupied, 10*time.Second, 107*time.Millisecond, "again signal on number %d : %d", try, i) - } -} diff --git a/internal/serverutil/server_test.go b/internal/serverutil/protocol.go similarity index 71% rename from internal/serverutil/server_test.go rename to internal/serverutil/protocol.go index fa04a06f..c551359f 100644 --- a/internal/serverutil/server_test.go +++ b/internal/serverutil/protocol.go @@ -17,19 +17,10 @@ package serverutil -import ( - "testing" +type Protocol string - "github.com/ProtonMail/proton-bridge/internal/serverutil/mocks" - "github.com/stretchr/testify/require" +const ( + HTTP = Protocol("HTTP") + IMAP = Protocol("IMAP") + SMTP = Protocol("SMTP") ) - -func TestServerTurnOffAndOnAgain(t *testing.T) { - r := require.New(t) - s := mocks.NewTestServer(12321) - - r.True(s.IsPortFree()) - - go ListenAndServe(s, s.EventListener) - s.RunServerTests(r) -} diff --git a/internal/serverutil/server.go b/internal/serverutil/server.go index 6542194e..e081b032 100644 --- a/internal/serverutil/server.go +++ b/internal/serverutil/server.go @@ -18,115 +18,24 @@ package serverutil import ( - "time" - - "github.com/ProtonMail/proton-bridge/internal/events" - "github.com/ProtonMail/proton-bridge/pkg/listener" - "github.com/ProtonMail/proton-bridge/pkg/ports" - "github.com/sirupsen/logrus" + "crypto/tls" + "io" + "net" ) -// Server which can handle disconnected users and lost internet connection. +// Server can handle disconnected users. type Server interface { + Protocol() Protocol + UseSSL() bool + Address() string + TLSConfig() *tls.Config + + DebugServer() bool + DebugClient() bool + SetLoggers(localDebug, remoteDebug io.Writer) + HandlePanic() DisconnectUser(string) - ListenRetryAndServe(int, time.Duration) - Close() - Port() int - IsRunning() bool -} - -func monitorDisconnectedUsers(s Server, l listener.Listener) { - ch := make(chan string) - l.Add(events.CloseConnectionEvent, ch) - for address := range ch { - s.DisconnectUser(address) - } -} - -func redirectInternetEventsToOneChannel(l listener.Listener) (isInternetOn chan bool) { - on := make(chan string) - l.Add(events.InternetOnEvent, on) - off := make(chan string) - l.Add(events.InternetOffEvent, off) - - // Redirect two channels into one. When select was used the algorithm - // first read all on channels and then read all off channels. - isInternetOn = make(chan bool, 20) - go func() { - for { - logrus.WithField("try", <-on).Trace("Internet ON") - isInternetOn <- true - } - }() - - go func() { - for { - logrus.WithField("try", <-off).Trace("Internet OFF") - isInternetOn <- false - } - }() - return -} - -const ( - recheckPortAfter = 50 * time.Millisecond - stopPortChecksAfter = 15 * time.Second - retryListenerAfter = 5 * time.Second -) - -func monitorInternetConnection(s Server, l listener.Listener) { - isInternetOn := redirectInternetEventsToOneChannel(l) - for { - var expectedIsPortFree bool - if <-isInternetOn { - if s.IsRunning() { - continue - } - go func() { - defer s.HandlePanic() - // We had issues on Mac that from time to time something - // blocked our port for a bit after we closed IMAP server - // due to connection issues. - // Restart always helped, so we do retry to not bother user. - s.ListenRetryAndServe(10, retryListenerAfter) - }() - expectedIsPortFree = false - } else { - if !s.IsRunning() { - continue - } - s.Close() - expectedIsPortFree = true - } - start := time.Now() - for { - isPortFree := ports.IsPortFree(s.Port()) - logrus. - WithField("port", s.Port()). - WithField("isFree", isPortFree). - WithField("wantToBeFree", expectedIsPortFree). - Trace("Check port") - if isPortFree == expectedIsPortFree { - break - } - // Safety stop if something went wrong. - if time.Since(start) > stopPortChecksAfter { - logrus.WithField("expectedIsPortFree", expectedIsPortFree).Warn("Server start/stop check timeouted") - break - } - time.Sleep(recheckPortAfter) - } - } -} - -// ListenAndServe starts the server and keeps it on based on internet -// availability. It also monitors and disconnect users if requested. -func ListenAndServe(s Server, l listener.Listener) { - go monitorDisconnectedUsers(s, l) - go monitorInternetConnection(s, l) - - // When starting the Bridge, we don't want to retry to notify user - // quickly about the issue. Very probably retry will not help anyway. - s.ListenRetryAndServe(0, 0) + Serve(net.Listener) error + StopServe() error } diff --git a/internal/serverutil/test/controller_test.go b/internal/serverutil/test/controller_test.go new file mode 100644 index 00000000..62cab2ec --- /dev/null +++ b/internal/serverutil/test/controller_test.go @@ -0,0 +1,153 @@ +// Copyright (c) 2021 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +package test + +import ( + "net/http" + "testing" + "time" + + "github.com/ProtonMail/proton-bridge/internal/events" + "github.com/ProtonMail/proton-bridge/internal/serverutil" + "github.com/ProtonMail/proton-bridge/pkg/listener" + "github.com/stretchr/testify/require" +) + +func setup(t *testing.T) (*require.Assertions, *testServer, listener.Listener, serverutil.Controller) { + r := require.New(t) + s := newTestServer() + l := listener.New() + c := serverutil.NewController(s, l) + + return r, s, l, c +} + +func TestControllerListernServeClose(t *testing.T) { + r, s, l, c := setup(t) + + errorCh := l.ProvideChannel(events.ErrorEvent) + + r.True(s.portIsFree()) + go c.ListenAndServe() + r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond) + + r.NoError(s.ping()) + + r.Nil(s.localDebug) + r.Nil(s.remoteDebug) + + c.Close() + r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond) + + select { + case msg := <-errorCh: + r.Fail("Expected no error but have %q", msg) + case <-time.Tick(100 * time.Millisecond): + break + } +} + +func TestControllerFailOnBusyPort(t *testing.T) { + r, s, l, c := setup(t) + + ocupator := http.Server{Addr: s.Address()} + defer ocupator.Close() //nolint[errcheck] + + go ocupator.ListenAndServe() //nolint[errcheck] + r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond) + + errorCh := l.ProvideChannel(events.ErrorEvent) + go c.ListenAndServe() + + r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond) + + select { + case <-errorCh: + break + case <-time.Tick(time.Second): + r.Fail("Expected error but have none.") + } +} + +func TestControllerCallDisconnectUser(t *testing.T) { + r, s, l, c := setup(t) + + go c.ListenAndServe() + r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond) + r.NoError(s.ping()) + + l.Emit(events.CloseConnectionEvent, "") + r.Eventually(func() bool { return s.calledDisconnected == 1 }, time.Second, 50*time.Millisecond) + + c.Close() + r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond) + + l.Emit(events.CloseConnectionEvent, "") + r.Equal(1, s.calledDisconnected) +} + +func TestDebugClient(t *testing.T) { + r, s, _, c := setup(t) + + s.debugServer = false + s.debugClient = true + + go c.ListenAndServe() + r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond) + r.NoError(s.ping()) + + r.Nil(s.localDebug) + r.NotNil(s.remoteDebug) + + c.Close() + r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond) +} + +func TestDebugServer(t *testing.T) { + r, s, _, c := setup(t) + + s.debugServer = true + s.debugClient = false + + go c.ListenAndServe() + r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond) + r.NoError(s.ping()) + + r.NotNil(s.localDebug) + r.Nil(s.remoteDebug) + + c.Close() + r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond) +} + +func TestDebugBoth(t *testing.T) { + r, s, _, c := setup(t) + + s.debugServer = true + s.debugClient = true + + go c.ListenAndServe() + r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond) + r.NoError(s.ping()) + + r.NotNil(s.localDebug) + r.NotNil(s.remoteDebug) + + c.Close() + r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond) +} diff --git a/internal/serverutil/test/server.go b/internal/serverutil/test/server.go new file mode 100644 index 00000000..f0615c5d --- /dev/null +++ b/internal/serverutil/test/server.go @@ -0,0 +1,88 @@ +// Copyright (c) 2021 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +package test + +import ( + "crypto/tls" + "fmt" + "io" + "net" + "net/http" + + "github.com/ProtonMail/proton-bridge/internal/serverutil" + "github.com/ProtonMail/proton-bridge/pkg/ports" +) + +func newTestServer() *testServer { + return &testServer{port: 11188} +} + +type testServer struct { + http http.Server + + useSSL, + debugServer, + debugClient bool + calledDisconnected int + + port int + tls *tls.Config + + localDebug, remoteDebug io.Writer +} + +func (*testServer) Protocol() serverutil.Protocol { return serverutil.HTTP } +func (s *testServer) UseSSL() bool { return s.useSSL } +func (s *testServer) Address() string { return fmt.Sprintf("127.0.0.1:%d", s.port) } +func (s *testServer) TLSConfig() *tls.Config { return s.tls } +func (s *testServer) HandlePanic() {} + +func (s *testServer) DebugServer() bool { return s.debugServer } +func (s *testServer) DebugClient() bool { return s.debugClient } +func (s *testServer) SetLoggers(localDebug, remoteDebug io.Writer) { + s.localDebug = localDebug + s.remoteDebug = remoteDebug +} + +func (s *testServer) DisconnectUser(string) { + s.calledDisconnected++ +} + +func (s *testServer) Serve(l net.Listener) error { + return s.http.Serve(l) +} + +func (s *testServer) StopServe() error { return s.http.Close() } + +func (s *testServer) portIsFree() bool { + return ports.IsPortFree(s.port) +} + +func (s *testServer) portIsOccupied() bool { + return !ports.IsPortFree(s.port) +} + +func (s *testServer) ping() error { + client := &http.Client{} + resp, err := client.Get("http://" + s.Address() + "/ping") + if err != nil { + return err + } + + return resp.Body.Close() +} diff --git a/internal/smtp/server.go b/internal/smtp/server.go index ecb9f2f6..3db68c69 100644 --- a/internal/smtp/server.go +++ b/internal/smtp/server.go @@ -20,72 +20,60 @@ package smtp import ( "crypto/tls" "fmt" + "io" "net" - "sync/atomic" - "time" "github.com/ProtonMail/proton-bridge/internal/bridge" - "github.com/ProtonMail/proton-bridge/internal/events" "github.com/ProtonMail/proton-bridge/internal/serverutil" "github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/emersion/go-sasl" goSMTP "github.com/emersion/go-smtp" - "github.com/sirupsen/logrus" ) // Server is Bridge SMTP server implementation. type Server struct { - panicHandler panicHandler - backend goSMTP.Backend - server *goSMTP.Server - eventListener listener.Listener - debug bool - useSSL bool - port int - tls *tls.Config - isRunning atomic.Value + panicHandler panicHandler + backend goSMTP.Backend + debug bool + useSSL bool + port int + tls *tls.Config + + server *goSMTP.Server + controller serverutil.Controller } // NewSMTPServer returns an SMTP server configured with the given options. -func NewSMTPServer(panicHandler panicHandler, debug bool, port int, useSSL bool, tls *tls.Config, smtpBackend goSMTP.Backend, eventListener listener.Listener) *Server { - if debug { - fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA") - log.Warning("================================================") - log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA") - log.Warning("================================================") +func NewSMTPServer( + panicHandler panicHandler, + debug bool, port int, useSSL bool, + tls *tls.Config, + smtpBackend goSMTP.Backend, + eventListener listener.Listener, +) *Server { + server := &Server{ + panicHandler: panicHandler, + backend: smtpBackend, + debug: debug, + useSSL: useSSL, + port: port, + tls: tls, } - server := &Server{ - panicHandler: panicHandler, - backend: smtpBackend, - eventListener: eventListener, - debug: debug, - useSSL: useSSL, - port: port, - tls: tls, - } - server.isRunning.Store(false) + server.server = newGoSMTPServer(server) + server.controller = serverutil.NewController(server, eventListener) return server } -func (s *Server) HandlePanic() { s.panicHandler.HandlePanic() } -func (s *Server) IsRunning() bool { return s.isRunning.Load().(bool) } -func (s *Server) Port() int { return s.port } - -func newGoSMTPServer(debug bool, smtpBackend goSMTP.Backend, port int, tls *tls.Config) *goSMTP.Server { - newSMTP := goSMTP.NewServer(smtpBackend) - newSMTP.Addr = fmt.Sprintf("%v:%v", bridge.Host, port) - newSMTP.TLSConfig = tls +func newGoSMTPServer(s *Server) *goSMTP.Server { + newSMTP := goSMTP.NewServer(s.backend) + newSMTP.Addr = s.Address() + newSMTP.TLSConfig = s.tls newSMTP.Domain = bridge.Host + newSMTP.ErrorLog = serverutil.NewServerErrorLogger(serverutil.SMTP) newSMTP.AllowInsecureAuth = true newSMTP.MaxLineLength = 1 << 16 - if debug { - newSMTP.Debug = logrus. - WithField("pkg", "smtp/server"). - WriterLevel(logrus.DebugLevel) - } - newSMTP.EnableAuth(sasl.Login, func(conn *goSMTP.Conn) sasl.Server { return sasl.NewLoginServer(func(address, password string) error { user, err := conn.Server().Backend.Login(nil, address, password) @@ -100,80 +88,24 @@ func newGoSMTPServer(debug bool, smtpBackend goSMTP.Backend, port int, tls *tls. return newSMTP } -// ListenAndServe starts the server and keeps it on based on internet -// availability. -func (s *Server) ListenAndServe() { - serverutil.ListenAndServe(s, s.eventListener) -} +// ListenAndServe will run server and all monitors. +func (s *Server) ListenAndServe() { s.controller.ListenAndServe() } -func (s *Server) ListenRetryAndServe(retries int, retryAfter time.Duration) { - if s.IsRunning() { - return - } - s.isRunning.Store(true) +// Close turns off server and monitors. +func (s *Server) Close() { s.controller.Close() } - s.server = newGoSMTPServer(s.debug, s.backend, s.port, s.tls) +// Implements servertutil.Server interface. - l := log.WithField("useSSL", s.useSSL).WithField("address", s.server.Addr) - l.Info("SMTP server is starting") +func (Server) Protocol() serverutil.Protocol { return serverutil.SMTP } +func (s *Server) UseSSL() bool { return s.useSSL } +func (s *Server) Address() string { return fmt.Sprintf("%s:%d", bridge.Host, s.port) } +func (s *Server) TLSConfig() *tls.Config { return s.tls } +func (s *Server) HandlePanic() { s.panicHandler.HandlePanic() } - var listener net.Listener - var err error - if s.useSSL { - listener, err = tls.Listen("tcp", s.server.Addr, s.server.TLSConfig) - } else { - listener, err = net.Listen("tcp", s.server.Addr) - } - l.WithError(err).Debug("Listener for SMTP created") - if err != nil { - s.isRunning.Store(false) - if retries > 0 { - l.WithError(err).WithField("retries", retries).Warn("SMTP listener failed") - time.Sleep(retryAfter) - s.ListenRetryAndServe(retries-1, retryAfter) - return - } +func (s *Server) DebugServer() bool { return s.debug } +func (s *Server) DebugClient() bool { return s.debug } - l.WithError(err).Error("SMTP listener failed") - s.eventListener.Emit(events.ErrorEvent, "SMTP failed: "+err.Error()) - return - } - - err = s.server.Serve(listener) - l.WithError(err).Debug("GoSMTP not serving") - // Serve returns error every time, even after closing the server. - // User shouldn't be notified about error if server shouldn't be running, - // but it should in case it was not closed by `s.Close()`. - if err != nil && s.IsRunning() { - s.isRunning.Store(false) - l.WithError(err).Error("SMTP server failed") - s.eventListener.Emit(events.ErrorEvent, "SMTP failed: "+err.Error()) - return - } - defer func() { - // Go SMTP server instance can be closed only once. Otherwise - // it returns an error. The error is not export therefore we - // will check the string value. - err := s.server.Close() - if err == nil || err.Error() != "smtp: server already closed" { - l.WithError(err).Warn("Server was not closed") - } - }() - - l.Info("SMTP server closed") -} - -// Close stops the server. -func (s *Server) Close() { - if !s.IsRunning() { - return - } - s.isRunning.Store(false) - - if err := s.server.Close(); err != nil { - log.WithError(err).Error("Cannot close the server") - } -} +func (s *Server) SetLoggers(localDebug, remoteDebug io.Writer) { s.server.Debug = localDebug } func (s *Server) DisconnectUser(address string) { log.Info("Disconnecting all open SMTP connections for ", address) @@ -186,3 +118,6 @@ func (s *Server) DisconnectUser(address string) { } }) } + +func (s *Server) Serve(l net.Listener) error { return s.server.Serve(l) } +func (s *Server) StopServe() error { return s.server.Close() } diff --git a/internal/store/mocks/mocks.go b/internal/store/mocks/mocks.go index acb846be..7adf0ed3 100644 --- a/internal/store/mocks/mocks.go +++ b/internal/store/mocks/mocks.go @@ -12,89 +12,89 @@ import ( gomock "github.com/golang/mock/gomock" ) -// MockPanicHandler is a mock of PanicHandler interface +// MockPanicHandler is a mock of PanicHandler interface. type MockPanicHandler struct { ctrl *gomock.Controller recorder *MockPanicHandlerMockRecorder } -// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler +// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler. type MockPanicHandlerMockRecorder struct { mock *MockPanicHandler } -// NewMockPanicHandler creates a new mock instance +// NewMockPanicHandler creates a new mock instance. func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler { mock := &MockPanicHandler{ctrl: ctrl} mock.recorder = &MockPanicHandlerMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder { return m.recorder } -// HandlePanic mocks base method +// HandlePanic mocks base method. func (m *MockPanicHandler) HandlePanic() { m.ctrl.T.Helper() m.ctrl.Call(m, "HandlePanic") } -// HandlePanic indicates an expected call of HandlePanic +// HandlePanic indicates an expected call of HandlePanic. func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic)) } -// MockBridgeUser is a mock of BridgeUser interface +// MockBridgeUser is a mock of BridgeUser interface. type MockBridgeUser struct { ctrl *gomock.Controller recorder *MockBridgeUserMockRecorder } -// MockBridgeUserMockRecorder is the mock recorder for MockBridgeUser +// MockBridgeUserMockRecorder is the mock recorder for MockBridgeUser. type MockBridgeUserMockRecorder struct { mock *MockBridgeUser } -// NewMockBridgeUser creates a new mock instance +// NewMockBridgeUser creates a new mock instance. func NewMockBridgeUser(ctrl *gomock.Controller) *MockBridgeUser { mock := &MockBridgeUser{ctrl: ctrl} mock.recorder = &MockBridgeUserMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockBridgeUser) EXPECT() *MockBridgeUserMockRecorder { return m.recorder } -// CloseAllConnections mocks base method +// CloseAllConnections mocks base method. func (m *MockBridgeUser) CloseAllConnections() { m.ctrl.T.Helper() m.ctrl.Call(m, "CloseAllConnections") } -// CloseAllConnections indicates an expected call of CloseAllConnections +// CloseAllConnections indicates an expected call of CloseAllConnections. func (mr *MockBridgeUserMockRecorder) CloseAllConnections() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseAllConnections", reflect.TypeOf((*MockBridgeUser)(nil).CloseAllConnections)) } -// CloseConnection mocks base method +// CloseConnection mocks base method. func (m *MockBridgeUser) CloseConnection(arg0 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "CloseConnection", arg0) } -// CloseConnection indicates an expected call of CloseConnection +// CloseConnection indicates an expected call of CloseConnection. func (mr *MockBridgeUserMockRecorder) CloseConnection(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseConnection", reflect.TypeOf((*MockBridgeUser)(nil).CloseConnection), arg0) } -// GetAddressID mocks base method +// GetAddressID mocks base method. func (m *MockBridgeUser) GetAddressID(arg0 string) (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAddressID", arg0) @@ -103,13 +103,13 @@ func (m *MockBridgeUser) GetAddressID(arg0 string) (string, error) { return ret0, ret1 } -// GetAddressID indicates an expected call of GetAddressID +// GetAddressID indicates an expected call of GetAddressID. func (mr *MockBridgeUserMockRecorder) GetAddressID(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAddressID", reflect.TypeOf((*MockBridgeUser)(nil).GetAddressID), arg0) } -// GetClient mocks base method +// GetClient mocks base method. func (m *MockBridgeUser) GetClient() pmapi.Client { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetClient") @@ -117,13 +117,13 @@ func (m *MockBridgeUser) GetClient() pmapi.Client { return ret0 } -// GetClient indicates an expected call of GetClient +// GetClient indicates an expected call of GetClient. func (mr *MockBridgeUserMockRecorder) GetClient() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClient", reflect.TypeOf((*MockBridgeUser)(nil).GetClient)) } -// GetPrimaryAddress mocks base method +// GetPrimaryAddress mocks base method. func (m *MockBridgeUser) GetPrimaryAddress() string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPrimaryAddress") @@ -131,13 +131,13 @@ func (m *MockBridgeUser) GetPrimaryAddress() string { return ret0 } -// GetPrimaryAddress indicates an expected call of GetPrimaryAddress +// GetPrimaryAddress indicates an expected call of GetPrimaryAddress. func (mr *MockBridgeUserMockRecorder) GetPrimaryAddress() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrimaryAddress", reflect.TypeOf((*MockBridgeUser)(nil).GetPrimaryAddress)) } -// GetStoreAddresses mocks base method +// GetStoreAddresses mocks base method. func (m *MockBridgeUser) GetStoreAddresses() []string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetStoreAddresses") @@ -145,13 +145,13 @@ func (m *MockBridgeUser) GetStoreAddresses() []string { return ret0 } -// GetStoreAddresses indicates an expected call of GetStoreAddresses +// GetStoreAddresses indicates an expected call of GetStoreAddresses. func (mr *MockBridgeUserMockRecorder) GetStoreAddresses() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStoreAddresses", reflect.TypeOf((*MockBridgeUser)(nil).GetStoreAddresses)) } -// ID mocks base method +// ID mocks base method. func (m *MockBridgeUser) ID() string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ID") @@ -159,13 +159,13 @@ func (m *MockBridgeUser) ID() string { return ret0 } -// ID indicates an expected call of ID +// ID indicates an expected call of ID. func (mr *MockBridgeUserMockRecorder) ID() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockBridgeUser)(nil).ID)) } -// IsCombinedAddressMode mocks base method +// IsCombinedAddressMode mocks base method. func (m *MockBridgeUser) IsCombinedAddressMode() bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "IsCombinedAddressMode") @@ -173,13 +173,13 @@ func (m *MockBridgeUser) IsCombinedAddressMode() bool { return ret0 } -// IsCombinedAddressMode indicates an expected call of IsCombinedAddressMode +// IsCombinedAddressMode indicates an expected call of IsCombinedAddressMode. func (mr *MockBridgeUserMockRecorder) IsCombinedAddressMode() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsCombinedAddressMode", reflect.TypeOf((*MockBridgeUser)(nil).IsCombinedAddressMode)) } -// IsConnected mocks base method +// IsConnected mocks base method. func (m *MockBridgeUser) IsConnected() bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "IsConnected") @@ -187,13 +187,13 @@ func (m *MockBridgeUser) IsConnected() bool { return ret0 } -// IsConnected indicates an expected call of IsConnected +// IsConnected indicates an expected call of IsConnected. func (mr *MockBridgeUserMockRecorder) IsConnected() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsConnected", reflect.TypeOf((*MockBridgeUser)(nil).IsConnected)) } -// Logout mocks base method +// Logout mocks base method. func (m *MockBridgeUser) Logout() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Logout") @@ -201,13 +201,13 @@ func (m *MockBridgeUser) Logout() error { return ret0 } -// Logout indicates an expected call of Logout +// Logout indicates an expected call of Logout. func (mr *MockBridgeUserMockRecorder) Logout() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockBridgeUser)(nil).Logout)) } -// UpdateUser mocks base method +// UpdateUser mocks base method. func (m *MockBridgeUser) UpdateUser(arg0 context.Context) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateUser", arg0) @@ -215,36 +215,36 @@ func (m *MockBridgeUser) UpdateUser(arg0 context.Context) error { return ret0 } -// UpdateUser indicates an expected call of UpdateUser +// UpdateUser indicates an expected call of UpdateUser. func (mr *MockBridgeUserMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockBridgeUser)(nil).UpdateUser), arg0) } -// MockChangeNotifier is a mock of ChangeNotifier interface +// MockChangeNotifier is a mock of ChangeNotifier interface. type MockChangeNotifier struct { ctrl *gomock.Controller recorder *MockChangeNotifierMockRecorder } -// MockChangeNotifierMockRecorder is the mock recorder for MockChangeNotifier +// MockChangeNotifierMockRecorder is the mock recorder for MockChangeNotifier. type MockChangeNotifierMockRecorder struct { mock *MockChangeNotifier } -// NewMockChangeNotifier creates a new mock instance +// NewMockChangeNotifier creates a new mock instance. func NewMockChangeNotifier(ctrl *gomock.Controller) *MockChangeNotifier { mock := &MockChangeNotifier{ctrl: ctrl} mock.recorder = &MockChangeNotifierMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockChangeNotifier) EXPECT() *MockChangeNotifierMockRecorder { return m.recorder } -// CanDelete mocks base method +// CanDelete mocks base method. func (m *MockChangeNotifier) CanDelete(arg0 string) (bool, func()) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CanDelete", arg0) @@ -253,67 +253,67 @@ func (m *MockChangeNotifier) CanDelete(arg0 string) (bool, func()) { return ret0, ret1 } -// CanDelete indicates an expected call of CanDelete +// CanDelete indicates an expected call of CanDelete. func (mr *MockChangeNotifierMockRecorder) CanDelete(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanDelete", reflect.TypeOf((*MockChangeNotifier)(nil).CanDelete), arg0) } -// DeleteMessage mocks base method +// DeleteMessage mocks base method. func (m *MockChangeNotifier) DeleteMessage(arg0, arg1 string, arg2 uint32) { m.ctrl.T.Helper() m.ctrl.Call(m, "DeleteMessage", arg0, arg1, arg2) } -// DeleteMessage indicates an expected call of DeleteMessage +// DeleteMessage indicates an expected call of DeleteMessage. func (mr *MockChangeNotifierMockRecorder) DeleteMessage(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessage", reflect.TypeOf((*MockChangeNotifier)(nil).DeleteMessage), arg0, arg1, arg2) } -// MailboxCreated mocks base method +// MailboxCreated mocks base method. func (m *MockChangeNotifier) MailboxCreated(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "MailboxCreated", arg0, arg1) } -// MailboxCreated indicates an expected call of MailboxCreated +// MailboxCreated indicates an expected call of MailboxCreated. func (mr *MockChangeNotifierMockRecorder) MailboxCreated(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MailboxCreated", reflect.TypeOf((*MockChangeNotifier)(nil).MailboxCreated), arg0, arg1) } -// MailboxStatus mocks base method +// MailboxStatus mocks base method. func (m *MockChangeNotifier) MailboxStatus(arg0, arg1 string, arg2, arg3, arg4 uint32) { m.ctrl.T.Helper() m.ctrl.Call(m, "MailboxStatus", arg0, arg1, arg2, arg3, arg4) } -// MailboxStatus indicates an expected call of MailboxStatus +// MailboxStatus indicates an expected call of MailboxStatus. func (mr *MockChangeNotifierMockRecorder) MailboxStatus(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MailboxStatus", reflect.TypeOf((*MockChangeNotifier)(nil).MailboxStatus), arg0, arg1, arg2, arg3, arg4) } -// Notice mocks base method +// Notice mocks base method. func (m *MockChangeNotifier) Notice(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "Notice", arg0, arg1) } -// Notice indicates an expected call of Notice +// Notice indicates an expected call of Notice. func (mr *MockChangeNotifierMockRecorder) Notice(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Notice", reflect.TypeOf((*MockChangeNotifier)(nil).Notice), arg0, arg1) } -// UpdateMessage mocks base method +// UpdateMessage mocks base method. func (m *MockChangeNotifier) UpdateMessage(arg0, arg1 string, arg2, arg3 uint32, arg4 *pmapi.Message, arg5 bool) { m.ctrl.T.Helper() m.ctrl.Call(m, "UpdateMessage", arg0, arg1, arg2, arg3, arg4, arg5) } -// UpdateMessage indicates an expected call of UpdateMessage +// UpdateMessage indicates an expected call of UpdateMessage. func (mr *MockChangeNotifierMockRecorder) UpdateMessage(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMessage", reflect.TypeOf((*MockChangeNotifier)(nil).UpdateMessage), arg0, arg1, arg2, arg3, arg4, arg5) diff --git a/internal/store/mocks/utils_mocks.go b/internal/store/mocks/utils_mocks.go index 8a6c8677..ec99aa03 100644 --- a/internal/store/mocks/utils_mocks.go +++ b/internal/store/mocks/utils_mocks.go @@ -11,54 +11,54 @@ import ( gomock "github.com/golang/mock/gomock" ) -// MockListener is a mock of Listener interface +// MockListener is a mock of Listener interface. type MockListener struct { ctrl *gomock.Controller recorder *MockListenerMockRecorder } -// MockListenerMockRecorder is the mock recorder for MockListener +// MockListenerMockRecorder is the mock recorder for MockListener. type MockListenerMockRecorder struct { mock *MockListener } -// NewMockListener creates a new mock instance +// NewMockListener creates a new mock instance. func NewMockListener(ctrl *gomock.Controller) *MockListener { mock := &MockListener{ctrl: ctrl} mock.recorder = &MockListenerMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockListener) EXPECT() *MockListenerMockRecorder { return m.recorder } -// Add mocks base method +// Add mocks base method. func (m *MockListener) Add(arg0 string, arg1 chan<- string) { m.ctrl.T.Helper() m.ctrl.Call(m, "Add", arg0, arg1) } -// Add indicates an expected call of Add +// Add indicates an expected call of Add. func (mr *MockListenerMockRecorder) Add(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockListener)(nil).Add), arg0, arg1) } -// Emit mocks base method +// Emit mocks base method. func (m *MockListener) Emit(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "Emit", arg0, arg1) } -// Emit indicates an expected call of Emit +// Emit indicates an expected call of Emit. func (mr *MockListenerMockRecorder) Emit(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Emit", reflect.TypeOf((*MockListener)(nil).Emit), arg0, arg1) } -// ProvideChannel mocks base method +// ProvideChannel mocks base method. func (m *MockListener) ProvideChannel(arg0 string) <-chan string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ProvideChannel", arg0) @@ -66,55 +66,55 @@ func (m *MockListener) ProvideChannel(arg0 string) <-chan string { return ret0 } -// ProvideChannel indicates an expected call of ProvideChannel +// ProvideChannel indicates an expected call of ProvideChannel. func (mr *MockListenerMockRecorder) ProvideChannel(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProvideChannel", reflect.TypeOf((*MockListener)(nil).ProvideChannel), arg0) } -// Remove mocks base method +// Remove mocks base method. func (m *MockListener) Remove(arg0 string, arg1 chan<- string) { m.ctrl.T.Helper() m.ctrl.Call(m, "Remove", arg0, arg1) } -// Remove indicates an expected call of Remove +// Remove indicates an expected call of Remove. func (mr *MockListenerMockRecorder) Remove(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockListener)(nil).Remove), arg0, arg1) } -// RetryEmit mocks base method +// RetryEmit mocks base method. func (m *MockListener) RetryEmit(arg0 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RetryEmit", arg0) } -// RetryEmit indicates an expected call of RetryEmit +// RetryEmit indicates an expected call of RetryEmit. func (mr *MockListenerMockRecorder) RetryEmit(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryEmit", reflect.TypeOf((*MockListener)(nil).RetryEmit), arg0) } -// SetBuffer mocks base method +// SetBuffer mocks base method. func (m *MockListener) SetBuffer(arg0 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetBuffer", arg0) } -// SetBuffer indicates an expected call of SetBuffer +// SetBuffer indicates an expected call of SetBuffer. func (mr *MockListenerMockRecorder) SetBuffer(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBuffer", reflect.TypeOf((*MockListener)(nil).SetBuffer), arg0) } -// SetLimit mocks base method +// SetLimit mocks base method. func (m *MockListener) SetLimit(arg0 string, arg1 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetLimit", arg0, arg1) } -// SetLimit indicates an expected call of SetLimit +// SetLimit indicates an expected call of SetLimit. func (mr *MockListenerMockRecorder) SetLimit(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLimit", reflect.TypeOf((*MockListener)(nil).SetLimit), arg0, arg1) diff --git a/internal/transfer/mocks/mocks.go b/internal/transfer/mocks/mocks.go index 1c8040e0..3b57b51d 100644 --- a/internal/transfer/mocks/mocks.go +++ b/internal/transfer/mocks/mocks.go @@ -12,65 +12,65 @@ import ( gomock "github.com/golang/mock/gomock" ) -// MockPanicHandler is a mock of PanicHandler interface +// MockPanicHandler is a mock of PanicHandler interface. type MockPanicHandler struct { ctrl *gomock.Controller recorder *MockPanicHandlerMockRecorder } -// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler +// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler. type MockPanicHandlerMockRecorder struct { mock *MockPanicHandler } -// NewMockPanicHandler creates a new mock instance +// NewMockPanicHandler creates a new mock instance. func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler { mock := &MockPanicHandler{ctrl: ctrl} mock.recorder = &MockPanicHandlerMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder { return m.recorder } -// HandlePanic mocks base method +// HandlePanic mocks base method. func (m *MockPanicHandler) HandlePanic() { m.ctrl.T.Helper() m.ctrl.Call(m, "HandlePanic") } -// HandlePanic indicates an expected call of HandlePanic +// HandlePanic indicates an expected call of HandlePanic. func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic)) } -// MockIMAPClientProvider is a mock of IMAPClientProvider interface +// MockIMAPClientProvider is a mock of IMAPClientProvider interface. type MockIMAPClientProvider struct { ctrl *gomock.Controller recorder *MockIMAPClientProviderMockRecorder } -// MockIMAPClientProviderMockRecorder is the mock recorder for MockIMAPClientProvider +// MockIMAPClientProviderMockRecorder is the mock recorder for MockIMAPClientProvider. type MockIMAPClientProviderMockRecorder struct { mock *MockIMAPClientProvider } -// NewMockIMAPClientProvider creates a new mock instance +// NewMockIMAPClientProvider creates a new mock instance. func NewMockIMAPClientProvider(ctrl *gomock.Controller) *MockIMAPClientProvider { mock := &MockIMAPClientProvider{ctrl: ctrl} mock.recorder = &MockIMAPClientProviderMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockIMAPClientProvider) EXPECT() *MockIMAPClientProviderMockRecorder { return m.recorder } -// Authenticate mocks base method +// Authenticate mocks base method. func (m *MockIMAPClientProvider) Authenticate(arg0 sasl.Client) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Authenticate", arg0) @@ -78,13 +78,13 @@ func (m *MockIMAPClientProvider) Authenticate(arg0 sasl.Client) error { return ret0 } -// Authenticate indicates an expected call of Authenticate +// Authenticate indicates an expected call of Authenticate. func (mr *MockIMAPClientProviderMockRecorder) Authenticate(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authenticate", reflect.TypeOf((*MockIMAPClientProvider)(nil).Authenticate), arg0) } -// Capability mocks base method +// Capability mocks base method. func (m *MockIMAPClientProvider) Capability() (map[string]bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Capability") @@ -93,13 +93,13 @@ func (m *MockIMAPClientProvider) Capability() (map[string]bool, error) { return ret0, ret1 } -// Capability indicates an expected call of Capability +// Capability indicates an expected call of Capability. func (mr *MockIMAPClientProviderMockRecorder) Capability() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Capability", reflect.TypeOf((*MockIMAPClientProvider)(nil).Capability)) } -// Fetch mocks base method +// Fetch mocks base method. func (m *MockIMAPClientProvider) Fetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem, arg2 chan *imap.Message) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Fetch", arg0, arg1, arg2) @@ -107,13 +107,13 @@ func (m *MockIMAPClientProvider) Fetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem, return ret0 } -// Fetch indicates an expected call of Fetch +// Fetch indicates an expected call of Fetch. func (mr *MockIMAPClientProviderMockRecorder) Fetch(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fetch", reflect.TypeOf((*MockIMAPClientProvider)(nil).Fetch), arg0, arg1, arg2) } -// List mocks base method +// List mocks base method. func (m *MockIMAPClientProvider) List(arg0, arg1 string, arg2 chan *imap.MailboxInfo) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "List", arg0, arg1, arg2) @@ -121,13 +121,13 @@ func (m *MockIMAPClientProvider) List(arg0, arg1 string, arg2 chan *imap.Mailbox return ret0 } -// List indicates an expected call of List +// List indicates an expected call of List. func (mr *MockIMAPClientProviderMockRecorder) List(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockIMAPClientProvider)(nil).List), arg0, arg1, arg2) } -// Login mocks base method +// Login mocks base method. func (m *MockIMAPClientProvider) Login(arg0, arg1 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Login", arg0, arg1) @@ -135,13 +135,13 @@ func (m *MockIMAPClientProvider) Login(arg0, arg1 string) error { return ret0 } -// Login indicates an expected call of Login +// Login indicates an expected call of Login. func (mr *MockIMAPClientProviderMockRecorder) Login(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockIMAPClientProvider)(nil).Login), arg0, arg1) } -// Select mocks base method +// Select mocks base method. func (m *MockIMAPClientProvider) Select(arg0 string, arg1 bool) (*imap.MailboxStatus, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Select", arg0, arg1) @@ -150,13 +150,13 @@ func (m *MockIMAPClientProvider) Select(arg0 string, arg1 bool) (*imap.MailboxSt return ret0, ret1 } -// Select indicates an expected call of Select +// Select indicates an expected call of Select. func (mr *MockIMAPClientProviderMockRecorder) Select(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*MockIMAPClientProvider)(nil).Select), arg0, arg1) } -// State mocks base method +// State mocks base method. func (m *MockIMAPClientProvider) State() imap.ConnState { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "State") @@ -164,13 +164,13 @@ func (m *MockIMAPClientProvider) State() imap.ConnState { return ret0 } -// State indicates an expected call of State +// State indicates an expected call of State. func (mr *MockIMAPClientProviderMockRecorder) State() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "State", reflect.TypeOf((*MockIMAPClientProvider)(nil).State)) } -// Support mocks base method +// Support mocks base method. func (m *MockIMAPClientProvider) Support(arg0 string) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Support", arg0) @@ -179,13 +179,13 @@ func (m *MockIMAPClientProvider) Support(arg0 string) (bool, error) { return ret0, ret1 } -// Support indicates an expected call of Support +// Support indicates an expected call of Support. func (mr *MockIMAPClientProviderMockRecorder) Support(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Support", reflect.TypeOf((*MockIMAPClientProvider)(nil).Support), arg0) } -// SupportAuth mocks base method +// SupportAuth mocks base method. func (m *MockIMAPClientProvider) SupportAuth(arg0 string) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SupportAuth", arg0) @@ -194,13 +194,13 @@ func (m *MockIMAPClientProvider) SupportAuth(arg0 string) (bool, error) { return ret0, ret1 } -// SupportAuth indicates an expected call of SupportAuth +// SupportAuth indicates an expected call of SupportAuth. func (mr *MockIMAPClientProviderMockRecorder) SupportAuth(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SupportAuth", reflect.TypeOf((*MockIMAPClientProvider)(nil).SupportAuth), arg0) } -// UidFetch mocks base method +// UidFetch mocks base method. func (m *MockIMAPClientProvider) UidFetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem, arg2 chan *imap.Message) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UidFetch", arg0, arg1, arg2) @@ -208,7 +208,7 @@ func (m *MockIMAPClientProvider) UidFetch(arg0 *imap.SeqSet, arg1 []imap.FetchIt return ret0 } -// UidFetch indicates an expected call of UidFetch +// UidFetch indicates an expected call of UidFetch. func (mr *MockIMAPClientProviderMockRecorder) UidFetch(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UidFetch", reflect.TypeOf((*MockIMAPClientProvider)(nil).UidFetch), arg0, arg1, arg2) diff --git a/internal/users/mocks/listener_mocks.go b/internal/users/mocks/listener_mocks.go index 8a6c8677..ec99aa03 100644 --- a/internal/users/mocks/listener_mocks.go +++ b/internal/users/mocks/listener_mocks.go @@ -11,54 +11,54 @@ import ( gomock "github.com/golang/mock/gomock" ) -// MockListener is a mock of Listener interface +// MockListener is a mock of Listener interface. type MockListener struct { ctrl *gomock.Controller recorder *MockListenerMockRecorder } -// MockListenerMockRecorder is the mock recorder for MockListener +// MockListenerMockRecorder is the mock recorder for MockListener. type MockListenerMockRecorder struct { mock *MockListener } -// NewMockListener creates a new mock instance +// NewMockListener creates a new mock instance. func NewMockListener(ctrl *gomock.Controller) *MockListener { mock := &MockListener{ctrl: ctrl} mock.recorder = &MockListenerMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockListener) EXPECT() *MockListenerMockRecorder { return m.recorder } -// Add mocks base method +// Add mocks base method. func (m *MockListener) Add(arg0 string, arg1 chan<- string) { m.ctrl.T.Helper() m.ctrl.Call(m, "Add", arg0, arg1) } -// Add indicates an expected call of Add +// Add indicates an expected call of Add. func (mr *MockListenerMockRecorder) Add(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockListener)(nil).Add), arg0, arg1) } -// Emit mocks base method +// Emit mocks base method. func (m *MockListener) Emit(arg0, arg1 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "Emit", arg0, arg1) } -// Emit indicates an expected call of Emit +// Emit indicates an expected call of Emit. func (mr *MockListenerMockRecorder) Emit(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Emit", reflect.TypeOf((*MockListener)(nil).Emit), arg0, arg1) } -// ProvideChannel mocks base method +// ProvideChannel mocks base method. func (m *MockListener) ProvideChannel(arg0 string) <-chan string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ProvideChannel", arg0) @@ -66,55 +66,55 @@ func (m *MockListener) ProvideChannel(arg0 string) <-chan string { return ret0 } -// ProvideChannel indicates an expected call of ProvideChannel +// ProvideChannel indicates an expected call of ProvideChannel. func (mr *MockListenerMockRecorder) ProvideChannel(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProvideChannel", reflect.TypeOf((*MockListener)(nil).ProvideChannel), arg0) } -// Remove mocks base method +// Remove mocks base method. func (m *MockListener) Remove(arg0 string, arg1 chan<- string) { m.ctrl.T.Helper() m.ctrl.Call(m, "Remove", arg0, arg1) } -// Remove indicates an expected call of Remove +// Remove indicates an expected call of Remove. func (mr *MockListenerMockRecorder) Remove(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockListener)(nil).Remove), arg0, arg1) } -// RetryEmit mocks base method +// RetryEmit mocks base method. func (m *MockListener) RetryEmit(arg0 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "RetryEmit", arg0) } -// RetryEmit indicates an expected call of RetryEmit +// RetryEmit indicates an expected call of RetryEmit. func (mr *MockListenerMockRecorder) RetryEmit(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryEmit", reflect.TypeOf((*MockListener)(nil).RetryEmit), arg0) } -// SetBuffer mocks base method +// SetBuffer mocks base method. func (m *MockListener) SetBuffer(arg0 string) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetBuffer", arg0) } -// SetBuffer indicates an expected call of SetBuffer +// SetBuffer indicates an expected call of SetBuffer. func (mr *MockListenerMockRecorder) SetBuffer(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBuffer", reflect.TypeOf((*MockListener)(nil).SetBuffer), arg0) } -// SetLimit mocks base method +// SetLimit mocks base method. func (m *MockListener) SetLimit(arg0 string, arg1 time.Duration) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetLimit", arg0, arg1) } -// SetLimit indicates an expected call of SetLimit +// SetLimit indicates an expected call of SetLimit. func (mr *MockListenerMockRecorder) SetLimit(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLimit", reflect.TypeOf((*MockListener)(nil).SetLimit), arg0, arg1) diff --git a/internal/users/mocks/mocks.go b/internal/users/mocks/mocks.go index 8293f297..83e327dd 100644 --- a/internal/users/mocks/mocks.go +++ b/internal/users/mocks/mocks.go @@ -12,30 +12,30 @@ import ( gomock "github.com/golang/mock/gomock" ) -// MockLocator is a mock of Locator interface +// MockLocator is a mock of Locator interface. type MockLocator struct { ctrl *gomock.Controller recorder *MockLocatorMockRecorder } -// MockLocatorMockRecorder is the mock recorder for MockLocator +// MockLocatorMockRecorder is the mock recorder for MockLocator. type MockLocatorMockRecorder struct { mock *MockLocator } -// NewMockLocator creates a new mock instance +// NewMockLocator creates a new mock instance. func NewMockLocator(ctrl *gomock.Controller) *MockLocator { mock := &MockLocator{ctrl: ctrl} mock.recorder = &MockLocatorMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockLocator) EXPECT() *MockLocatorMockRecorder { return m.recorder } -// Clear mocks base method +// Clear mocks base method. func (m *MockLocator) Clear() error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Clear") @@ -43,71 +43,71 @@ func (m *MockLocator) Clear() error { return ret0 } -// Clear indicates an expected call of Clear +// Clear indicates an expected call of Clear. func (mr *MockLocatorMockRecorder) Clear() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockLocator)(nil).Clear)) } -// MockPanicHandler is a mock of PanicHandler interface +// MockPanicHandler is a mock of PanicHandler interface. type MockPanicHandler struct { ctrl *gomock.Controller recorder *MockPanicHandlerMockRecorder } -// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler +// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler. type MockPanicHandlerMockRecorder struct { mock *MockPanicHandler } -// NewMockPanicHandler creates a new mock instance +// NewMockPanicHandler creates a new mock instance. func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler { mock := &MockPanicHandler{ctrl: ctrl} mock.recorder = &MockPanicHandlerMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder { return m.recorder } -// HandlePanic mocks base method +// HandlePanic mocks base method. func (m *MockPanicHandler) HandlePanic() { m.ctrl.T.Helper() m.ctrl.Call(m, "HandlePanic") } -// HandlePanic indicates an expected call of HandlePanic +// HandlePanic indicates an expected call of HandlePanic. func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic)) } -// MockCredentialsStorer is a mock of CredentialsStorer interface +// MockCredentialsStorer is a mock of CredentialsStorer interface. type MockCredentialsStorer struct { ctrl *gomock.Controller recorder *MockCredentialsStorerMockRecorder } -// MockCredentialsStorerMockRecorder is the mock recorder for MockCredentialsStorer +// MockCredentialsStorerMockRecorder is the mock recorder for MockCredentialsStorer. type MockCredentialsStorerMockRecorder struct { mock *MockCredentialsStorer } -// NewMockCredentialsStorer creates a new mock instance +// NewMockCredentialsStorer creates a new mock instance. func NewMockCredentialsStorer(ctrl *gomock.Controller) *MockCredentialsStorer { mock := &MockCredentialsStorer{ctrl: ctrl} mock.recorder = &MockCredentialsStorerMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockCredentialsStorer) EXPECT() *MockCredentialsStorerMockRecorder { return m.recorder } -// Add mocks base method +// Add mocks base method. func (m *MockCredentialsStorer) Add(arg0, arg1, arg2, arg3 string, arg4 []byte, arg5 []string) (*credentials.Credentials, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Add", arg0, arg1, arg2, arg3, arg4, arg5) @@ -116,13 +116,13 @@ func (m *MockCredentialsStorer) Add(arg0, arg1, arg2, arg3 string, arg4 []byte, return ret0, ret1 } -// Add indicates an expected call of Add +// Add indicates an expected call of Add. func (mr *MockCredentialsStorerMockRecorder) Add(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockCredentialsStorer)(nil).Add), arg0, arg1, arg2, arg3, arg4, arg5) } -// Delete mocks base method +// Delete mocks base method. func (m *MockCredentialsStorer) Delete(arg0 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Delete", arg0) @@ -130,13 +130,13 @@ func (m *MockCredentialsStorer) Delete(arg0 string) error { return ret0 } -// Delete indicates an expected call of Delete +// Delete indicates an expected call of Delete. func (mr *MockCredentialsStorerMockRecorder) Delete(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockCredentialsStorer)(nil).Delete), arg0) } -// Get mocks base method +// Get mocks base method. func (m *MockCredentialsStorer) Get(arg0 string) (*credentials.Credentials, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Get", arg0) @@ -145,13 +145,13 @@ func (m *MockCredentialsStorer) Get(arg0 string) (*credentials.Credentials, erro return ret0, ret1 } -// Get indicates an expected call of Get +// Get indicates an expected call of Get. func (mr *MockCredentialsStorerMockRecorder) Get(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockCredentialsStorer)(nil).Get), arg0) } -// List mocks base method +// List mocks base method. func (m *MockCredentialsStorer) List() ([]string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "List") @@ -160,13 +160,13 @@ func (m *MockCredentialsStorer) List() ([]string, error) { return ret0, ret1 } -// List indicates an expected call of List +// List indicates an expected call of List. func (mr *MockCredentialsStorerMockRecorder) List() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockCredentialsStorer)(nil).List)) } -// Logout mocks base method +// Logout mocks base method. func (m *MockCredentialsStorer) Logout(arg0 string) (*credentials.Credentials, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Logout", arg0) @@ -175,13 +175,13 @@ func (m *MockCredentialsStorer) Logout(arg0 string) (*credentials.Credentials, e return ret0, ret1 } -// Logout indicates an expected call of Logout +// Logout indicates an expected call of Logout. func (mr *MockCredentialsStorerMockRecorder) Logout(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockCredentialsStorer)(nil).Logout), arg0) } -// SwitchAddressMode mocks base method +// SwitchAddressMode mocks base method. func (m *MockCredentialsStorer) SwitchAddressMode(arg0 string) (*credentials.Credentials, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SwitchAddressMode", arg0) @@ -190,13 +190,13 @@ func (m *MockCredentialsStorer) SwitchAddressMode(arg0 string) (*credentials.Cre return ret0, ret1 } -// SwitchAddressMode indicates an expected call of SwitchAddressMode +// SwitchAddressMode indicates an expected call of SwitchAddressMode. func (mr *MockCredentialsStorerMockRecorder) SwitchAddressMode(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SwitchAddressMode", reflect.TypeOf((*MockCredentialsStorer)(nil).SwitchAddressMode), arg0) } -// UpdateEmails mocks base method +// UpdateEmails mocks base method. func (m *MockCredentialsStorer) UpdateEmails(arg0 string, arg1 []string) (*credentials.Credentials, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateEmails", arg0, arg1) @@ -205,13 +205,13 @@ func (m *MockCredentialsStorer) UpdateEmails(arg0 string, arg1 []string) (*crede return ret0, ret1 } -// UpdateEmails indicates an expected call of UpdateEmails +// UpdateEmails indicates an expected call of UpdateEmails. func (mr *MockCredentialsStorerMockRecorder) UpdateEmails(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEmails", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateEmails), arg0, arg1) } -// UpdatePassword mocks base method +// UpdatePassword mocks base method. func (m *MockCredentialsStorer) UpdatePassword(arg0 string, arg1 []byte) (*credentials.Credentials, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdatePassword", arg0, arg1) @@ -220,13 +220,13 @@ func (m *MockCredentialsStorer) UpdatePassword(arg0 string, arg1 []byte) (*crede return ret0, ret1 } -// UpdatePassword indicates an expected call of UpdatePassword +// UpdatePassword indicates an expected call of UpdatePassword. func (mr *MockCredentialsStorerMockRecorder) UpdatePassword(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePassword", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdatePassword), arg0, arg1) } -// UpdateToken mocks base method +// UpdateToken mocks base method. func (m *MockCredentialsStorer) UpdateToken(arg0, arg1, arg2 string) (*credentials.Credentials, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateToken", arg0, arg1, arg2) @@ -235,36 +235,36 @@ func (m *MockCredentialsStorer) UpdateToken(arg0, arg1, arg2 string) (*credentia return ret0, ret1 } -// UpdateToken indicates an expected call of UpdateToken +// UpdateToken indicates an expected call of UpdateToken. func (mr *MockCredentialsStorerMockRecorder) UpdateToken(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateToken", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateToken), arg0, arg1, arg2) } -// MockStoreMaker is a mock of StoreMaker interface +// MockStoreMaker is a mock of StoreMaker interface. type MockStoreMaker struct { ctrl *gomock.Controller recorder *MockStoreMakerMockRecorder } -// MockStoreMakerMockRecorder is the mock recorder for MockStoreMaker +// MockStoreMakerMockRecorder is the mock recorder for MockStoreMaker. type MockStoreMakerMockRecorder struct { mock *MockStoreMaker } -// NewMockStoreMaker creates a new mock instance +// NewMockStoreMaker creates a new mock instance. func NewMockStoreMaker(ctrl *gomock.Controller) *MockStoreMaker { mock := &MockStoreMaker{ctrl: ctrl} mock.recorder = &MockStoreMakerMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockStoreMaker) EXPECT() *MockStoreMakerMockRecorder { return m.recorder } -// New mocks base method +// New mocks base method. func (m *MockStoreMaker) New(arg0 store.BridgeUser) (*store.Store, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "New", arg0) @@ -273,13 +273,13 @@ func (m *MockStoreMaker) New(arg0 store.BridgeUser) (*store.Store, error) { return ret0, ret1 } -// New indicates an expected call of New +// New indicates an expected call of New. func (mr *MockStoreMakerMockRecorder) New(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockStoreMaker)(nil).New), arg0) } -// Remove mocks base method +// Remove mocks base method. func (m *MockStoreMaker) Remove(arg0 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Remove", arg0) @@ -287,7 +287,7 @@ func (m *MockStoreMaker) Remove(arg0 string) error { return ret0 } -// Remove indicates an expected call of Remove +// Remove indicates an expected call of Remove. func (mr *MockStoreMakerMockRecorder) Remove(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockStoreMaker)(nil).Remove), arg0) diff --git a/pkg/message/build_framework_test.go b/pkg/message/build_framework_test.go index 4e00ce00..16ddfed6 100644 --- a/pkg/message/build_framework_test.go +++ b/pkg/message/build_framework_test.go @@ -33,6 +33,7 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/text/encoding/htmlindex" ) func newTestFetcher( @@ -298,6 +299,25 @@ func decryptsTo(kr *crypto.KeyRing, want string) decryptsToMatcher { return decryptsToMatcher{kr: kr, want: want} } +type decodesToMatcher struct { + charset string + want string +} + +func (matcher decodesToMatcher) match(t *testing.T, have string) { + enc, err := htmlindex.Get(matcher.charset) + require.NoError(t, err) + + dec, err := enc.NewDecoder().String(have) + require.NoError(t, err) + + assert.Equal(t, matcher.want, dec) +} + +func decodesTo(charset string, want string) decodesToMatcher { + return decodesToMatcher{charset: charset, want: want} +} + type verifiesAgainstMatcher struct { kr *crypto.KeyRing sig *crypto.PGPSignature diff --git a/pkg/message/build_rfc822.go b/pkg/message/build_rfc822.go index 36366ca9..50471a3b 100644 --- a/pkg/message/build_rfc822.go +++ b/pkg/message/build_rfc822.go @@ -20,7 +20,6 @@ package message import ( "bytes" "encoding/base64" - "io/ioutil" "mime" "net/mail" "strings" @@ -290,17 +289,12 @@ func writeMultipartSignedRFC822(header message.Header, body []byte, sig pmapi.Si return nil, err } - ent, err := message.Read(bytes.NewReader(body)) + bodyHeader, bodyData, err := readHeaderBody(body) if err != nil { return nil, err } - bodyPart, err := w.CreatePart(ent.Header) - if err != nil { - return nil, err - } - - bodyData, err := ioutil.ReadAll(ent.Body) + bodyPart, err := w.CreatePart(message.Header{Header: *bodyHeader}) if err != nil { return nil, err } @@ -347,12 +341,12 @@ func writeMultipartSignedRFC822(header message.Header, body []byte, sig pmapi.Si func writeMultipartEncryptedRFC822(header message.Header, body []byte) ([]byte, error) { buf := new(bytes.Buffer) - ent, err := message.Read(bytes.NewReader(body)) + bodyHeader, bodyData, err := readHeaderBody(body) if err != nil { return nil, err } - entFields := ent.Header.Fields() + entFields := bodyHeader.Fields() for entFields.Next() { header.Set(entFields.Key(), entFields.Value()) @@ -363,11 +357,6 @@ func writeMultipartEncryptedRFC822(header message.Header, body []byte) ([]byte, return nil, err } - bodyData, err := ioutil.ReadAll(ent.Body) - if err != nil { - return nil, err - } - if _, err := w.Write(bodyData); err != nil { return nil, err } diff --git a/pkg/message/build_test.go b/pkg/message/build_test.go index c9f7fe70..f692a33c 100644 --- a/pkg/message/build_test.go +++ b/pkg/message/build_test.go @@ -98,6 +98,31 @@ func TestBuildPlainEncryptedMessage(t *testing.T) { expectBody(contains(`Where do fruits go on vacation? Pear-is!`)) } +func TestBuildPlainEncryptedLatin2Message(t *testing.T) { + m := gomock.NewController(t) + defer m.Finish() + + b := NewBuilder(1, 1, 1) + defer b.Done() + + body := readerToString(getFileReader("pgp-mime-body-plaintext-latin2.eml")) + + kr := tests.MakeKeyRing(t) + msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)) + + res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult() + require.NoError(t, err) + + section(t, res). + expectContentType(is(`text/plain`)). + expectContentTypeParam("charset", is(`iso-8859-2`)). + expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)). + expectHeader(`Subject`, is(`plain no pubkey no sign`)). + expectHeader(`From`, is(`"pm.bridge.qa" `)). + expectHeader(`To`, is(`schizofrenic@pm.me`)). + expectBody(decodesTo("iso-8859-2", "řšřšřš\r\n")) +} + func TestBuildHTMLEncryptedMessage(t *testing.T) { m := gomock.NewController(t) defer m.Finish() diff --git a/pkg/message/mocks/mocks.go b/pkg/message/mocks/mocks.go index 0aa4b7ed..4c58525d 100644 --- a/pkg/message/mocks/mocks.go +++ b/pkg/message/mocks/mocks.go @@ -14,30 +14,30 @@ import ( gomock "github.com/golang/mock/gomock" ) -// MockFetcher is a mock of Fetcher interface +// MockFetcher is a mock of Fetcher interface. type MockFetcher struct { ctrl *gomock.Controller recorder *MockFetcherMockRecorder } -// MockFetcherMockRecorder is the mock recorder for MockFetcher +// MockFetcherMockRecorder is the mock recorder for MockFetcher. type MockFetcherMockRecorder struct { mock *MockFetcher } -// NewMockFetcher creates a new mock instance +// NewMockFetcher creates a new mock instance. func NewMockFetcher(ctrl *gomock.Controller) *MockFetcher { mock := &MockFetcher{ctrl: ctrl} mock.recorder = &MockFetcherMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockFetcher) EXPECT() *MockFetcherMockRecorder { return m.recorder } -// GetAttachment mocks base method +// GetAttachment mocks base method. func (m *MockFetcher) GetAttachment(arg0 context.Context, arg1 string) (io.ReadCloser, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAttachment", arg0, arg1) @@ -46,13 +46,13 @@ func (m *MockFetcher) GetAttachment(arg0 context.Context, arg1 string) (io.ReadC return ret0, ret1 } -// GetAttachment indicates an expected call of GetAttachment +// GetAttachment indicates an expected call of GetAttachment. func (mr *MockFetcherMockRecorder) GetAttachment(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAttachment", reflect.TypeOf((*MockFetcher)(nil).GetAttachment), arg0, arg1) } -// GetMessage mocks base method +// GetMessage mocks base method. func (m *MockFetcher) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Message, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetMessage", arg0, arg1) @@ -61,13 +61,13 @@ func (m *MockFetcher) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Mess return ret0, ret1 } -// GetMessage indicates an expected call of GetMessage +// GetMessage indicates an expected call of GetMessage. func (mr *MockFetcherMockRecorder) GetMessage(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockFetcher)(nil).GetMessage), arg0, arg1) } -// KeyRingForAddressID mocks base method +// KeyRingForAddressID mocks base method. func (m *MockFetcher) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "KeyRingForAddressID", arg0) @@ -76,7 +76,7 @@ func (m *MockFetcher) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error) return ret0, ret1 } -// KeyRingForAddressID indicates an expected call of KeyRingForAddressID +// KeyRingForAddressID indicates an expected call of KeyRingForAddressID. func (mr *MockFetcherMockRecorder) KeyRingForAddressID(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyRingForAddressID", reflect.TypeOf((*MockFetcher)(nil).KeyRingForAddressID), arg0) diff --git a/pkg/message/testdata/pgp-mime-body-plaintext-latin2.eml b/pkg/message/testdata/pgp-mime-body-plaintext-latin2.eml new file mode 100644 index 00000000..d971a446 --- /dev/null +++ b/pkg/message/testdata/pgp-mime-body-plaintext-latin2.eml @@ -0,0 +1,8 @@ +Subject: plain no pubkey no sign +From: "pm.bridge.qa" +To: schizofrenic@pm.me +Message-ID: <564b9c7c-91eb-6508-107a-35108f383a44@gmail.com> +Content-Type: text/plain; charset=iso-8859-2; format=flowed +Content-Language: en-US + +ø¹ø¹ø¹ diff --git a/pkg/pmapi/auth.go b/pkg/pmapi/auth.go index 8773278d..d58b1060 100644 --- a/pkg/pmapi/auth.go +++ b/pkg/pmapi/auth.go @@ -217,3 +217,13 @@ func randomString(length int) string { return base64.StdEncoding.EncodeToString(noise)[:length] } + +func (c *client) GetCurrentAuth() *Auth { + return &Auth{ + UserID: c.user.ID, + AuthRefresh: AuthRefresh{ + UID: c.uid, + RefreshToken: c.ref, + }, + } +} diff --git a/pkg/pmapi/mocks/mocks.go b/pkg/pmapi/mocks/mocks.go index 78492681..03085507 100644 --- a/pkg/pmapi/mocks/mocks.go +++ b/pkg/pmapi/mocks/mocks.go @@ -17,42 +17,42 @@ import ( logrus "github.com/sirupsen/logrus" ) -// MockClient is a mock of Client interface +// MockClient is a mock of Client interface. type MockClient struct { ctrl *gomock.Controller recorder *MockClientMockRecorder } -// MockClientMockRecorder is the mock recorder for MockClient +// MockClientMockRecorder is the mock recorder for MockClient. type MockClientMockRecorder struct { mock *MockClient } -// NewMockClient creates a new mock instance +// NewMockClient creates a new mock instance. func NewMockClient(ctrl *gomock.Controller) *MockClient { mock := &MockClient{ctrl: ctrl} mock.recorder = &MockClientMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockClient) EXPECT() *MockClientMockRecorder { return m.recorder } -// AddAuthRefreshHandler mocks base method +// AddAuthRefreshHandler mocks base method. func (m *MockClient) AddAuthRefreshHandler(arg0 pmapi.AuthRefreshHandler) { m.ctrl.T.Helper() m.ctrl.Call(m, "AddAuthRefreshHandler", arg0) } -// AddAuthRefreshHandler indicates an expected call of AddAuthRefreshHandler +// AddAuthRefreshHandler indicates an expected call of AddAuthRefreshHandler. func (mr *MockClientMockRecorder) AddAuthRefreshHandler(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAuthRefreshHandler", reflect.TypeOf((*MockClient)(nil).AddAuthRefreshHandler), arg0) } -// Addresses mocks base method +// Addresses mocks base method. func (m *MockClient) Addresses() pmapi.AddressList { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Addresses") @@ -60,13 +60,13 @@ func (m *MockClient) Addresses() pmapi.AddressList { return ret0 } -// Addresses indicates an expected call of Addresses +// Addresses indicates an expected call of Addresses. func (mr *MockClientMockRecorder) Addresses() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addresses", reflect.TypeOf((*MockClient)(nil).Addresses)) } -// Auth2FA mocks base method +// Auth2FA mocks base method. func (m *MockClient) Auth2FA(arg0 context.Context, arg1 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Auth2FA", arg0, arg1) @@ -74,13 +74,13 @@ func (m *MockClient) Auth2FA(arg0 context.Context, arg1 string) error { return ret0 } -// Auth2FA indicates an expected call of Auth2FA +// Auth2FA indicates an expected call of Auth2FA. func (mr *MockClientMockRecorder) Auth2FA(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Auth2FA", reflect.TypeOf((*MockClient)(nil).Auth2FA), arg0, arg1) } -// AuthDelete mocks base method +// AuthDelete mocks base method. func (m *MockClient) AuthDelete(arg0 context.Context) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AuthDelete", arg0) @@ -88,13 +88,13 @@ func (m *MockClient) AuthDelete(arg0 context.Context) error { return ret0 } -// AuthDelete indicates an expected call of AuthDelete +// AuthDelete indicates an expected call of AuthDelete. func (mr *MockClientMockRecorder) AuthDelete(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthDelete", reflect.TypeOf((*MockClient)(nil).AuthDelete), arg0) } -// AuthSalt mocks base method +// AuthSalt mocks base method. func (m *MockClient) AuthSalt(arg0 context.Context) (string, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AuthSalt", arg0) @@ -103,13 +103,13 @@ func (m *MockClient) AuthSalt(arg0 context.Context) (string, error) { return ret0, ret1 } -// AuthSalt indicates an expected call of AuthSalt +// AuthSalt indicates an expected call of AuthSalt. func (mr *MockClientMockRecorder) AuthSalt(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthSalt", reflect.TypeOf((*MockClient)(nil).AuthSalt), arg0) } -// CountMessages mocks base method +// CountMessages mocks base method. func (m *MockClient) CountMessages(arg0 context.Context, arg1 string) ([]*pmapi.MessagesCount, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CountMessages", arg0, arg1) @@ -118,13 +118,13 @@ func (m *MockClient) CountMessages(arg0 context.Context, arg1 string) ([]*pmapi. return ret0, ret1 } -// CountMessages indicates an expected call of CountMessages +// CountMessages indicates an expected call of CountMessages. func (mr *MockClientMockRecorder) CountMessages(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountMessages", reflect.TypeOf((*MockClient)(nil).CountMessages), arg0, arg1) } -// CreateAttachment mocks base method +// CreateAttachment mocks base method. func (m *MockClient) CreateAttachment(arg0 context.Context, arg1 *pmapi.Attachment, arg2, arg3 io.Reader) (*pmapi.Attachment, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateAttachment", arg0, arg1, arg2, arg3) @@ -133,13 +133,13 @@ func (m *MockClient) CreateAttachment(arg0 context.Context, arg1 *pmapi.Attachme return ret0, ret1 } -// CreateAttachment indicates an expected call of CreateAttachment +// CreateAttachment indicates an expected call of CreateAttachment. func (mr *MockClientMockRecorder) CreateAttachment(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAttachment", reflect.TypeOf((*MockClient)(nil).CreateAttachment), arg0, arg1, arg2, arg3) } -// CreateDraft mocks base method +// CreateDraft mocks base method. func (m *MockClient) CreateDraft(arg0 context.Context, arg1 *pmapi.Message, arg2 string, arg3 int) (*pmapi.Message, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateDraft", arg0, arg1, arg2, arg3) @@ -148,13 +148,13 @@ func (m *MockClient) CreateDraft(arg0 context.Context, arg1 *pmapi.Message, arg2 return ret0, ret1 } -// CreateDraft indicates an expected call of CreateDraft +// CreateDraft indicates an expected call of CreateDraft. func (mr *MockClientMockRecorder) CreateDraft(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateDraft", reflect.TypeOf((*MockClient)(nil).CreateDraft), arg0, arg1, arg2, arg3) } -// CreateLabel mocks base method +// CreateLabel mocks base method. func (m *MockClient) CreateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmapi.Label, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateLabel", arg0, arg1) @@ -163,13 +163,13 @@ func (m *MockClient) CreateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmap return ret0, ret1 } -// CreateLabel indicates an expected call of CreateLabel +// CreateLabel indicates an expected call of CreateLabel. func (mr *MockClientMockRecorder) CreateLabel(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLabel", reflect.TypeOf((*MockClient)(nil).CreateLabel), arg0, arg1) } -// CurrentUser mocks base method +// CurrentUser mocks base method. func (m *MockClient) CurrentUser(arg0 context.Context) (*pmapi.User, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CurrentUser", arg0) @@ -178,13 +178,13 @@ func (m *MockClient) CurrentUser(arg0 context.Context) (*pmapi.User, error) { return ret0, ret1 } -// CurrentUser indicates an expected call of CurrentUser +// CurrentUser indicates an expected call of CurrentUser. func (mr *MockClientMockRecorder) CurrentUser(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentUser", reflect.TypeOf((*MockClient)(nil).CurrentUser), arg0) } -// DecryptAndVerifyCards mocks base method +// DecryptAndVerifyCards mocks base method. func (m *MockClient) DecryptAndVerifyCards(arg0 []pmapi.Card) ([]pmapi.Card, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DecryptAndVerifyCards", arg0) @@ -193,13 +193,13 @@ func (m *MockClient) DecryptAndVerifyCards(arg0 []pmapi.Card) ([]pmapi.Card, err return ret0, ret1 } -// DecryptAndVerifyCards indicates an expected call of DecryptAndVerifyCards +// DecryptAndVerifyCards indicates an expected call of DecryptAndVerifyCards. func (mr *MockClientMockRecorder) DecryptAndVerifyCards(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptAndVerifyCards", reflect.TypeOf((*MockClient)(nil).DecryptAndVerifyCards), arg0) } -// DeleteLabel mocks base method +// DeleteLabel mocks base method. func (m *MockClient) DeleteLabel(arg0 context.Context, arg1 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DeleteLabel", arg0, arg1) @@ -207,13 +207,13 @@ func (m *MockClient) DeleteLabel(arg0 context.Context, arg1 string) error { return ret0 } -// DeleteLabel indicates an expected call of DeleteLabel +// DeleteLabel indicates an expected call of DeleteLabel. func (mr *MockClientMockRecorder) DeleteLabel(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLabel", reflect.TypeOf((*MockClient)(nil).DeleteLabel), arg0, arg1) } -// DeleteMessages mocks base method +// DeleteMessages mocks base method. func (m *MockClient) DeleteMessages(arg0 context.Context, arg1 []string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DeleteMessages", arg0, arg1) @@ -221,13 +221,13 @@ func (m *MockClient) DeleteMessages(arg0 context.Context, arg1 []string) error { return ret0 } -// DeleteMessages indicates an expected call of DeleteMessages +// DeleteMessages indicates an expected call of DeleteMessages. func (mr *MockClientMockRecorder) DeleteMessages(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessages", reflect.TypeOf((*MockClient)(nil).DeleteMessages), arg0, arg1) } -// EmptyFolder mocks base method +// EmptyFolder mocks base method. func (m *MockClient) EmptyFolder(arg0 context.Context, arg1, arg2 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "EmptyFolder", arg0, arg1, arg2) @@ -235,13 +235,13 @@ func (m *MockClient) EmptyFolder(arg0 context.Context, arg1, arg2 string) error return ret0 } -// EmptyFolder indicates an expected call of EmptyFolder +// EmptyFolder indicates an expected call of EmptyFolder. func (mr *MockClientMockRecorder) EmptyFolder(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EmptyFolder", reflect.TypeOf((*MockClient)(nil).EmptyFolder), arg0, arg1, arg2) } -// GetAddresses mocks base method +// GetAddresses mocks base method. func (m *MockClient) GetAddresses(arg0 context.Context) (pmapi.AddressList, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAddresses", arg0) @@ -250,13 +250,13 @@ func (m *MockClient) GetAddresses(arg0 context.Context) (pmapi.AddressList, erro return ret0, ret1 } -// GetAddresses indicates an expected call of GetAddresses +// GetAddresses indicates an expected call of GetAddresses. func (mr *MockClientMockRecorder) GetAddresses(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAddresses", reflect.TypeOf((*MockClient)(nil).GetAddresses), arg0) } -// GetAttachment mocks base method +// GetAttachment mocks base method. func (m *MockClient) GetAttachment(arg0 context.Context, arg1 string) (io.ReadCloser, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAttachment", arg0, arg1) @@ -265,13 +265,13 @@ func (m *MockClient) GetAttachment(arg0 context.Context, arg1 string) (io.ReadCl return ret0, ret1 } -// GetAttachment indicates an expected call of GetAttachment +// GetAttachment indicates an expected call of GetAttachment. func (mr *MockClientMockRecorder) GetAttachment(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAttachment", reflect.TypeOf((*MockClient)(nil).GetAttachment), arg0, arg1) } -// GetContactByID mocks base method +// GetContactByID mocks base method. func (m *MockClient) GetContactByID(arg0 context.Context, arg1 string) (pmapi.Contact, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetContactByID", arg0, arg1) @@ -280,13 +280,13 @@ func (m *MockClient) GetContactByID(arg0 context.Context, arg1 string) (pmapi.Co return ret0, ret1 } -// GetContactByID indicates an expected call of GetContactByID +// GetContactByID indicates an expected call of GetContactByID. func (mr *MockClientMockRecorder) GetContactByID(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContactByID", reflect.TypeOf((*MockClient)(nil).GetContactByID), arg0, arg1) } -// GetContactEmailByEmail mocks base method +// GetContactEmailByEmail mocks base method. func (m *MockClient) GetContactEmailByEmail(arg0 context.Context, arg1 string, arg2, arg3 int) ([]pmapi.ContactEmail, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetContactEmailByEmail", arg0, arg1, arg2, arg3) @@ -295,13 +295,13 @@ func (m *MockClient) GetContactEmailByEmail(arg0 context.Context, arg1 string, a return ret0, ret1 } -// GetContactEmailByEmail indicates an expected call of GetContactEmailByEmail +// GetContactEmailByEmail indicates an expected call of GetContactEmailByEmail. func (mr *MockClientMockRecorder) GetContactEmailByEmail(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContactEmailByEmail", reflect.TypeOf((*MockClient)(nil).GetContactEmailByEmail), arg0, arg1, arg2, arg3) } -// GetEvent mocks base method +// GetEvent mocks base method. func (m *MockClient) GetEvent(arg0 context.Context, arg1 string) (*pmapi.Event, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEvent", arg0, arg1) @@ -310,13 +310,13 @@ func (m *MockClient) GetEvent(arg0 context.Context, arg1 string) (*pmapi.Event, return ret0, ret1 } -// GetEvent indicates an expected call of GetEvent +// GetEvent indicates an expected call of GetEvent. func (mr *MockClientMockRecorder) GetEvent(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEvent", reflect.TypeOf((*MockClient)(nil).GetEvent), arg0, arg1) } -// GetMailSettings mocks base method +// GetMailSettings mocks base method. func (m *MockClient) GetMailSettings(arg0 context.Context) (pmapi.MailSettings, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetMailSettings", arg0) @@ -325,13 +325,13 @@ func (m *MockClient) GetMailSettings(arg0 context.Context) (pmapi.MailSettings, return ret0, ret1 } -// GetMailSettings indicates an expected call of GetMailSettings +// GetMailSettings indicates an expected call of GetMailSettings. func (mr *MockClientMockRecorder) GetMailSettings(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMailSettings", reflect.TypeOf((*MockClient)(nil).GetMailSettings), arg0) } -// GetMessage mocks base method +// GetMessage mocks base method. func (m *MockClient) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Message, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetMessage", arg0, arg1) @@ -340,13 +340,13 @@ func (m *MockClient) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Messa return ret0, ret1 } -// GetMessage indicates an expected call of GetMessage +// GetMessage indicates an expected call of GetMessage. func (mr *MockClientMockRecorder) GetMessage(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockClient)(nil).GetMessage), arg0, arg1) } -// GetPublicKeysForEmail mocks base method +// GetPublicKeysForEmail mocks base method. func (m *MockClient) GetPublicKeysForEmail(arg0 context.Context, arg1 string) ([]pmapi.PublicKey, bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPublicKeysForEmail", arg0, arg1) @@ -356,13 +356,13 @@ func (m *MockClient) GetPublicKeysForEmail(arg0 context.Context, arg1 string) ([ return ret0, ret1, ret2 } -// GetPublicKeysForEmail indicates an expected call of GetPublicKeysForEmail +// GetPublicKeysForEmail indicates an expected call of GetPublicKeysForEmail. func (mr *MockClientMockRecorder) GetPublicKeysForEmail(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicKeysForEmail", reflect.TypeOf((*MockClient)(nil).GetPublicKeysForEmail), arg0, arg1) } -// Import mocks base method +// Import mocks base method. func (m *MockClient) Import(arg0 context.Context, arg1 pmapi.ImportMsgReqs) ([]*pmapi.ImportMsgRes, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Import", arg0, arg1) @@ -371,13 +371,13 @@ func (m *MockClient) Import(arg0 context.Context, arg1 pmapi.ImportMsgReqs) ([]* return ret0, ret1 } -// Import indicates an expected call of Import +// Import indicates an expected call of Import. func (mr *MockClientMockRecorder) Import(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Import", reflect.TypeOf((*MockClient)(nil).Import), arg0, arg1) } -// IsUnlocked mocks base method +// IsUnlocked mocks base method. func (m *MockClient) IsUnlocked() bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "IsUnlocked") @@ -385,13 +385,13 @@ func (m *MockClient) IsUnlocked() bool { return ret0 } -// IsUnlocked indicates an expected call of IsUnlocked +// IsUnlocked indicates an expected call of IsUnlocked. func (mr *MockClientMockRecorder) IsUnlocked() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsUnlocked", reflect.TypeOf((*MockClient)(nil).IsUnlocked)) } -// KeyRingForAddressID mocks base method +// KeyRingForAddressID mocks base method. func (m *MockClient) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "KeyRingForAddressID", arg0) @@ -400,13 +400,13 @@ func (m *MockClient) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error) { return ret0, ret1 } -// KeyRingForAddressID indicates an expected call of KeyRingForAddressID +// KeyRingForAddressID indicates an expected call of KeyRingForAddressID. func (mr *MockClientMockRecorder) KeyRingForAddressID(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyRingForAddressID", reflect.TypeOf((*MockClient)(nil).KeyRingForAddressID), arg0) } -// LabelMessages mocks base method +// LabelMessages mocks base method. func (m *MockClient) LabelMessages(arg0 context.Context, arg1 []string, arg2 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "LabelMessages", arg0, arg1, arg2) @@ -414,13 +414,13 @@ func (m *MockClient) LabelMessages(arg0 context.Context, arg1 []string, arg2 str return ret0 } -// LabelMessages indicates an expected call of LabelMessages +// LabelMessages indicates an expected call of LabelMessages. func (mr *MockClientMockRecorder) LabelMessages(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LabelMessages", reflect.TypeOf((*MockClient)(nil).LabelMessages), arg0, arg1, arg2) } -// ListLabels mocks base method +// ListLabels mocks base method. func (m *MockClient) ListLabels(arg0 context.Context) ([]*pmapi.Label, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListLabels", arg0) @@ -429,13 +429,13 @@ func (m *MockClient) ListLabels(arg0 context.Context) ([]*pmapi.Label, error) { return ret0, ret1 } -// ListLabels indicates an expected call of ListLabels +// ListLabels indicates an expected call of ListLabels. func (mr *MockClientMockRecorder) ListLabels(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLabels", reflect.TypeOf((*MockClient)(nil).ListLabels), arg0) } -// ListMessages mocks base method +// ListMessages mocks base method. func (m *MockClient) ListMessages(arg0 context.Context, arg1 *pmapi.MessagesFilter) ([]*pmapi.Message, int, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListMessages", arg0, arg1) @@ -445,13 +445,13 @@ func (m *MockClient) ListMessages(arg0 context.Context, arg1 *pmapi.MessagesFilt return ret0, ret1, ret2 } -// ListMessages indicates an expected call of ListMessages +// ListMessages indicates an expected call of ListMessages. func (mr *MockClientMockRecorder) ListMessages(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMessages", reflect.TypeOf((*MockClient)(nil).ListMessages), arg0, arg1) } -// MarkMessagesRead mocks base method +// MarkMessagesRead mocks base method. func (m *MockClient) MarkMessagesRead(arg0 context.Context, arg1 []string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "MarkMessagesRead", arg0, arg1) @@ -459,13 +459,13 @@ func (m *MockClient) MarkMessagesRead(arg0 context.Context, arg1 []string) error return ret0 } -// MarkMessagesRead indicates an expected call of MarkMessagesRead +// MarkMessagesRead indicates an expected call of MarkMessagesRead. func (mr *MockClientMockRecorder) MarkMessagesRead(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkMessagesRead", reflect.TypeOf((*MockClient)(nil).MarkMessagesRead), arg0, arg1) } -// MarkMessagesUnread mocks base method +// MarkMessagesUnread mocks base method. func (m *MockClient) MarkMessagesUnread(arg0 context.Context, arg1 []string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "MarkMessagesUnread", arg0, arg1) @@ -473,13 +473,13 @@ func (m *MockClient) MarkMessagesUnread(arg0 context.Context, arg1 []string) err return ret0 } -// MarkMessagesUnread indicates an expected call of MarkMessagesUnread +// MarkMessagesUnread indicates an expected call of MarkMessagesUnread. func (mr *MockClientMockRecorder) MarkMessagesUnread(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkMessagesUnread", reflect.TypeOf((*MockClient)(nil).MarkMessagesUnread), arg0, arg1) } -// ReloadKeys mocks base method +// ReloadKeys mocks base method. func (m *MockClient) ReloadKeys(arg0 context.Context, arg1 []byte) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ReloadKeys", arg0, arg1) @@ -487,13 +487,13 @@ func (m *MockClient) ReloadKeys(arg0 context.Context, arg1 []byte) error { return ret0 } -// ReloadKeys indicates an expected call of ReloadKeys +// ReloadKeys indicates an expected call of ReloadKeys. func (mr *MockClientMockRecorder) ReloadKeys(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReloadKeys", reflect.TypeOf((*MockClient)(nil).ReloadKeys), arg0, arg1) } -// ReorderAddresses mocks base method +// ReorderAddresses mocks base method. func (m *MockClient) ReorderAddresses(arg0 context.Context, arg1 []string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ReorderAddresses", arg0, arg1) @@ -501,13 +501,13 @@ func (m *MockClient) ReorderAddresses(arg0 context.Context, arg1 []string) error return ret0 } -// ReorderAddresses indicates an expected call of ReorderAddresses +// ReorderAddresses indicates an expected call of ReorderAddresses. func (mr *MockClientMockRecorder) ReorderAddresses(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReorderAddresses", reflect.TypeOf((*MockClient)(nil).ReorderAddresses), arg0, arg1) } -// SendMessage mocks base method +// SendMessage mocks base method. func (m *MockClient) SendMessage(arg0 context.Context, arg1 string, arg2 *pmapi.SendMessageReq) (*pmapi.Message, *pmapi.Message, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendMessage", arg0, arg1, arg2) @@ -517,13 +517,13 @@ func (m *MockClient) SendMessage(arg0 context.Context, arg1 string, arg2 *pmapi. return ret0, ret1, ret2 } -// SendMessage indicates an expected call of SendMessage +// SendMessage indicates an expected call of SendMessage. func (mr *MockClientMockRecorder) SendMessage(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockClient)(nil).SendMessage), arg0, arg1, arg2) } -// UnlabelMessages mocks base method +// UnlabelMessages mocks base method. func (m *MockClient) UnlabelMessages(arg0 context.Context, arg1 []string, arg2 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UnlabelMessages", arg0, arg1, arg2) @@ -531,13 +531,13 @@ func (m *MockClient) UnlabelMessages(arg0 context.Context, arg1 []string, arg2 s return ret0 } -// UnlabelMessages indicates an expected call of UnlabelMessages +// UnlabelMessages indicates an expected call of UnlabelMessages. func (mr *MockClientMockRecorder) UnlabelMessages(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlabelMessages", reflect.TypeOf((*MockClient)(nil).UnlabelMessages), arg0, arg1, arg2) } -// Unlock mocks base method +// Unlock mocks base method. func (m *MockClient) Unlock(arg0 context.Context, arg1 []byte) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Unlock", arg0, arg1) @@ -545,13 +545,13 @@ func (m *MockClient) Unlock(arg0 context.Context, arg1 []byte) error { return ret0 } -// Unlock indicates an expected call of Unlock +// Unlock indicates an expected call of Unlock. func (mr *MockClientMockRecorder) Unlock(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockClient)(nil).Unlock), arg0, arg1) } -// UpdateLabel mocks base method +// UpdateLabel mocks base method. func (m *MockClient) UpdateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmapi.Label, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateLabel", arg0, arg1) @@ -560,13 +560,13 @@ func (m *MockClient) UpdateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmap return ret0, ret1 } -// UpdateLabel indicates an expected call of UpdateLabel +// UpdateLabel indicates an expected call of UpdateLabel. func (mr *MockClientMockRecorder) UpdateLabel(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLabel", reflect.TypeOf((*MockClient)(nil).UpdateLabel), arg0, arg1) } -// UpdateUser mocks base method +// UpdateUser mocks base method. func (m *MockClient) UpdateUser(arg0 context.Context) (*pmapi.User, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "UpdateUser", arg0) @@ -575,72 +575,72 @@ func (m *MockClient) UpdateUser(arg0 context.Context) (*pmapi.User, error) { return ret0, ret1 } -// UpdateUser indicates an expected call of UpdateUser +// UpdateUser indicates an expected call of UpdateUser. func (mr *MockClientMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockClient)(nil).UpdateUser), arg0) } -// MockManager is a mock of Manager interface +// MockManager is a mock of Manager interface. type MockManager struct { ctrl *gomock.Controller recorder *MockManagerMockRecorder } -// MockManagerMockRecorder is the mock recorder for MockManager +// MockManagerMockRecorder is the mock recorder for MockManager. type MockManagerMockRecorder struct { mock *MockManager } -// NewMockManager creates a new mock instance +// NewMockManager creates a new mock instance. func NewMockManager(ctrl *gomock.Controller) *MockManager { mock := &MockManager{ctrl: ctrl} mock.recorder = &MockManagerMockRecorder{mock} return mock } -// EXPECT returns an object that allows the caller to indicate expected use +// EXPECT returns an object that allows the caller to indicate expected use. func (m *MockManager) EXPECT() *MockManagerMockRecorder { return m.recorder } -// AddConnectionObserver mocks base method +// AddConnectionObserver mocks base method. func (m *MockManager) AddConnectionObserver(arg0 pmapi.ConnectionObserver) { m.ctrl.T.Helper() m.ctrl.Call(m, "AddConnectionObserver", arg0) } -// AddConnectionObserver indicates an expected call of AddConnectionObserver +// AddConnectionObserver indicates an expected call of AddConnectionObserver. func (mr *MockManagerMockRecorder) AddConnectionObserver(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddConnectionObserver", reflect.TypeOf((*MockManager)(nil).AddConnectionObserver), arg0) } -// AllowProxy mocks base method +// AllowProxy mocks base method. func (m *MockManager) AllowProxy() { m.ctrl.T.Helper() m.ctrl.Call(m, "AllowProxy") } -// AllowProxy indicates an expected call of AllowProxy +// AllowProxy indicates an expected call of AllowProxy. func (mr *MockManagerMockRecorder) AllowProxy() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllowProxy", reflect.TypeOf((*MockManager)(nil).AllowProxy)) } -// DisallowProxy mocks base method +// DisallowProxy mocks base method. func (m *MockManager) DisallowProxy() { m.ctrl.T.Helper() m.ctrl.Call(m, "DisallowProxy") } -// DisallowProxy indicates an expected call of DisallowProxy +// DisallowProxy indicates an expected call of DisallowProxy. func (mr *MockManagerMockRecorder) DisallowProxy() *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisallowProxy", reflect.TypeOf((*MockManager)(nil).DisallowProxy)) } -// DownloadAndVerify mocks base method +// DownloadAndVerify mocks base method. func (m *MockManager) DownloadAndVerify(arg0 *crypto.KeyRing, arg1, arg2 string) ([]byte, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DownloadAndVerify", arg0, arg1, arg2) @@ -649,13 +649,13 @@ func (m *MockManager) DownloadAndVerify(arg0 *crypto.KeyRing, arg1, arg2 string) return ret0, ret1 } -// DownloadAndVerify indicates an expected call of DownloadAndVerify +// DownloadAndVerify indicates an expected call of DownloadAndVerify. func (mr *MockManagerMockRecorder) DownloadAndVerify(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadAndVerify", reflect.TypeOf((*MockManager)(nil).DownloadAndVerify), arg0, arg1, arg2) } -// NewClient mocks base method +// NewClient mocks base method. func (m *MockManager) NewClient(arg0, arg1, arg2 string, arg3 time.Time) pmapi.Client { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NewClient", arg0, arg1, arg2, arg3) @@ -663,13 +663,13 @@ func (m *MockManager) NewClient(arg0, arg1, arg2 string, arg3 time.Time) pmapi.C return ret0 } -// NewClient indicates an expected call of NewClient +// NewClient indicates an expected call of NewClient. func (mr *MockManagerMockRecorder) NewClient(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClient", reflect.TypeOf((*MockManager)(nil).NewClient), arg0, arg1, arg2, arg3) } -// NewClientWithLogin mocks base method +// NewClientWithLogin mocks base method. func (m *MockManager) NewClientWithLogin(arg0 context.Context, arg1 string, arg2 []byte) (pmapi.Client, *pmapi.Auth, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NewClientWithLogin", arg0, arg1, arg2) @@ -679,13 +679,13 @@ func (m *MockManager) NewClientWithLogin(arg0 context.Context, arg1 string, arg2 return ret0, ret1, ret2 } -// NewClientWithLogin indicates an expected call of NewClientWithLogin +// NewClientWithLogin indicates an expected call of NewClientWithLogin. func (mr *MockManagerMockRecorder) NewClientWithLogin(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClientWithLogin", reflect.TypeOf((*MockManager)(nil).NewClientWithLogin), arg0, arg1, arg2) } -// NewClientWithRefresh mocks base method +// NewClientWithRefresh mocks base method. func (m *MockManager) NewClientWithRefresh(arg0 context.Context, arg1, arg2 string) (pmapi.Client, *pmapi.AuthRefresh, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "NewClientWithRefresh", arg0, arg1, arg2) @@ -695,13 +695,13 @@ func (m *MockManager) NewClientWithRefresh(arg0 context.Context, arg1, arg2 stri return ret0, ret1, ret2 } -// NewClientWithRefresh indicates an expected call of NewClientWithRefresh +// NewClientWithRefresh indicates an expected call of NewClientWithRefresh. func (mr *MockManagerMockRecorder) NewClientWithRefresh(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClientWithRefresh", reflect.TypeOf((*MockManager)(nil).NewClientWithRefresh), arg0, arg1, arg2) } -// ReportBug mocks base method +// ReportBug mocks base method. func (m *MockManager) ReportBug(arg0 context.Context, arg1 pmapi.ReportBugReq) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ReportBug", arg0, arg1) @@ -709,13 +709,13 @@ func (m *MockManager) ReportBug(arg0 context.Context, arg1 pmapi.ReportBugReq) e return ret0 } -// ReportBug indicates an expected call of ReportBug +// ReportBug indicates an expected call of ReportBug. func (mr *MockManagerMockRecorder) ReportBug(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportBug", reflect.TypeOf((*MockManager)(nil).ReportBug), arg0, arg1) } -// SendSimpleMetric mocks base method +// SendSimpleMetric mocks base method. func (m *MockManager) SendSimpleMetric(arg0 context.Context, arg1, arg2, arg3 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SendSimpleMetric", arg0, arg1, arg2, arg3) @@ -723,55 +723,55 @@ func (m *MockManager) SendSimpleMetric(arg0 context.Context, arg1, arg2, arg3 st return ret0 } -// SendSimpleMetric indicates an expected call of SendSimpleMetric +// SendSimpleMetric indicates an expected call of SendSimpleMetric. func (mr *MockManagerMockRecorder) SendSimpleMetric(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendSimpleMetric", reflect.TypeOf((*MockManager)(nil).SendSimpleMetric), arg0, arg1, arg2, arg3) } -// SetCookieJar mocks base method +// SetCookieJar mocks base method. func (m *MockManager) SetCookieJar(arg0 http.CookieJar) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetCookieJar", arg0) } -// SetCookieJar indicates an expected call of SetCookieJar +// SetCookieJar indicates an expected call of SetCookieJar. func (mr *MockManagerMockRecorder) SetCookieJar(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCookieJar", reflect.TypeOf((*MockManager)(nil).SetCookieJar), arg0) } -// SetLogging mocks base method +// SetLogging mocks base method. func (m *MockManager) SetLogging(arg0 *logrus.Entry, arg1 bool) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetLogging", arg0, arg1) } -// SetLogging indicates an expected call of SetLogging +// SetLogging indicates an expected call of SetLogging. func (mr *MockManagerMockRecorder) SetLogging(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogging", reflect.TypeOf((*MockManager)(nil).SetLogging), arg0, arg1) } -// SetRetryCount mocks base method +// SetRetryCount mocks base method. func (m *MockManager) SetRetryCount(arg0 int) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetRetryCount", arg0) } -// SetRetryCount indicates an expected call of SetRetryCount +// SetRetryCount indicates an expected call of SetRetryCount. func (mr *MockManagerMockRecorder) SetRetryCount(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRetryCount", reflect.TypeOf((*MockManager)(nil).SetRetryCount), arg0) } -// SetTransport mocks base method +// SetTransport mocks base method. func (m *MockManager) SetTransport(arg0 http.RoundTripper) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetTransport", arg0) } -// SetTransport indicates an expected call of SetTransport +// SetTransport indicates an expected call of SetTransport. func (mr *MockManagerMockRecorder) SetTransport(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTransport", reflect.TypeOf((*MockManager)(nil).SetTransport), arg0) diff --git a/release-notes/bridge_early.md b/release-notes/bridge_early.md index 0d44e25a..47aaf860 100644 --- a/release-notes/bridge_early.md +++ b/release-notes/bridge_early.md @@ -1,15 +1,27 @@ -## v1.8.3 -- 2021-05-31 +## v1.8.5 +- 2021-06-11 ### New -- Improved moving messages from other accounts to ProtonMail - implemented new parser for processing such messages -- Performance improvements +- Implemented golang Secure Remote Password Protocol +- Updated crypto-libraries to gopenpgp/v2 v2.1.9 +- Implemented new message parser (for imports from external accounts) ### Fixed -- Sync issue with Microsoft Outlook (changed the order of processing requests) -- Fetching the bodies of non-multipart messages +- Bridge not to strip PGP signatures of incoming clear text messages +- Import of messages with malformed MIME header +- Improved parsing of message headers +- Fetching bodies of non-multipart messages +- Sync and performance improvements + + +## v1.8.3 +- 2021-05-27 + +### Fixed + +- A bug with sending encrypted emails to external contacts ## v1.8.2 diff --git a/release-notes/bridge_stable.md b/release-notes/bridge_stable.md index e2328c9b..e2328088 100644 --- a/release-notes/bridge_stable.md +++ b/release-notes/bridge_stable.md @@ -1,3 +1,11 @@ +## v1.8.3 +- 2021-05-27 + +### Fixed + +- A bug with sending encrypted emails to external contacts + + ## v1.8.2 - 2021-05-21 diff --git a/test/Makefile b/test/Makefile index 36197e27..61f43340 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,7 +1,7 @@ .PHONY: check-go check-godog install-godog test test-bridge test-ie test-live test-live-bridge test-live-ie test-stage test-debug test-live-debug bench export GO111MODULE=on -export BRIDGE_VERSION:=1.5.5+integrationtests +export BRIDGE_VERSION:=1.8.6+integrationtests export VERBOSITY?=fatal export TEST_DATA=testdata export TEST_APP?=bridge diff --git a/test/bdd_test.go b/test/bdd_test.go index 0817b587..0b06c025 100644 --- a/test/bdd_test.go +++ b/test/bdd_test.go @@ -29,6 +29,9 @@ const ( ) func FeatureContext(s *godog.Suite) { + s.BeforeSuite(context.BeforeRun) + s.AfterSuite(context.AfterRun) + s.BeforeScenario(beforeScenario) s.AfterScenario(afterScenario) diff --git a/test/benchmarks/bench_test.go b/test/benchmarks/bench_test.go index 4e01b6df..c7212914 100644 --- a/test/benchmarks/bench_test.go +++ b/test/benchmarks/bench_test.go @@ -35,7 +35,7 @@ func benchTestContext() (*context.TestContext, *mocks.IMAPClient) { panic("account " + username + " does not exist") } - _ = ctx.GetPMAPIController().AddUser(account.User(), account.Addresses(), account.Password(), account.IsTwoFAEnabled()) + _ = ctx.GetPMAPIController().AddUser(account) if err := ctx.LoginUser(account.Username(), account.Password(), account.MailboxPassword()); err != nil { panic(err) } diff --git a/test/context/context.go b/test/context/context.go index 4d2a1dc0..0778bebf 100644 --- a/test/context/context.go +++ b/test/context/context.go @@ -94,8 +94,6 @@ type TestContext struct { // New returns a new test TestContext. func New(app string) *TestContext { - setLogrusVerbosityFromEnv() - listener := listener.New() pmapiController, clientManager := newPMAPIController(app, listener) diff --git a/test/context/globals.go b/test/context/globals.go new file mode 100644 index 00000000..619cc5f5 --- /dev/null +++ b/test/context/globals.go @@ -0,0 +1,40 @@ +// Copyright (c) 2021 Proton Technologies AG +// +// This file is part of ProtonMail Bridge.Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +package context + +import ( + "os" + + "github.com/ProtonMail/proton-bridge/test/liveapi" +) + +// BeforeRun does necessary setup. +func BeforeRun() { + setLogrusVerbosityFromEnv() + + if os.Getenv(EnvName) == EnvLive { + liveapi.SetupPersistentClients() + } +} + +// AfterRun does necessary cleanup. +func AfterRun() { + if os.Getenv(EnvName) == EnvLive { + liveapi.CleanupPersistentClients() + } +} diff --git a/test/context/pmapi_controller.go b/test/context/pmapi_controller.go index 17c5a1b9..7de8799d 100644 --- a/test/context/pmapi_controller.go +++ b/test/context/pmapi_controller.go @@ -23,6 +23,7 @@ import ( "github.com/ProtonMail/proton-bridge/internal/events" "github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/pmapi" + "github.com/ProtonMail/proton-bridge/test/accounts" "github.com/ProtonMail/proton-bridge/test/fakeapi" "github.com/ProtonMail/proton-bridge/test/liveapi" ) @@ -30,7 +31,8 @@ import ( type PMAPIController interface { TurnInternetConnectionOff() TurnInternetConnectionOn() - AddUser(user *pmapi.User, addresses *pmapi.AddressList, password []byte, twoFAEnabled bool) error + GetAuthClient(username string) pmapi.Client + AddUser(account *accounts.TestAccount) error AddUserLabel(username string, label *pmapi.Label) error GetLabelIDs(username string, labelNames []string) ([]string, error) AddUserMessage(username string, message *pmapi.Message) (string, error) diff --git a/test/context/users.go b/test/context/users.go index 9fa66ec0..1fb440b7 100644 --- a/test/context/users.go +++ b/test/context/users.go @@ -27,6 +27,7 @@ import ( "github.com/ProtonMail/go-srp" "github.com/ProtonMail/proton-bridge/internal/store" "github.com/ProtonMail/proton-bridge/internal/users" + "github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -61,6 +62,27 @@ func (ctx *TestContext) LoginUser(username string, password, mailboxPassword []b return nil } +// FinishLogin prevents authentication if not necessary. +func (ctx *TestContext) FinishLogin(client pmapi.Client, mailboxPassword []byte) error { + type currentAuthGetter interface { + GetCurrentAuth() *pmapi.Auth + } + + c, ok := client.(currentAuthGetter) + if c == nil || !ok { + return errors.New("cannot get current auth tokens from client") + } + + user, err := ctx.users.FinishLogin(client, c.GetCurrentAuth(), mailboxPassword) + if err != nil { + return errors.Wrap(err, "failed to finish login") + } + + ctx.addCleanupChecked(user.Logout, "Logging out user") + + return nil +} + // GetUser retrieves the bridge user matching the given query string. func (ctx *TestContext) GetUser(username string) (*users.User, error) { return ctx.users.GetUser(username) diff --git a/test/fakeapi/auth.go b/test/fakeapi/auth.go index 8f45a8df..b63fcc46 100644 --- a/test/fakeapi/auth.go +++ b/test/fakeapi/auth.go @@ -63,3 +63,13 @@ func (api *FakePMAPI) AuthDelete(_ context.Context) error { return nil } + +func (api *FakePMAPI) GetCurrentAuth() *pmapi.Auth { + return &pmapi.Auth{ + UserID: api.userID, + AuthRefresh: pmapi.AuthRefresh{ + UID: api.uid, + RefreshToken: api.ref, + }, + } +} diff --git a/test/fakeapi/controller.go b/test/fakeapi/controller.go index a01f0064..50a5d6d1 100644 --- a/test/fakeapi/controller.go +++ b/test/fakeapi/controller.go @@ -24,6 +24,8 @@ import ( "github.com/sirupsen/logrus" ) +// Controller implements dummy PMAPIController interface without actual +// endpoint. type Controller struct { // Internal states. lock *sync.RWMutex diff --git a/test/fakeapi/controller_control.go b/test/fakeapi/controller_control.go index 35a9e184..8e853b65 100644 --- a/test/fakeapi/controller_control.go +++ b/test/fakeapi/controller_control.go @@ -22,8 +22,10 @@ import ( "errors" "fmt" "strings" + "time" "github.com/ProtonMail/proton-bridge/pkg/pmapi" + "github.com/ProtonMail/proton-bridge/test/accounts" ) var systemLabelNameToID = map[string]string{ //nolint[gochecknoglobals] @@ -61,13 +63,15 @@ func (ctl *Controller) ReorderAddresses(user *pmapi.User, addressIDs []string) e return api.ReorderAddresses(context.Background(), addressIDs) } -func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password []byte, twoFAEnabled bool) error { - ctl.usersByUsername[user.Name] = &fakeUser{ - user: user, - password: password, - has2FA: twoFAEnabled, +func (ctl *Controller) AddUser(account *accounts.TestAccount) error { + ctl.usersByUsername[account.User().Name] = &fakeUser{ + user: account.User(), + password: account.Password(), + has2FA: account.IsTwoFAEnabled(), } - ctl.addressesByUsername[user.Name] = addresses + ctl.addressesByUsername[account.User().Name] = account.Addresses() + ctl.createSession(account.User().Name, true) + return nil } @@ -181,3 +185,15 @@ func (ctl *Controller) GetMessages(username, labelID string) ([]*pmapi.Message, } return messages, nil } + +func (ctl *Controller) GetAuthClient(username string) pmapi.Client { + for uid, session := range ctl.sessionsByUID { + if session.username == username { + return ctl.clientManager.NewClient(uid, session.acc, session.ref, time.Now()) + } + } + + ctl.log.WithField("username", username).Fatal("Cannot get authenticated client.") + + return nil +} diff --git a/test/fakeapi/controller_session.go b/test/fakeapi/controller_session.go index 87de705f..6fd81de5 100644 --- a/test/fakeapi/controller_session.go +++ b/test/fakeapi/controller_session.go @@ -51,24 +51,25 @@ func (ctl *Controller) checkScope(uid string) bool { } func (ctl *Controller) createSessionIfAuthorized(username string, password []byte) (*fakeSession, error) { - // get user user, ok := ctl.usersByUsername[username] if !ok || !bytes.Equal(user.password, password) { return nil, errWrongNameOrPassword } - // create session + return ctl.createSession(username, !user.has2FA), nil +} + +func (ctl *Controller) createSession(username string, hasFullScope bool) *fakeSession { session := &fakeSession{ username: username, uid: ctl.tokenGenerator.next("uid"), acc: ctl.tokenGenerator.next("acc"), ref: ctl.tokenGenerator.next("ref"), - hasFullScope: !user.has2FA, + hasFullScope: hasFullScope, } ctl.sessionsByUID[session.uid] = session - - return session, nil + return session } func (ctl *Controller) refreshSessionIfAuthorized(uid, ref string) (*fakeSession, error) { diff --git a/test/features/bridge/imap/auth.feature b/test/features/bridge/imap/auth.feature index 3191d1e3..a86e0897 100644 --- a/test/features/bridge/imap/auth.feature +++ b/test/features/bridge/imap/auth.feature @@ -15,7 +15,7 @@ Feature: IMAP auth Then IMAP response is "IMAP error: NO account is logged out, use the app to login again" Scenario: Authenticates with connected user that was loaded without internet - Given there is connected user "user" + Given there is user "user" which just logged in And there is no internet connection When bridge starts And the internet connection is restored @@ -28,13 +28,13 @@ Feature: IMAP auth Then "user" is connected Scenario: Authenticates with freshly logged-out user - Given there is connected user "user" + Given there is user "user" which just logged in When "user" logs out And IMAP client authenticates "user" Then IMAP response is "IMAP error: NO account is logged out, use the app to login again" Scenario: Authenticates user which was re-logged in - Given there is connected user "user" + Given there is user "user" which just logged in When "user" logs out And IMAP client authenticates "user" Then IMAP response is "IMAP error: NO account is logged out, use the app to login again" diff --git a/test/features/bridge/no_internet.feature b/test/features/bridge/no_internet.feature deleted file mode 100644 index 5abb1841..00000000 --- a/test/features/bridge/no_internet.feature +++ /dev/null @@ -1,30 +0,0 @@ -Feature: Servers are closed when no internet - - Scenario: All connection are closed and then restored multiple times - Given there is connected user "user" - And there is IMAP client "i1" logged in as "user" - And there is SMTP client "s1" logged in as "user" - When there is no internet connection - And 1 second pass - Then IMAP client "i1" is logged out - And SMTP client "s1" is logged out - Given the internet connection is restored - And 1 second pass - And there is IMAP client "i2" logged in as "user" - And there is SMTP client "s2" logged in as "user" - When IMAP client "i2" gets info of "INBOX" - When SMTP client "s2" sends "HELO example.com" - Then IMAP response to "i2" is "OK" - Then SMTP response to "s2" is "OK" - When there is no internet connection - And 1 second pass - Then IMAP client "i2" is logged out - And SMTP client "s2" is logged out - Given the internet connection is restored - And 1 second pass - And there is IMAP client "i3" logged in as "user" - And there is SMTP client "s3" logged in as "user" - When IMAP client "i3" gets info of "INBOX" - When SMTP client "s3" sends "HELO example.com" - Then IMAP response to "i3" is "OK" - Then SMTP response to "s3" is "OK" diff --git a/test/features/bridge/start.feature b/test/features/bridge/start.feature index 2d0b82e7..2b7c50c0 100644 --- a/test/features/bridge/start.feature +++ b/test/features/bridge/start.feature @@ -1,6 +1,6 @@ Feature: Start bridge Scenario: Start with connected user, database file and internet connection - Given there is connected user "user" + Given there is user "user" which just logged in And there is database file for "user" When bridge starts Then "user" is connected @@ -8,7 +8,7 @@ Feature: Start bridge And "user" has running event loop Scenario: Start with connected user, database file and no internet connection - Given there is connected user "user" + Given there is user "user" which just logged in And there is database file for "user" And there is no internet connection When bridge starts @@ -17,7 +17,7 @@ Feature: Start bridge And "user" has running event loop Scenario: Start with connected user, no database file and internet connection - Given there is connected user "user" + Given there is user "user" which just logged in And there is no database file for "user" When bridge starts Then "user" is connected @@ -25,7 +25,7 @@ Feature: Start bridge And "user" has running event loop Scenario: Start with connected user, no database file and no internet connection - Given there is connected user "user" + Given there is user "user" which just logged in And there is no database file for "user" And there is no internet connection When bridge starts diff --git a/test/features/bridge/users/delete.feature b/test/features/bridge/users/delete.feature index 770ddf6d..be7dc8d1 100644 --- a/test/features/bridge/users/delete.feature +++ b/test/features/bridge/users/delete.feature @@ -1,18 +1,18 @@ Feature: Delete user Scenario: Deleting connected user - Given there is connected user "user" + Given there is user "user" which just logged in When user deletes "user" Then last response is "OK" And "user" has database file Scenario: Deleting connected user with cache - Given there is connected user "user" + Given there is user "user" which just logged in When user deletes "user" with cache Then last response is "OK" And "user" does not have database file Scenario: Deleting connected user without database file - Given there is connected user "user" + Given there is user "user" which just logged in And there is no database file for "user" When user deletes "user" with cache Then last response is "OK" diff --git a/test/features/bridge/users/relogin.feature b/test/features/bridge/users/relogin.feature index e0b43d79..f1f8b9e0 100644 --- a/test/features/bridge/users/relogin.feature +++ b/test/features/bridge/users/relogin.feature @@ -1,6 +1,6 @@ Feature: Re-login Scenario: Re-login with connected user and database file - Given there is connected user "user" + Given there is user "user" which just logged in And there is database file for "user" When "user" logs in Then last response is "failed to finish login: user is already connected" @@ -9,7 +9,7 @@ Feature: Re-login @ignore Scenario: Re-login with connected user and no database file - Given there is connected user "user" + Given there is user "user" which just logged in And there is no database file for "user" When "user" logs in Then last response is "failed to finish login: user is already connected" diff --git a/test/liveapi/controller.go b/test/liveapi/controller.go index d4664007..9e28f512 100644 --- a/test/liveapi/controller.go +++ b/test/liveapi/controller.go @@ -21,45 +21,36 @@ import ( "net/http" "sync" - "github.com/ProtonMail/proton-bridge/internal/constants" "github.com/ProtonMail/proton-bridge/pkg/pmapi" + "github.com/sirupsen/logrus" ) +// Controller implements PMAPIController interface for specified endpoint. type Controller struct { + log *logrus.Entry // Internal states. lock *sync.RWMutex calls []*fakeCall - pmapiByUsername map[string]pmapi.Client messageIDsByUsername map[string][]string - clientManager pmapi.Manager // State controlled by test. noInternetConnection bool } -func NewController(app string) (*Controller, pmapi.Manager) { - cm := pmapi.New(pmapi.NewConfig(getAppVersionName(app), constants.Version)) +func NewController(_ string) (*Controller, pmapi.Manager) { controller := &Controller{ + log: logrus.WithField("pkg", "live-controller"), lock: &sync.RWMutex{}, calls: []*fakeCall{}, - pmapiByUsername: map[string]pmapi.Client{}, messageIDsByUsername: map[string][]string{}, - clientManager: cm, noInternetConnection: false, } - cm.SetTransport(&fakeTransport{ + persistentClients.manager.SetTransport(&fakeTransport{ ctl: controller, transport: http.DefaultTransport, }) - return controller, cm -} - -func getAppVersionName(app string) string { - if app == "ie" { - return "importExport" - } - return app + return controller, persistentClients.manager } diff --git a/test/liveapi/labels.go b/test/liveapi/labels.go index 105b74e9..974ec085 100644 --- a/test/liveapi/labels.go +++ b/test/liveapi/labels.go @@ -37,9 +37,9 @@ var systemLabelNameToID = map[string]string{ //nolint[gochecknoglobals] } func (ctl *Controller) AddUserLabel(username string, label *pmapi.Label) error { - client, ok := ctl.pmapiByUsername[username] - if !ok { - return fmt.Errorf("user %s does not exist", username) + client, err := getPersistentClient(username) + if err != nil { + return err } label.Exclusive = getLabelExclusive(label.Name) @@ -68,9 +68,9 @@ func (ctl *Controller) getLabelID(username, labelName string) (string, error) { return labelID, nil } - client, ok := ctl.pmapiByUsername[username] - if !ok { - return "", fmt.Errorf("user %s does not exist", username) + client, err := getPersistentClient(username) + if err != nil { + return "", err } labels, err := client.ListLabels(context.Background()) diff --git a/test/liveapi/messages.go b/test/liveapi/messages.go index ed696f81..2487b568 100644 --- a/test/liveapi/messages.go +++ b/test/liveapi/messages.go @@ -19,7 +19,6 @@ package liveapi import ( "context" - "fmt" messageUtils "github.com/ProtonMail/proton-bridge/pkg/message" "github.com/ProtonMail/proton-bridge/pkg/pmapi" @@ -31,9 +30,9 @@ func (ctl *Controller) AddUserMessage(username string, message *pmapi.Message) ( return "", errors.New("add user messages with attachments is not implemented for live") } - client, ok := ctl.pmapiByUsername[username] - if !ok { - return "", fmt.Errorf("user %s does not exist", username) + client, err := getPersistentClient(username) + if err != nil { + return "", err } if message.Flags == 0 { @@ -75,9 +74,9 @@ func (ctl *Controller) AddUserMessage(username string, message *pmapi.Message) ( } func (ctl *Controller) GetMessages(username, labelID string) ([]*pmapi.Message, error) { - client, ok := ctl.pmapiByUsername[username] - if !ok { - return nil, fmt.Errorf("user %s does not exist", username) + client, err := getPersistentClient(username) + if err != nil { + return nil, err } page := 0 diff --git a/test/liveapi/persistent_clients.go b/test/liveapi/persistent_clients.go new file mode 100644 index 00000000..46538995 --- /dev/null +++ b/test/liveapi/persistent_clients.go @@ -0,0 +1,141 @@ +// Copyright (c) 2021 Proton Technologies AG +// +// This file is part of ProtonMail Bridge.Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +package liveapi + +import ( + "context" + "fmt" + "math/rand" + "os" + + "github.com/ProtonMail/go-srp" + "github.com/ProtonMail/proton-bridge/internal/constants" + "github.com/ProtonMail/proton-bridge/pkg/pmapi" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +type clientAuthGetter interface { + pmapi.Client + GetCurrentAuth() *pmapi.Auth +} + +// persistentClients keeps authenticated clients for tests. +// +// We need to reduce the number of authentication done by live tests. +// Before every *scenario* we are creating and authenticating new client. +// This is not necessary for controller purposes. We can reuse the same clients +// for all tests. +// +//nolint:gochecknoglobals // This is necessary for testing +var persistentClients = struct { + manager pmapi.Manager + byName map[string]clientAuthGetter + saltByName map[string]string +}{} + +type persistentClient struct { + clientAuthGetter + username string +} + +// AuthDelete is noop. All sessions will be closed in CleanupPersistentClients. +func (pc *persistentClient) AuthDelete(_ context.Context) error { + return nil +} + +// AuthSalt returns cached string. Otherwise after some time there is an error: +// +// Access token does not have sufficient scope +// +// while all other routes works normally. Need to confirm with Aron that this +// is expected behaviour. +func (pc *persistentClient) AuthSalt(_ context.Context) (string, error) { + return persistentClients.saltByName[pc.username], nil +} + +func SetupPersistentClients() { + app := os.Getenv("TEST_APP") + + persistentClients.manager = pmapi.New(pmapi.NewConfig(getAppVersionName(app), constants.Version)) + persistentClients.manager.SetLogging(logrus.WithField("pkg", "liveapi"), logrus.GetLevel() == logrus.TraceLevel) + + persistentClients.byName = map[string]clientAuthGetter{} + persistentClients.saltByName = map[string]string{} +} + +func getAppVersionName(app string) string { + if app == "ie" { + return "importExport" + } + return app +} + +func CleanupPersistentClients() { + for username, client := range persistentClients.byName { + if err := client.AuthDelete(context.Background()); err != nil { + logrus.WithError(err). + WithField("username", username). + Error("Failed to logout persistent client") + } + } +} + +func addPersistentClient(username string, password, mailboxPassword []byte) (pmapi.Client, error) { + if cl, ok := persistentClients.byName[username]; ok { + return cl, nil + } + + srp.RandReader = rand.New(rand.NewSource(42)) //nolint:gosec // It is OK to use weaker random number generator here + + normalClient, _, err := persistentClients.manager.NewClientWithLogin(context.Background(), username, password) + if err != nil { + return nil, errors.Wrap(err, "failed to create new persistent client") + } + + client, ok := normalClient.(clientAuthGetter) + if !ok { + return nil, errors.New("cannot make clientAuthGetter") + } + + salt, err := client.AuthSalt(context.Background()) + if err != nil { + return nil, errors.Wrap(err, "persistent client: failed to get salt") + } + + hashedMboxPass, err := pmapi.HashMailboxPassword(mailboxPassword, salt) + if err != nil { + return nil, errors.Wrap(err, "persistent client: failed to hash mailbox password") + } + + if err := client.Unlock(context.Background(), hashedMboxPass); err != nil { + return nil, errors.Wrap(err, "persistent client: failed to unlock user") + } + + persistentClients.byName[username] = client + persistentClients.saltByName[username] = salt + return client, nil +} + +func getPersistentClient(username string) (pmapi.Client, error) { + v, ok := persistentClients.byName[username] + if !ok { + return nil, fmt.Errorf("user %s does not exist", username) + } + return &persistentClient{v, username}, nil +} diff --git a/test/liveapi/users.go b/test/liveapi/users.go index eac77afa..b85aaa5b 100644 --- a/test/liveapi/users.go +++ b/test/liveapi/users.go @@ -21,43 +21,42 @@ import ( "context" "github.com/ProtonMail/proton-bridge/pkg/pmapi" + "github.com/ProtonMail/proton-bridge/test/accounts" "github.com/cucumber/godog" "github.com/pkg/errors" ) -func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password []byte, twoFAEnabled bool) error { - if twoFAEnabled { +func (ctl *Controller) AddUser(account *accounts.TestAccount) error { + if account.IsTwoFAEnabled() { return godog.ErrPending } - client, _, err := ctl.clientManager.NewClientWithLogin(context.Background(), user.Name, password) + client, err := addPersistentClient(account.User().Name, account.Password(), account.MailboxPassword()) if err != nil { - return errors.Wrap(err, "failed to create new client") + return errors.Wrap(err, "failed to add persistent client") } - salt, err := client.AuthSalt(context.Background()) - if err != nil { - return errors.Wrap(err, "failed to get salt") - } - - mailboxPassword, err := pmapi.HashMailboxPassword(password, salt) - if err != nil { - return errors.Wrap(err, "failed to hash mailbox password") - } - - if err := client.Unlock(context.Background(), mailboxPassword); err != nil { - return errors.Wrap(err, "failed to unlock user") - } - - if err := cleanup(client, addresses); err != nil { + if err := cleanup(client, account.Addresses()); err != nil { return errors.Wrap(err, "failed to clean user") } - ctl.pmapiByUsername[user.Name] = client - return nil } func (ctl *Controller) ReorderAddresses(user *pmapi.User, addressIDs []string) error { - return ctl.pmapiByUsername[user.Name].ReorderAddresses(context.Background(), addressIDs) + client, err := getPersistentClient(user.Name) + if err != nil { + return err + } + return client.ReorderAddresses(context.Background(), addressIDs) +} + +func (ctl *Controller) GetAuthClient(username string) pmapi.Client { + client, err := getPersistentClient(username) + if err != nil { + ctl.log.WithError(err). + WithField("username", username). + Fatal("Cannot get authenticated client") + } + return client } diff --git a/test/users_setup_test.go b/test/users_setup_test.go index 5ccfdc58..2c56fa0a 100644 --- a/test/users_setup_test.go +++ b/test/users_setup_test.go @@ -28,6 +28,7 @@ import ( func UsersSetupFeatureContext(s *godog.Suite) { s.Step(`^there is user "([^"]*)"$`, thereIsUser) s.Step(`^there is connected user "([^"]*)"$`, thereIsConnectedUser) + s.Step(`^there is user "([^"]*)" which just logged in$`, thereIsUserWhichJustLoggedIn) s.Step(`^there is disconnected user "([^"]*)"$`, thereIsDisconnectedUser) s.Step(`^there is database file for "([^"]*)"$`, thereIsDatabaseFileForUser) s.Step(`^there is no database file for "([^"]*)"$`, thereIsNoDatabaseFileForUser) @@ -39,7 +40,7 @@ func thereIsUser(bddUserID string) error { if account == nil { return godog.ErrPending } - err := ctx.GetPMAPIController().AddUser(account.User(), account.Addresses(), account.Password(), account.IsTwoFAEnabled()) + err := ctx.GetPMAPIController().AddUser(account) return internalError(err, "adding user %s", account.Username()) } @@ -48,7 +49,21 @@ func thereIsConnectedUser(bddUserID string) error { if account == nil { return godog.ErrPending } - err := ctx.GetPMAPIController().AddUser(account.User(), account.Addresses(), account.Password(), account.IsTwoFAEnabled()) + username := account.Username() + ctl := ctx.GetPMAPIController() + err := ctl.AddUser(account) + if err != nil { + return internalError(err, "adding user %s", username) + } + return ctx.FinishLogin(ctx.GetPMAPIController().GetAuthClient(username), account.MailboxPassword()) +} + +func thereIsUserWhichJustLoggedIn(bddUserID string) error { + account := ctx.GetTestAccount(bddUserID) + if account == nil { + return godog.ErrPending + } + err := ctx.GetPMAPIController().AddUser(account) if err != nil { return internalError(err, "adding user %s", account.Username()) } @@ -60,7 +75,7 @@ func thereIsDisconnectedUser(bddUserID string) error { if account == nil { return godog.ErrPending } - err := ctx.GetPMAPIController().AddUser(account.User(), account.Addresses(), account.Password(), account.IsTwoFAEnabled()) + err := ctx.GetPMAPIController().AddUser(account) if err != nil { return internalError(err, "adding user %s", account.Username()) }