diff --git a/cmd/launcher/main.go b/cmd/launcher/main.go index e2b5fa53..ddabed2e 100644 --- a/cmd/launcher/main.go +++ b/cmd/launcher/main.go @@ -25,13 +25,14 @@ import ( "runtime" "github.com/ProtonMail/gopenpgp/v2/crypto" + "github.com/ProtonMail/proton-bridge/internal/config/useragent" "github.com/ProtonMail/proton-bridge/internal/constants" "github.com/ProtonMail/proton-bridge/internal/crash" "github.com/ProtonMail/proton-bridge/internal/locations" "github.com/ProtonMail/proton-bridge/internal/logging" + "github.com/ProtonMail/proton-bridge/internal/sentry" "github.com/ProtonMail/proton-bridge/internal/updater" "github.com/ProtonMail/proton-bridge/internal/versioner" - "github.com/ProtonMail/proton-bridge/pkg/sentry" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -44,7 +45,7 @@ var ( ) func main() { // nolint[funlen] - reporter := sentry.NewReporter(appName, constants.Version) + reporter := sentry.NewReporter(appName, constants.Version, useragent.New()) crashHandler := crash.NewHandler(reporter.ReportException) defer crashHandler.HandlePanic() diff --git a/internal/app/base/base.go b/internal/app/base/base.go index bfcdfabd..11a7254d 100644 --- a/internal/app/base/base.go +++ b/internal/app/base/base.go @@ -43,19 +43,20 @@ import ( "github.com/ProtonMail/proton-bridge/internal/config/cache" "github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/internal/config/tls" + "github.com/ProtonMail/proton-bridge/internal/config/useragent" "github.com/ProtonMail/proton-bridge/internal/constants" "github.com/ProtonMail/proton-bridge/internal/cookies" "github.com/ProtonMail/proton-bridge/internal/crash" "github.com/ProtonMail/proton-bridge/internal/events" "github.com/ProtonMail/proton-bridge/internal/locations" "github.com/ProtonMail/proton-bridge/internal/logging" + "github.com/ProtonMail/proton-bridge/internal/sentry" "github.com/ProtonMail/proton-bridge/internal/updater" "github.com/ProtonMail/proton-bridge/internal/users/credentials" "github.com/ProtonMail/proton-bridge/internal/versioner" "github.com/ProtonMail/proton-bridge/pkg/keychain" "github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/pmapi" - "github.com/ProtonMail/proton-bridge/pkg/sentry" "github.com/allan-simon/go-singleinstance" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" @@ -86,6 +87,7 @@ type Base struct { Creds *credentials.Store CM *pmapi.ClientManager CookieJar *cookies.Jar + UserAgent *useragent.UserAgent Updater *updater.Updater Versioner *versioner.Versioner TLS *tls.TLS @@ -107,7 +109,10 @@ func New( // nolint[funlen] keychainName, cacheVersion string, ) (*Base, error) { - sentryReporter := sentry.NewReporter(appName, constants.Version) + userAgent := useragent.New() + + sentryReporter := sentry.NewReporter(appName, constants.Version, userAgent) + crashHandler := crash.NewHandler( sentryReporter.ReportException, crash.ShowErrorNotification(appName), @@ -181,20 +186,9 @@ func New( // nolint[funlen] return nil, err } - apiConfig := pmapi.GetAPIConfig(configName, constants.Version) - apiConfig.ConnectionOffHandler = func() { - listener.Emit(events.InternetOffEvent, "") - } - apiConfig.ConnectionOnHandler = func() { - listener.Emit(events.InternetOnEvent, "") - } - apiConfig.UpgradeApplicationHandler = func() { - listener.Emit(events.UpgradeApplicationEvent, "") - } - cm := pmapi.NewClientManager(apiConfig) + cm := pmapi.NewClientManager(getAPIConfig(configName, listener), userAgent) cm.SetRoundTripper(pmapi.GetRoundTripper(cm, listener)) cm.SetCookieJar(jar) - sentryReporter.SetUserAgentProvider(cm) key, err := crypto.NewKeyFromArmored(updater.DefaultPublicKey) if err != nil { @@ -245,6 +239,7 @@ func New( // nolint[funlen] Creds: credentials.NewStore(kc), CM: cm, CookieJar: jar, + UserAgent: userAgent, Updater: updater, Versioner: versioner, TLS: tls.New(settingsPath), @@ -380,3 +375,13 @@ func (b *Base) doTeardown() error { return nil } + +func getAPIConfig(configName string, listener listener.Listener) *pmapi.ClientConfig { + apiConfig := pmapi.GetAPIConfig(configName, constants.Version) + + apiConfig.ConnectionOffHandler = func() { listener.Emit(events.InternetOffEvent, "") } + apiConfig.ConnectionOnHandler = func() { listener.Emit(events.InternetOnEvent, "") } + apiConfig.UpgradeApplicationHandler = func() { listener.Emit(events.UpgradeApplicationEvent, "") } + + return apiConfig +} diff --git a/internal/app/bridge/bridge.go b/internal/app/bridge/bridge.go index 54544254..ac19deb9 100644 --- a/internal/app/bridge/bridge.go +++ b/internal/app/bridge/bridge.go @@ -87,7 +87,7 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen] b.CrashHandler, c.String(flagLogIMAP) == "client" || c.String(flagLogIMAP) == "all", c.String(flagLogIMAP) == "server" || c.String(flagLogIMAP) == "all", - imapPort, tlsConfig, imapBackend, b.Listener).ListenAndServe() + imapPort, tlsConfig, imapBackend, b.UserAgent, b.Listener).ListenAndServe() }() go func() { @@ -130,6 +130,7 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen] b.Settings, b.Listener, b.Updater, + b.UserAgent, bridge, smtpBackend, b.Autostart, diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index b5249315..4f950c46 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -26,10 +26,10 @@ import ( "github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/internal/constants" "github.com/ProtonMail/proton-bridge/internal/metrics" + "github.com/ProtonMail/proton-bridge/internal/sentry" "github.com/ProtonMail/proton-bridge/internal/updater" "github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/pkg/pmapi" - "github.com/ProtonMail/proton-bridge/pkg/sentry" "github.com/ProtonMail/proton-bridge/pkg/listener" logrus "github.com/sirupsen/logrus" @@ -47,10 +47,6 @@ type Bridge struct { clientManager users.ClientManager updater Updater versioner Versioner - - userAgentClientName string - userAgentClientVersion string - userAgentOS string } func New( @@ -120,40 +116,6 @@ func (b *Bridge) heartbeat() { } } -// GetCurrentClient returns currently connected client (e.g. Thunderbird). -func (b *Bridge) GetCurrentClient() string { - res := b.userAgentClientName - if b.userAgentClientVersion != "" { - res = res + " " + b.userAgentClientVersion - } - return res -} - -// SetCurrentClient updates client info (e.g. Thunderbird) and sets the user agent -// on pmapi. By default no client is used, IMAP has to detect it on first login. -func (b *Bridge) SetCurrentClient(clientName, clientVersion string) { - b.userAgentClientName = clientName - b.userAgentClientVersion = clientVersion - b.updateUserAgent() -} - -// SetCurrentOS updates OS and sets the user agent on pmapi. By default we use -// `runtime.GOOS`, but this can be overridden in case of better detection. -func (b *Bridge) SetCurrentOS(os string) { - b.userAgentOS = os - b.updateUserAgent() -} - -func (b *Bridge) updateUserAgent() { - logrus. - WithField("clientName", b.userAgentClientName). - WithField("clientVersion", b.userAgentClientVersion). - WithField("OS", b.userAgentOS). - Info("Updating user agent") - - b.clientManager.SetUserAgent(b.userAgentClientName, b.userAgentClientVersion, b.userAgentOS) -} - // ReportBug reports a new bug from the user. func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, emailClient string) error { c := b.clientManager.GetAnonymousClient() diff --git a/internal/bridge/store_factory.go b/internal/bridge/store_factory.go index ba15b870..4765ab49 100644 --- a/internal/bridge/store_factory.go +++ b/internal/bridge/store_factory.go @@ -21,10 +21,10 @@ import ( "fmt" "path/filepath" + "github.com/ProtonMail/proton-bridge/internal/sentry" "github.com/ProtonMail/proton-bridge/internal/store" "github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/pkg/listener" - "github.com/ProtonMail/proton-bridge/pkg/sentry" ) type storeFactory struct { diff --git a/pkg/useragent/useragent.go b/internal/config/useragent/platform.go similarity index 68% rename from pkg/useragent/useragent.go rename to internal/config/useragent/platform.go index 4b8cb45b..bca20121 100644 --- a/pkg/useragent/useragent.go +++ b/internal/config/useragent/platform.go @@ -25,29 +25,27 @@ import ( "github.com/Masterminds/semver/v3" ) -// IsCatalinaOrNewer checks that host is MacOS Catalina 10.15.x or higher. +// IsCatalinaOrNewer checks whether host is MacOS Catalina 10.15.x or higher. func IsCatalinaOrNewer() bool { if runtime.GOOS != "darwin" { return false } - return isVersionCatalinaOrNewer(getMacVersion()) -} -func getMacVersion() string { - out, err := exec.Command("sw_vers", "-productVersion").Output() - if err != nil { - return "" - } - - return strings.TrimSpace(string(out)) -} - -func isVersionCatalinaOrNewer(version string) bool { - v, err := semver.NewVersion(version) + rawVersion, err := exec.Command("sw_vers", "-productVersion").Output() if err != nil { return false } - catalina := semver.MustParse("10.15.0") - return v.GreaterThan(catalina) || v.Equal(catalina) + return isVersionCatalinaOrNewer(strings.TrimSpace(string(rawVersion))) +} + +func isVersionCatalinaOrNewer(rawVersion string) bool { + semVersion, err := semver.NewVersion(rawVersion) + if err != nil { + return false + } + + minVersion := semver.MustParse("10.15.0") + + return semVersion.GreaterThan(minVersion) || semVersion.Equal(minVersion) } diff --git a/pkg/useragent/useragent_test.go b/internal/config/useragent/platform_test.go similarity index 100% rename from pkg/useragent/useragent_test.go rename to internal/config/useragent/platform_test.go diff --git a/pkg/pmapi/useragent.go b/internal/config/useragent/useragent.go similarity index 50% rename from pkg/pmapi/useragent.go rename to internal/config/useragent/useragent.go index e82c0c13..5e9559ea 100644 --- a/pkg/pmapi/useragent.go +++ b/internal/config/useragent/useragent.go @@ -15,38 +15,45 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -package pmapi +package useragent import ( "fmt" + "regexp" "runtime" - "strings" ) -// removeBrackets handle unwanted brackets in client identification string and join with given joinBy parameter. -// Mac OS X Mail/13.0 (3601.0.4) -> Mac OS X Mail/13.0-3601.0.4 (joinBy = "-") -func removeBrackets(s string, joinBy string) (r string) { - r = strings.ReplaceAll(s, " (", joinBy) - r = strings.ReplaceAll(r, "(", joinBy) // Should be faster than regex. - r = strings.ReplaceAll(r, ")", "") - - return +type UserAgent struct { + client, platform string } -func formatUserAgent(clientName, clientVersion, os string) string { - client := "" - if clientName != "" { - client = removeBrackets(clientName, "-") - if clientVersion != "" { - client += "/" + removeBrackets(clientVersion, "-") - } +func New() *UserAgent { + return &UserAgent{ + client: "", + platform: runtime.GOOS, } - - if os == "" { - os = runtime.GOOS - } - - os = removeBrackets(os, " ") - - return fmt.Sprintf("%s (%s)", client, os) +} + +func (ua *UserAgent) SetClient(name, version string) { + ua.client = fmt.Sprintf("%v/%v", name, regexp.MustCompile(`(.*) \((.*)\)`).ReplaceAllString(version, "$1-$2")) +} + +func (ua *UserAgent) HasClient() bool { + return ua.client != "" +} + +func (ua *UserAgent) SetPlatform(platform string) { + ua.platform = platform +} + +func (ua *UserAgent) String() string { + var client string + + if ua.client != "" { + client = ua.client + } else { + client = "NoClient/0.0.1" + } + + return fmt.Sprintf("%v (%v)", client, ua.platform) } diff --git a/internal/config/useragent/useragent_test.go b/internal/config/useragent/useragent_test.go new file mode 100644 index 00000000..356fbf3f --- /dev/null +++ b/internal/config/useragent/useragent_test.go @@ -0,0 +1,86 @@ +// 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 useragent + +import ( + "fmt" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUserAgent(t *testing.T) { + tests := []struct { + name, version, platform string + want string + }{ + // No name/version, no platform. + { + want: fmt.Sprintf("NoClient/0.0.1 (%v)", runtime.GOOS), + }, + + // No name/version, with platform. + { + platform: "macOS 10.15", + want: "NoClient/0.0.1 (macOS 10.15)", + }, + + // With name/version, with platform. + { + name: "Mac OS X Mail", + version: "1.0.0", + platform: "macOS 10.15", + want: "Mac OS X Mail/1.0.0 (macOS 10.15)", + }, + + // With name/version, with platform. + { + name: "Mac OS X Mail", + version: "13.4 (3608.120.23.2.4)", + platform: "macOS 10.15", + want: "Mac OS X Mail/13.4-3608.120.23.2.4 (macOS 10.15)", + }, + + // With name/version, with platform. + { + name: "Thunderbird", + version: "78.6.1", + platform: "Windows 10 (10.0)", + want: "Thunderbird/78.6.1 (Windows 10 (10.0))", + }, + } + + for _, test := range tests { + test := test + + t.Run(test.want, func(t *testing.T) { + ua := New() + + if test.name != "" && test.version != "" { + ua.SetClient(test.name, test.version) + } + + if test.platform != "" { + ua.SetPlatform(test.platform) + } + + assert.Equal(t, test.want, ua.String()) + }) + } +} diff --git a/internal/crash/handler.go b/internal/crash/handler.go index 115f0773..62729e0f 100644 --- a/internal/crash/handler.go +++ b/internal/crash/handler.go @@ -19,7 +19,7 @@ package crash import ( - "github.com/ProtonMail/proton-bridge/pkg/sentry" + "github.com/ProtonMail/proton-bridge/internal/sentry" "github.com/sirupsen/logrus" ) diff --git a/internal/frontend/frontend.go b/internal/frontend/frontend.go index 85c21aa3..0335d91c 100644 --- a/internal/frontend/frontend.go +++ b/internal/frontend/frontend.go @@ -22,6 +22,7 @@ import ( "github.com/ProtonMail/go-autostart" "github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/config/settings" + "github.com/ProtonMail/proton-bridge/internal/config/useragent" "github.com/ProtonMail/proton-bridge/internal/frontend/cli" cliie "github.com/ProtonMail/proton-bridge/internal/frontend/cli-ie" "github.com/ProtonMail/proton-bridge/internal/frontend/qt" @@ -60,6 +61,7 @@ func New( settings *settings.Settings, eventListener listener.Listener, updater types.Updater, + userAgent *useragent.UserAgent, bridge *bridge.Bridge, noEncConfirmator types.NoEncConfirmator, autostart *autostart.App, @@ -77,6 +79,7 @@ func New( settings, eventListener, updater, + userAgent, bridgeWrap, noEncConfirmator, autostart, @@ -95,6 +98,7 @@ func newBridgeFrontend( settings *settings.Settings, eventListener listener.Listener, updater types.Updater, + userAgent *useragent.UserAgent, bridge types.Bridger, noEncConfirmator types.NoEncConfirmator, autostart *autostart.App, @@ -122,6 +126,7 @@ func newBridgeFrontend( settings, eventListener, updater, + userAgent, bridge, noEncConfirmator, autostart, diff --git a/internal/frontend/qt/frontend.go b/internal/frontend/qt/frontend.go index 3a23aef1..e7d1a5ad 100644 --- a/internal/frontend/qt/frontend.go +++ b/internal/frontend/qt/frontend.go @@ -39,6 +39,7 @@ import ( "github.com/ProtonMail/go-autostart" "github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/config/settings" + "github.com/ProtonMail/proton-bridge/internal/config/useragent" "github.com/ProtonMail/proton-bridge/internal/events" "github.com/ProtonMail/proton-bridge/internal/frontend/autoconfig" qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common" @@ -49,7 +50,6 @@ import ( "github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/ProtonMail/proton-bridge/pkg/ports" - "github.com/ProtonMail/proton-bridge/pkg/useragent" "github.com/sirupsen/logrus" "github.com/skratchdot/open-golang/open" "github.com/therecipe/qt/core" @@ -75,6 +75,7 @@ type FrontendQt struct { settings *settings.Settings eventListener listener.Listener updater types.Updater + userAgent *useragent.UserAgent bridge types.Bridger noEncConfirmator types.NoEncConfirmator @@ -114,12 +115,15 @@ func New( settings *settings.Settings, eventListener listener.Listener, updater types.Updater, + userAgent *useragent.UserAgent, bridge types.Bridger, noEncConfirmator types.NoEncConfirmator, autostart *autostart.App, restarter types.Restarter, ) *FrontendQt { - tmp := &FrontendQt{ + userAgent.SetPlatform(core.QSysInfo_PrettyProductName()) + + f := &FrontendQt{ version: version, buildVersion: buildVersion, programName: programName, @@ -129,6 +133,7 @@ func New( settings: settings, eventListener: eventListener, updater: updater, + userAgent: userAgent, bridge: bridge, noEncConfirmator: noEncConfirmator, programVer: "v" + version, @@ -138,13 +143,9 @@ func New( // Initializing.Done is only called sync.Once. Please keep the increment // set to 1 - tmp.initializing.Add(1) + f.initializing.Add(1) - // Nicer string for OS. - currentOS := core.QSysInfo_PrettyProductName() - bridge.SetCurrentOS(currentOS) - - return tmp + return f } // InstanceExistAlert is a global warning window indicating an instance already exists. @@ -506,7 +507,7 @@ func (s *FrontendQt) sendBug(description, client, address string) (isOK bool) { } func (s *FrontendQt) getLastMailClient() string { - return s.bridge.GetCurrentClient() + return s.userAgent.String() } func (s *FrontendQt) configureAppleMail(iAccount, iAddress int) { diff --git a/internal/frontend/qt/frontend_nogui.go b/internal/frontend/qt/frontend_nogui.go index c3125e21..6db03ead 100644 --- a/internal/frontend/qt/frontend_nogui.go +++ b/internal/frontend/qt/frontend_nogui.go @@ -25,6 +25,7 @@ import ( "github.com/ProtonMail/go-autostart" "github.com/ProtonMail/proton-bridge/internal/config/settings" + "github.com/ProtonMail/proton-bridge/internal/config/useragent" "github.com/ProtonMail/proton-bridge/internal/frontend/types" "github.com/ProtonMail/proton-bridge/internal/locations" "github.com/ProtonMail/proton-bridge/internal/updater" @@ -71,6 +72,7 @@ func New( settings *settings.Settings, eventListener listener.Listener, updater types.Updater, + userAgent *useragent.UserAgent, bridge types.Bridger, noEncConfirmator types.NoEncConfirmator, autostart *autostart.App, diff --git a/internal/frontend/types/types.go b/internal/frontend/types/types.go index 3e4079e5..3b375b70 100644 --- a/internal/frontend/types/types.go +++ b/internal/frontend/types/types.go @@ -75,8 +75,6 @@ type User interface { type Bridger interface { UserManager - GetCurrentClient() string - SetCurrentOS(os string) ReportBug(osType, osVersion, description, accountName, address, emailClient string) error AllowProxy() DisallowProxy() diff --git a/internal/imap/bridge.go b/internal/imap/bridge.go index e950bd59..2be46194 100644 --- a/internal/imap/bridge.go +++ b/internal/imap/bridge.go @@ -29,7 +29,6 @@ type cacheProvider interface { } type bridger interface { - SetCurrentClient(clientName, clientVersion string) GetUser(query string) (bridgeUser, error) } diff --git a/internal/imap/id/extension.go b/internal/imap/id/extension.go index bf7deb2b..63a5a54c 100644 --- a/internal/imap/id/extension.go +++ b/internal/imap/id/extension.go @@ -23,13 +23,13 @@ import ( ) type currentClientSetter interface { - SetCurrentClient(name, version string) + SetClient(name, version string) } // Extension for IMAP server type extension struct { - extID imapserver.ConnExtension - setter currentClientSetter + extID imapserver.ConnExtension + clientSetter currentClientSetter } func (ext *extension) Capabilities(conn imapserver.Conn) []string { @@ -44,8 +44,8 @@ func (ext *extension) Command(name string) imapserver.HandlerFactory { return func() imapserver.Handler { if hdlrID, ok := newIDHandler().(*imapid.Handler); ok { return &handler{ - hdlrID: hdlrID, - setter: ext.setter, + hdlrID: hdlrID, + clientSetter: ext.clientSetter, } } return nil @@ -57,8 +57,8 @@ func (ext *extension) NewConn(conn imapserver.Conn) imapserver.Conn { } type handler struct { - hdlrID *imapid.Handler - setter currentClientSetter + hdlrID *imapid.Handler + clientSetter currentClientSetter } func (hdlr *handler) Parse(fields []interface{}) error { @@ -69,21 +69,18 @@ func (hdlr *handler) Handle(conn imapserver.Conn) error { err := hdlr.hdlrID.Handle(conn) if err == nil { id := hdlr.hdlrID.Command.ID - hdlr.setter.SetCurrentClient( - id[imapid.FieldName], - id[imapid.FieldVersion], - ) + hdlr.clientSetter.SetClient(id[imapid.FieldName], id[imapid.FieldVersion]) } return err } // NewExtension returns extension which is adding RFC2871 ID capability, with // direct interface to set information about email client to backend. -func NewExtension(serverID imapid.ID, setter currentClientSetter) imapserver.Extension { +func NewExtension(serverID imapid.ID, clientSetter currentClientSetter) imapserver.Extension { if conExtID, ok := imapid.NewExtension(serverID).(imapserver.ConnExtension); ok { return &extension{ - extID: conExtID, - setter: setter, + extID: conExtID, + clientSetter: clientSetter, } } return nil diff --git a/internal/imap/server.go b/internal/imap/server.go index b01195ca..4fa2e84c 100644 --- a/internal/imap/server.go +++ b/internal/imap/server.go @@ -28,6 +28,7 @@ import ( 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/uidplus" @@ -39,6 +40,7 @@ import ( imapmove "github.com/emersion/go-imap-move" imapquota "github.com/emersion/go-imap-quota" imapunselect "github.com/emersion/go-imap-unselect" + "github.com/emersion/go-imap/backend" imapserver "github.com/emersion/go-imap/server" "github.com/emersion/go-sasl" "github.com/sirupsen/logrus" @@ -47,6 +49,7 @@ import ( type imapServer struct { panicHandler panicHandler server *imapserver.Server + userAgent *useragent.UserAgent eventListener listener.Listener debugClient bool debugServer bool @@ -55,7 +58,7 @@ type imapServer struct { } // NewIMAPServer constructs a new IMAP server configured with the given options. -func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, port int, tls *tls.Config, imapBackend *imapBackend, eventListener listener.Listener) *imapServer { //nolint[golint] +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 @@ -93,7 +96,7 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por s.Enable( imapidle.NewExtension(), imapmove.NewExtension(), - id.NewExtension(serverID, imapBackend.bridge), + id.NewExtension(serverID, userAgent), imapquota.NewExtension(), imapappendlimit.NewExtension(), imapunselect.NewExtension(), @@ -103,6 +106,7 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por server := &imapServer{ panicHandler: panicHandler, server: s, + userAgent: userAgent, eventListener: eventListener, debugClient: debugClient, debugServer: debugServer, @@ -144,9 +148,10 @@ func (s *imapServer) listenAndServe(retries int) { return } - err = s.server.Serve(&debugListener{ - Listener: l, - server: s, + err = s.server.Serve(&connListener{ + Listener: l, + 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, @@ -233,18 +238,19 @@ func (s *imapServer) monitorDisconnectedUsers() { } } -// debugListener sets debug loggers on server containing fields with local +// connListener sets debug loggers on server containing fields with local // and remote addresses right after new connection is accepted. -type debugListener struct { +type connListener struct { net.Listener - server *imapServer + server *imapServer + userAgent *useragent.UserAgent } -func (dl *debugListener) Accept() (net.Conn, error) { - conn, err := dl.Listener.Accept() +func (l *connListener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() - if err == nil && (dl.server.debugServer || dl.server.debugClient) { + if err == nil && (l.server.debugServer || l.server.debugClient) { debugLog := log if addr := conn.LocalAddr(); addr != nil { debugLog = debugLog.WithField("loc", addr.String()) @@ -254,14 +260,18 @@ func (dl *debugListener) Accept() (net.Conn, error) { } var localDebug, remoteDebug io.Writer - if dl.server.debugServer { + if l.server.debugServer { localDebug = debugLog.WithField("pkg", "imap/server").WriterLevel(logrus.DebugLevel) } - if dl.server.debugClient { + if l.server.debugClient { remoteDebug = debugLog.WithField("pkg", "imap/client").WriterLevel(logrus.DebugLevel) } - dl.server.server.Debug = imap.NewDebugWriter(localDebug, remoteDebug) + l.server.server.Debug = imap.NewDebugWriter(localDebug, remoteDebug) + } + + if !l.userAgent.HasClient() { + l.userAgent.SetClient("UnknownClient", "0.0.1") } return conn, err diff --git a/internal/imap/server_test.go b/internal/imap/server_test.go index 565e045e..d8e497c0 100644 --- a/internal/imap/server_test.go +++ b/internal/imap/server_test.go @@ -23,6 +23,7 @@ import ( "time" "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/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/ports" @@ -48,6 +49,7 @@ func TestIMAPServerTurnOffAndOnAgain(t *testing.T) { panicHandler: panicHandler, server: server, eventListener: eventListener, + userAgent: useragent.New(), } s.isRunning.Store(false) diff --git a/pkg/sentry/reporter.go b/internal/sentry/reporter.go similarity index 90% rename from pkg/sentry/reporter.go rename to internal/sentry/reporter.go index 051b9cc6..b50fa0a6 100644 --- a/pkg/sentry/reporter.go +++ b/internal/sentry/reporter.go @@ -45,28 +45,21 @@ func init() { // nolint[noinit] }) } -type userAgentProvider interface { - GetUserAgent() string -} - type Reporter struct { appName string appVersion string - uap userAgentProvider + userAgent fmt.Stringer } // NewReporter creates new sentry reporter with appName and appVersion to report. -func NewReporter(appName, appVersion string) *Reporter { +func NewReporter(appName, appVersion string, userAgent fmt.Stringer) *Reporter { return &Reporter{ appName: appName, appVersion: appVersion, + userAgent: userAgent, } } -func (r *Reporter) SetUserAgentProvider(uap userAgentProvider) { - r.uap = uap -} - func (r *Reporter) ReportException(i interface{}) error { err := fmt.Errorf("recover: %v", i) @@ -97,19 +90,11 @@ func (r *Reporter) scopedReport(doReport func()) error { return nil } - // In case clientManager is not yet created we can get at least OS string. - var userAgent string - if r.uap != nil { - userAgent = r.uap.GetUserAgent() - } else { - userAgent = runtime.GOOS - } - tags := map[string]string{ "OS": runtime.GOOS, "Client": r.appName, "Version": r.appVersion, - "UserAgent": userAgent, + "UserAgent": r.userAgent.String(), "UserID": "", } diff --git a/pkg/sentry/reporter_test.go b/internal/sentry/reporter_test.go similarity index 79% rename from pkg/sentry/reporter_test.go rename to internal/sentry/reporter_test.go index 6936afb2..8b36fef3 100644 --- a/pkg/sentry/reporter_test.go +++ b/internal/sentry/reporter_test.go @@ -35,8 +35,8 @@ func TestSkipDuringUnwind(t *testing.T) { }() wantSkippedFunctions := []string{ - "github.com/ProtonMail/proton-bridge/pkg/sentry.TestSkipDuringUnwind", - "github.com/ProtonMail/proton-bridge/pkg/sentry.TestSkipDuringUnwind.func1", + "github.com/ProtonMail/proton-bridge/internal/sentry.TestSkipDuringUnwind", + "github.com/ProtonMail/proton-bridge/internal/sentry.TestSkipDuringUnwind.func1", } r.Equal(t, wantSkippedFunctions, skippedFunctions) } @@ -45,8 +45,8 @@ func TestFilterOutPanicHandlers(t *testing.T) { skippedFunctions = []string{ "github.com/ProtonMail/proton-bridge/pkg/config.(*PanicHandler).HandlePanic", "github.com/ProtonMail/proton-bridge/pkg/config.HandlePanic", - "github.com/ProtonMail/proton-bridge/pkg/sentry.ReportSentryCrash", - "github.com/ProtonMail/proton-bridge/pkg/sentry.ReportSentryCrash.func1", + "github.com/ProtonMail/proton-bridge/internal/sentry.ReportSentryCrash", + "github.com/ProtonMail/proton-bridge/internal/sentry.ReportSentryCrash.func1", } frames := []sentry.Frame{ @@ -57,8 +57,8 @@ func TestFilterOutPanicHandlers(t *testing.T) { {Module: "main", Function: "run"}, {Module: "github.com/ProtonMail/proton-bridge/pkg/config", Function: "(*PanicHandler).HandlePanic"}, {Module: "github.com/ProtonMail/proton-bridge/pkg/config", Function: "HandlePanic"}, - {Module: "github.com/ProtonMail/proton-bridge/pkg/sentry", Function: "ReportSentryCrash"}, - {Module: "github.com/ProtonMail/proton-bridge/pkg/sentry", Function: "ReportSentryCrash.func1"}, + {Module: "github.com/ProtonMail/proton-bridge/internal/sentry", Function: "ReportSentryCrash"}, + {Module: "github.com/ProtonMail/proton-bridge/internal/sentry", Function: "ReportSentryCrash.func1"}, } gotFrames := filterOutPanicHandlers(frames) diff --git a/internal/store/store.go b/internal/store/store.go index 6f7482f0..48bb7a03 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -24,9 +24,9 @@ import ( "sync" "time" + "github.com/ProtonMail/proton-bridge/internal/sentry" "github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/pmapi" - "github.com/ProtonMail/proton-bridge/pkg/sentry" "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/sirupsen/logrus" diff --git a/internal/users/mocks/mocks.go b/internal/users/mocks/mocks.go index 78fcd248..3bf8ff39 100644 --- a/internal/users/mocks/mocks.go +++ b/internal/users/mocks/mocks.go @@ -188,18 +188,6 @@ func (mr *MockClientManagerMockRecorder) GetClient(arg0 interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClient", reflect.TypeOf((*MockClientManager)(nil).GetClient), arg0) } -// SetUserAgent mocks base method -func (m *MockClientManager) SetUserAgent(arg0, arg1, arg2 string) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "SetUserAgent", arg0, arg1, arg2) -} - -// SetUserAgent indicates an expected call of SetUserAgent -func (mr *MockClientManagerMockRecorder) SetUserAgent(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUserAgent", reflect.TypeOf((*MockClientManager)(nil).SetUserAgent), arg0, arg1, arg2) -} - // MockCredentialsStorer is a mock of CredentialsStorer interface type MockCredentialsStorer struct { ctrl *gomock.Controller diff --git a/internal/users/types.go b/internal/users/types.go index 975d9325..52e442ad 100644 --- a/internal/users/types.go +++ b/internal/users/types.go @@ -55,7 +55,6 @@ type ClientManager interface { DisallowProxy() GetAuthUpdateChannel() chan pmapi.ClientAuth CheckConnection() error - SetUserAgent(clientName, clientVersion, os string) } type StoreMaker interface { diff --git a/internal/users/users_test.go b/internal/users/users_test.go index f75289ad..f58ddbbc 100644 --- a/internal/users/users_test.go +++ b/internal/users/users_test.go @@ -26,12 +26,12 @@ import ( "time" "github.com/ProtonMail/proton-bridge/internal/events" + "github.com/ProtonMail/proton-bridge/internal/sentry" "github.com/ProtonMail/proton-bridge/internal/store" "github.com/ProtonMail/proton-bridge/internal/users/credentials" usersmocks "github.com/ProtonMail/proton-bridge/internal/users/mocks" "github.com/ProtonMail/proton-bridge/pkg/pmapi" pmapimocks "github.com/ProtonMail/proton-bridge/pkg/pmapi/mocks" - "github.com/ProtonMail/proton-bridge/pkg/sentry" gomock "github.com/golang/mock/gomock" "github.com/sirupsen/logrus" "github.com/stretchr/testify/require" diff --git a/pkg/pmapi/client.go b/pkg/pmapi/client.go index c4de4526..528a17e9 100644 --- a/pkg/pmapi/client.go +++ b/pkg/pmapi/client.go @@ -78,13 +78,6 @@ type ClientConfig struct { // The client application name and version. AppVersion string - // The client application user agent in format `client name/client version (os)`, e.g.: - // (Intel Mac OS X 10_15_3) - // Mac OS X Mail/13.0 (3608.60.0.2.5) (Intel Mac OS X 10_15_3) - // Thunderbird/1.5.0 (Ubuntu 18.04.4 LTS) - // MSOffice 12 (Windows 10 (10.0)) - UserAgent string - // The client ID. ClientID string @@ -236,7 +229,7 @@ func (c *client) Do(req *http.Request, retryUnauthorized bool) (res *http.Respon func (c *client) doBuffered(req *http.Request, bodyBuffer []byte, retryUnauthorized bool) (res *http.Response, err error) { // nolint[funlen] isAuthReq := strings.Contains(req.URL.Path, "/auth") - req.Header.Set("User-Agent", c.cm.config.UserAgent) + req.Header.Set("User-Agent", c.cm.userAgent.String()) req.Header.Set("x-pm-appversion", c.cm.config.AppVersion) if c.uid != "" { diff --git a/pkg/pmapi/clientmanager.go b/pkg/pmapi/clientmanager.go index 15eafe17..b787a14b 100644 --- a/pkg/pmapi/clientmanager.go +++ b/pkg/pmapi/clientmanager.go @@ -24,6 +24,7 @@ import ( "sync" "time" + "github.com/ProtonMail/proton-bridge/internal/config/useragent" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -37,6 +38,7 @@ type ClientManager struct { //nolint[maligned] newClient func(userID string) Client config *ClientConfig + userAgent *useragent.UserAgent roundTripper http.RoundTripper clients map[string]Client @@ -86,9 +88,10 @@ type tokenExpiration struct { } // NewClientManager creates a new ClientMan which manages clients configured with the given client config. -func NewClientManager(config *ClientConfig) (cm *ClientManager) { +func NewClientManager(config *ClientConfig, userAgent *useragent.UserAgent) (cm *ClientManager) { cm = &ClientManager{ config: config, + userAgent: userAgent, roundTripper: http.DefaultTransport, clients: make(map[string]Client), @@ -118,7 +121,6 @@ func NewClientManager(config *ClientConfig) (cm *ClientManager) { cm.newClient = func(userID string) Client { return newClient(cm, userID) } - cm.SetUserAgent("", "", "") // Set default user agent. go cm.watchTokenExpirations() @@ -169,16 +171,12 @@ func (cm *ClientManager) SetRoundTripper(rt http.RoundTripper) { cm.roundTripper = rt } -func (cm *ClientManager) GetClientConfig() *ClientConfig { - return cm.config -} - -func (cm *ClientManager) SetUserAgent(clientName, clientVersion, os string) { - cm.config.UserAgent = formatUserAgent(clientName, clientVersion, os) +func (cm *ClientManager) GetAppVersion() string { + return cm.config.AppVersion } func (cm *ClientManager) GetUserAgent() string { - return cm.config.UserAgent + return cm.userAgent.String() } // GetClient returns a client for the given userID. diff --git a/pkg/pmapi/clientmanager_test.go b/pkg/pmapi/clientmanager_test.go index b4a77091..c482bf93 100644 --- a/pkg/pmapi/clientmanager_test.go +++ b/pkg/pmapi/clientmanager_test.go @@ -17,8 +17,10 @@ package pmapi +import "github.com/ProtonMail/proton-bridge/internal/config/useragent" + func newTestClientManager(cfg *ClientConfig) *ClientManager { - cm := NewClientManager(cfg) + cm := NewClientManager(cfg, useragent.New()) go func() { for range cm.authUpdates { diff --git a/pkg/pmapi/pin_checker.go b/pkg/pmapi/pin_checker.go index 3b2cf1c7..11661f13 100644 --- a/pkg/pmapi/pin_checker.go +++ b/pkg/pmapi/pin_checker.go @@ -75,17 +75,18 @@ func certFingerprint(cert *x509.Certificate) string { return fmt.Sprintf(`pin-sha256=%q`, base64.StdEncoding.EncodeToString(hash[:])) } -type clientConfigProvider interface { - GetClientConfig() *ClientConfig +type clientInfoProvider interface { + GetAppVersion() string + GetUserAgent() string } type tlsReporter struct { - cm clientConfigProvider + cm clientInfoProvider p *pinChecker sentReports []sentReport } -func newTLSReporter(p *pinChecker, cm clientConfigProvider) *tlsReporter { +func newTLSReporter(p *pinChecker, cm clientInfoProvider) *tlsReporter { return &tlsReporter{ cm: cm, p: p, @@ -102,13 +103,14 @@ func (r *tlsReporter) reportCertIssue(remoteURI, host, port string, connState tl certChain = marshalCert7468(connState.PeerCertificates) } - cfg := r.cm.GetClientConfig() + appVersion := r.cm.GetAppVersion() + userAgent := r.cm.GetUserAgent() - report := newTLSReport(host, port, connState.ServerName, certChain, r.p.trustedPins, cfg.AppVersion) + report := newTLSReport(host, port, connState.ServerName, certChain, r.p.trustedPins, appVersion) if !r.hasRecentlySentReport(report) { r.recordReport(report) - go report.sendReport(remoteURI, cfg.UserAgent) + go report.sendReport(remoteURI, userAgent) } } diff --git a/pkg/pmapi/pin_checker_test.go b/pkg/pmapi/pin_checker_test.go index 1c0ad0f4..7511660c 100644 --- a/pkg/pmapi/pin_checker_test.go +++ b/pkg/pmapi/pin_checker_test.go @@ -27,12 +27,16 @@ import ( "github.com/stretchr/testify/assert" ) -type fakeClientConfigProvider struct { +type fakeClientInfoProvider struct { version, useragent string } -func (c *fakeClientConfigProvider) GetClientConfig() *ClientConfig { - return &ClientConfig{AppVersion: c.version, UserAgent: c.useragent} +func (c *fakeClientInfoProvider) GetAppVersion() string { + return c.version +} + +func (c *fakeClientInfoProvider) GetUserAgent() string { + return c.useragent } func TestPinCheckerDoubleReport(t *testing.T) { @@ -42,7 +46,7 @@ func TestPinCheckerDoubleReport(t *testing.T) { reportCounter++ })) - r := newTLSReporter(newPinChecker(TrustedAPIPins), &fakeClientConfigProvider{version: "3", useragent: "useragent"}) + r := newTLSReporter(newPinChecker(TrustedAPIPins), &fakeClientInfoProvider{version: "3", useragent: "useragent"}) // Report the same issue many times. for i := 0; i < 10; i++ { diff --git a/pkg/pmapi/useragent_test.go b/pkg/pmapi/useragent_test.go deleted file mode 100644 index 32dff8ce..00000000 --- a/pkg/pmapi/useragent_test.go +++ /dev/null @@ -1,55 +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 pmapi - -import ( - "runtime" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestUpdateCurrentUserAgentGOOS(t *testing.T) { - userAgent := formatUserAgent("", "", "") - assert.Equal(t, " ("+runtime.GOOS+")", userAgent) -} - -func TestUpdateCurrentUserAgentOS(t *testing.T) { - userAgent := formatUserAgent("", "", "os") - assert.Equal(t, " (os)", userAgent) -} - -func TestUpdateCurrentUserAgentClientVer(t *testing.T) { - userAgent := formatUserAgent("", "ver", "os") - assert.Equal(t, " (os)", userAgent) -} - -func TestUpdateCurrentUserAgentClientName(t *testing.T) { - userAgent := formatUserAgent("mail", "", "os") - assert.Equal(t, "mail (os)", userAgent) -} - -func TestUpdateCurrentUserAgentClientNameAndVersion(t *testing.T) { - userAgent := formatUserAgent("mail", "ver", "os") - assert.Equal(t, "mail/ver (os)", userAgent) -} - -func TestRemoveBrackets(t *testing.T) { - userAgent := formatUserAgent("mail (submail)", "ver (subver)", "os (subos)") - assert.Equal(t, "mail-submail/ver-subver (os subos)", userAgent) -} diff --git a/test/api_checks_test.go b/test/api_checks_test.go index 6eafa77f..0c05de66 100644 --- a/test/api_checks_test.go +++ b/test/api_checks_test.go @@ -41,7 +41,7 @@ func APIChecksFeatureContext(s *godog.Suite) { s.Step(`^API mailbox "([^"]*)" for address "([^"]*)" of "([^"]*)" has (\d+) message(?:s)?$`, apiMailboxForAddressOfUserHasNumberOfMessages) s.Step(`^API mailbox "([^"]*)" for "([^"]*)" has messages$`, apiMailboxForUserHasMessages) s.Step(`^API mailbox "([^"]*)" for address "([^"]*)" of "([^"]*)" has messages$`, apiMailboxForAddressOfUserHasMessages) - s.Step(`^API client manager user-agent is "([^"]*)"$`, clientManagerUserAgent) + s.Step(`^API user-agent is "([^"]*)"$`, userAgent) } func apiIsCalled(endpoint string) error { @@ -187,12 +187,11 @@ func getPMAPIMessages(account *accounts.TestAccount, mailboxName string) ([]*pma return ctx.GetPMAPIController().GetMessages(account.Username(), labelID) } -func clientManagerUserAgent(expectedUserAgent string) error { +func userAgent(expectedUserAgent string) error { expectedUserAgent = strings.ReplaceAll(expectedUserAgent, "[GOOS]", runtime.GOOS) assert.Eventually(ctx.GetTestingT(), func() bool { - userAgent := ctx.GetClientManager().GetUserAgent() - return userAgent == expectedUserAgent + return ctx.GetUserAgent() == expectedUserAgent }, 5*time.Second, time.Second) return nil diff --git a/test/context/bridge.go b/test/context/bridge.go index c3878e24..7f0632ee 100644 --- a/test/context/bridge.go +++ b/test/context/bridge.go @@ -21,10 +21,11 @@ import ( "time" "github.com/ProtonMail/proton-bridge/internal/bridge" + "github.com/ProtonMail/proton-bridge/internal/config/useragent" "github.com/ProtonMail/proton-bridge/internal/constants" + "github.com/ProtonMail/proton-bridge/internal/sentry" "github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/pkg/listener" - "github.com/ProtonMail/proton-bridge/pkg/sentry" ) // GetBridge returns bridge instance. @@ -69,7 +70,7 @@ func newBridgeInstance( eventListener listener.Listener, clientManager users.ClientManager, ) *bridge.Bridge { - sentryReporter := sentry.NewReporter("bridge", constants.Version) + sentryReporter := sentry.NewReporter("bridge", constants.Version, useragent.New()) panicHandler := &panicHandler{t: t} updater := newFakeUpdater() versioner := newFakeVersioner() diff --git a/test/context/context.go b/test/context/context.go index 2b1d4237..eb3be80d 100644 --- a/test/context/context.go +++ b/test/context/context.go @@ -22,6 +22,7 @@ import ( "sync" "github.com/ProtonMail/proton-bridge/internal/bridge" + "github.com/ProtonMail/proton-bridge/internal/config/useragent" "github.com/ProtonMail/proton-bridge/internal/constants" "github.com/ProtonMail/proton-bridge/internal/importexport" "github.com/ProtonMail/proton-bridge/internal/transfer" @@ -46,6 +47,7 @@ type TestContext struct { locations *fakeLocations settings *fakeSettings listener listener.Listener + userAgent *useragent.UserAgent testAccounts *accounts.TestAccounts // pmapiController is used to control real or fake pmapi clients. @@ -95,11 +97,12 @@ type TestContext struct { func New(app string) *TestContext { setLogrusVerbosityFromEnv() - configName := app - if app == "ie" { - configName = "importExport" - } - cm := pmapi.NewClientManager(pmapi.GetAPIConfig(configName, constants.Version)) + userAgent := useragent.New() + + cm := pmapi.NewClientManager( + pmapi.GetAPIConfig(getConfigName(app), constants.Version), + userAgent, + ) ctx := &TestContext{ t: &bddT{}, @@ -107,6 +110,7 @@ func New(app string) *TestContext { locations: newFakeLocations(), settings: newFakeSettings(), listener: listener.New(), + userAgent: userAgent, pmapiController: newPMAPIController(cm), clientManager: cm, testAccounts: newTestAccounts(), @@ -137,6 +141,14 @@ func New(app string) *TestContext { return ctx } +func getConfigName(app string) string { + if app == "ie" { + return "importExport" + } + + return app +} + // Cleanup runs through all cleanup steps. // This can be a deferred call so that it is run even if the test steps failed the test. func (ctx *TestContext) Cleanup() *TestContext { @@ -156,6 +168,11 @@ func (ctx *TestContext) GetClientManager() *pmapi.ClientManager { return ctx.clientManager } +// GetUserAgent returns the current user agent. +func (ctx *TestContext) GetUserAgent() string { + return ctx.userAgent.String() +} + // GetTestingT returns testing.T compatible struct. func (ctx *TestContext) GetTestingT() *bddT { //nolint[golint] return ctx.t diff --git a/test/context/imap.go b/test/context/imap.go index edcbf74b..1f85f802 100644 --- a/test/context/imap.go +++ b/test/context/imap.go @@ -59,7 +59,7 @@ func (ctx *TestContext) withIMAPServer() { tls, _ := tls.New(settingsPath).GetConfig() backend := imap.NewIMAPBackend(ph, ctx.listener, ctx.cache, ctx.bridge) - server := imap.NewIMAPServer(ph, true, true, port, tls, backend, ctx.listener) + server := imap.NewIMAPServer(ph, true, true, port, tls, backend, ctx.userAgent, ctx.listener) go server.ListenAndServe() require.NoError(ctx.t, waitForPort(port, 5*time.Second)) diff --git a/test/features/bridge/imap/user_agent.feature b/test/features/bridge/imap/user_agent.feature index 4919ab12..30adc3e3 100644 --- a/test/features/bridge/imap/user_agent.feature +++ b/test/features/bridge/imap/user_agent.feature @@ -4,21 +4,23 @@ Feature: User agent Scenario: Get user agent Given there is IMAP client logged in as "user" + Then API user-agent is "UnknownClient/0.0.1 ([GOOS])" When IMAP client sends ID with argument: """ "name" "Foo" "version" "1.4.0" """ - Then API client manager user-agent is "Foo/1.4.0 ([GOOS])" + Then API user-agent is "Foo/1.4.0 ([GOOS])" Scenario: Update user agent Given there is IMAP client logged in as "user" + Then API user-agent is "UnknownClient/0.0.1 ([GOOS])" When IMAP client sends ID with argument: """ "name" "Foo" "version" "1.4.0" """ - Then API client manager user-agent is "Foo/1.4.0 ([GOOS])" + Then API user-agent is "Foo/1.4.0 ([GOOS])" When IMAP client sends ID with argument: """ "name" "Bar" "version" "4.2.0" """ - Then API client manager user-agent is "Bar/4.2.0 ([GOOS])" + Then API user-agent is "Bar/4.2.0 ([GOOS])"