GODT-980: placeholder for user agent

This commit is contained in:
James Houlahan
2021-01-28 12:57:37 +01:00
committed by Jakub
parent 4e531d4524
commit 1f25aeab31
35 changed files with 287 additions and 278 deletions

View File

@ -25,13 +25,14 @@ import (
"runtime" "runtime"
"github.com/ProtonMail/gopenpgp/v2/crypto" "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/constants"
"github.com/ProtonMail/proton-bridge/internal/crash" "github.com/ProtonMail/proton-bridge/internal/crash"
"github.com/ProtonMail/proton-bridge/internal/locations" "github.com/ProtonMail/proton-bridge/internal/locations"
"github.com/ProtonMail/proton-bridge/internal/logging" "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/updater"
"github.com/ProtonMail/proton-bridge/internal/versioner" "github.com/ProtonMail/proton-bridge/internal/versioner"
"github.com/ProtonMail/proton-bridge/pkg/sentry"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -44,7 +45,7 @@ var (
) )
func main() { // nolint[funlen] func main() { // nolint[funlen]
reporter := sentry.NewReporter(appName, constants.Version) reporter := sentry.NewReporter(appName, constants.Version, useragent.New())
crashHandler := crash.NewHandler(reporter.ReportException) crashHandler := crash.NewHandler(reporter.ReportException)
defer crashHandler.HandlePanic() defer crashHandler.HandlePanic()

View File

@ -43,19 +43,20 @@ import (
"github.com/ProtonMail/proton-bridge/internal/config/cache" "github.com/ProtonMail/proton-bridge/internal/config/cache"
"github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/config/tls" "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/constants"
"github.com/ProtonMail/proton-bridge/internal/cookies" "github.com/ProtonMail/proton-bridge/internal/cookies"
"github.com/ProtonMail/proton-bridge/internal/crash" "github.com/ProtonMail/proton-bridge/internal/crash"
"github.com/ProtonMail/proton-bridge/internal/events" "github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/locations" "github.com/ProtonMail/proton-bridge/internal/locations"
"github.com/ProtonMail/proton-bridge/internal/logging" "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/updater"
"github.com/ProtonMail/proton-bridge/internal/users/credentials" "github.com/ProtonMail/proton-bridge/internal/users/credentials"
"github.com/ProtonMail/proton-bridge/internal/versioner" "github.com/ProtonMail/proton-bridge/internal/versioner"
"github.com/ProtonMail/proton-bridge/pkg/keychain" "github.com/ProtonMail/proton-bridge/pkg/keychain"
"github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/pkg/sentry"
"github.com/allan-simon/go-singleinstance" "github.com/allan-simon/go-singleinstance"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
@ -86,6 +87,7 @@ type Base struct {
Creds *credentials.Store Creds *credentials.Store
CM *pmapi.ClientManager CM *pmapi.ClientManager
CookieJar *cookies.Jar CookieJar *cookies.Jar
UserAgent *useragent.UserAgent
Updater *updater.Updater Updater *updater.Updater
Versioner *versioner.Versioner Versioner *versioner.Versioner
TLS *tls.TLS TLS *tls.TLS
@ -107,7 +109,10 @@ func New( // nolint[funlen]
keychainName, keychainName,
cacheVersion string, cacheVersion string,
) (*Base, error) { ) (*Base, error) {
sentryReporter := sentry.NewReporter(appName, constants.Version) userAgent := useragent.New()
sentryReporter := sentry.NewReporter(appName, constants.Version, userAgent)
crashHandler := crash.NewHandler( crashHandler := crash.NewHandler(
sentryReporter.ReportException, sentryReporter.ReportException,
crash.ShowErrorNotification(appName), crash.ShowErrorNotification(appName),
@ -181,20 +186,9 @@ func New( // nolint[funlen]
return nil, err return nil, err
} }
apiConfig := pmapi.GetAPIConfig(configName, constants.Version) cm := pmapi.NewClientManager(getAPIConfig(configName, listener), userAgent)
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.SetRoundTripper(pmapi.GetRoundTripper(cm, listener)) cm.SetRoundTripper(pmapi.GetRoundTripper(cm, listener))
cm.SetCookieJar(jar) cm.SetCookieJar(jar)
sentryReporter.SetUserAgentProvider(cm)
key, err := crypto.NewKeyFromArmored(updater.DefaultPublicKey) key, err := crypto.NewKeyFromArmored(updater.DefaultPublicKey)
if err != nil { if err != nil {
@ -245,6 +239,7 @@ func New( // nolint[funlen]
Creds: credentials.NewStore(kc), Creds: credentials.NewStore(kc),
CM: cm, CM: cm,
CookieJar: jar, CookieJar: jar,
UserAgent: userAgent,
Updater: updater, Updater: updater,
Versioner: versioner, Versioner: versioner,
TLS: tls.New(settingsPath), TLS: tls.New(settingsPath),
@ -380,3 +375,13 @@ func (b *Base) doTeardown() error {
return nil 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
}

View File

@ -87,7 +87,7 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
b.CrashHandler, b.CrashHandler,
c.String(flagLogIMAP) == "client" || c.String(flagLogIMAP) == "all", c.String(flagLogIMAP) == "client" || c.String(flagLogIMAP) == "all",
c.String(flagLogIMAP) == "server" || 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() { go func() {
@ -130,6 +130,7 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
b.Settings, b.Settings,
b.Listener, b.Listener,
b.Updater, b.Updater,
b.UserAgent,
bridge, bridge,
smtpBackend, smtpBackend,
b.Autostart, b.Autostart,

View File

@ -26,10 +26,10 @@ import (
"github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/constants" "github.com/ProtonMail/proton-bridge/internal/constants"
"github.com/ProtonMail/proton-bridge/internal/metrics" "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/updater"
"github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/internal/users"
"github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/pkg/sentry"
"github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/listener"
logrus "github.com/sirupsen/logrus" logrus "github.com/sirupsen/logrus"
@ -47,10 +47,6 @@ type Bridge struct {
clientManager users.ClientManager clientManager users.ClientManager
updater Updater updater Updater
versioner Versioner versioner Versioner
userAgentClientName string
userAgentClientVersion string
userAgentOS string
} }
func New( 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. // ReportBug reports a new bug from the user.
func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, emailClient string) error { func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, emailClient string) error {
c := b.clientManager.GetAnonymousClient() c := b.clientManager.GetAnonymousClient()

View File

@ -21,10 +21,10 @@ import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"github.com/ProtonMail/proton-bridge/internal/sentry"
"github.com/ProtonMail/proton-bridge/internal/store" "github.com/ProtonMail/proton-bridge/internal/store"
"github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/internal/users"
"github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/sentry"
) )
type storeFactory struct { type storeFactory struct {

View File

@ -25,29 +25,27 @@ import (
"github.com/Masterminds/semver/v3" "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 { func IsCatalinaOrNewer() bool {
if runtime.GOOS != "darwin" { if runtime.GOOS != "darwin" {
return false return false
} }
return isVersionCatalinaOrNewer(getMacVersion())
}
func getMacVersion() string { rawVersion, err := exec.Command("sw_vers", "-productVersion").Output()
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)
if err != nil { if err != nil {
return false return false
} }
catalina := semver.MustParse("10.15.0") return isVersionCatalinaOrNewer(strings.TrimSpace(string(rawVersion)))
return v.GreaterThan(catalina) || v.Equal(catalina) }
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)
} }

View File

@ -15,38 +15,45 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package pmapi package useragent
import ( import (
"fmt" "fmt"
"regexp"
"runtime" "runtime"
"strings"
) )
// removeBrackets handle unwanted brackets in client identification string and join with given joinBy parameter. type UserAgent struct {
// Mac OS X Mail/13.0 (3601.0.4) -> Mac OS X Mail/13.0-3601.0.4 (joinBy = "-") client, platform string
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
} }
func formatUserAgent(clientName, clientVersion, os string) string { func New() *UserAgent {
client := "" return &UserAgent{
if clientName != "" { client: "",
client = removeBrackets(clientName, "-") platform: runtime.GOOS,
if clientVersion != "" {
client += "/" + removeBrackets(clientVersion, "-")
} }
} }
if os == "" { func (ua *UserAgent) SetClient(name, version string) {
os = runtime.GOOS ua.client = fmt.Sprintf("%v/%v", name, regexp.MustCompile(`(.*) \((.*)\)`).ReplaceAllString(version, "$1-$2"))
} }
os = removeBrackets(os, " ") func (ua *UserAgent) HasClient() bool {
return ua.client != ""
return fmt.Sprintf("%s (%s)", client, os) }
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)
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
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())
})
}
}

View File

@ -19,7 +19,7 @@
package crash package crash
import ( import (
"github.com/ProtonMail/proton-bridge/pkg/sentry" "github.com/ProtonMail/proton-bridge/internal/sentry"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )

View File

@ -22,6 +22,7 @@ import (
"github.com/ProtonMail/go-autostart" "github.com/ProtonMail/go-autostart"
"github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/internal/frontend/cli" "github.com/ProtonMail/proton-bridge/internal/frontend/cli"
cliie "github.com/ProtonMail/proton-bridge/internal/frontend/cli-ie" cliie "github.com/ProtonMail/proton-bridge/internal/frontend/cli-ie"
"github.com/ProtonMail/proton-bridge/internal/frontend/qt" "github.com/ProtonMail/proton-bridge/internal/frontend/qt"
@ -60,6 +61,7 @@ func New(
settings *settings.Settings, settings *settings.Settings,
eventListener listener.Listener, eventListener listener.Listener,
updater types.Updater, updater types.Updater,
userAgent *useragent.UserAgent,
bridge *bridge.Bridge, bridge *bridge.Bridge,
noEncConfirmator types.NoEncConfirmator, noEncConfirmator types.NoEncConfirmator,
autostart *autostart.App, autostart *autostart.App,
@ -77,6 +79,7 @@ func New(
settings, settings,
eventListener, eventListener,
updater, updater,
userAgent,
bridgeWrap, bridgeWrap,
noEncConfirmator, noEncConfirmator,
autostart, autostart,
@ -95,6 +98,7 @@ func newBridgeFrontend(
settings *settings.Settings, settings *settings.Settings,
eventListener listener.Listener, eventListener listener.Listener,
updater types.Updater, updater types.Updater,
userAgent *useragent.UserAgent,
bridge types.Bridger, bridge types.Bridger,
noEncConfirmator types.NoEncConfirmator, noEncConfirmator types.NoEncConfirmator,
autostart *autostart.App, autostart *autostart.App,
@ -122,6 +126,7 @@ func newBridgeFrontend(
settings, settings,
eventListener, eventListener,
updater, updater,
userAgent,
bridge, bridge,
noEncConfirmator, noEncConfirmator,
autostart, autostart,

View File

@ -39,6 +39,7 @@ import (
"github.com/ProtonMail/go-autostart" "github.com/ProtonMail/go-autostart"
"github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/settings" "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/events"
"github.com/ProtonMail/proton-bridge/internal/frontend/autoconfig" "github.com/ProtonMail/proton-bridge/internal/frontend/autoconfig"
qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common" 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/listener"
"github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/pkg/ports" "github.com/ProtonMail/proton-bridge/pkg/ports"
"github.com/ProtonMail/proton-bridge/pkg/useragent"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/skratchdot/open-golang/open" "github.com/skratchdot/open-golang/open"
"github.com/therecipe/qt/core" "github.com/therecipe/qt/core"
@ -75,6 +75,7 @@ type FrontendQt struct {
settings *settings.Settings settings *settings.Settings
eventListener listener.Listener eventListener listener.Listener
updater types.Updater updater types.Updater
userAgent *useragent.UserAgent
bridge types.Bridger bridge types.Bridger
noEncConfirmator types.NoEncConfirmator noEncConfirmator types.NoEncConfirmator
@ -114,12 +115,15 @@ func New(
settings *settings.Settings, settings *settings.Settings,
eventListener listener.Listener, eventListener listener.Listener,
updater types.Updater, updater types.Updater,
userAgent *useragent.UserAgent,
bridge types.Bridger, bridge types.Bridger,
noEncConfirmator types.NoEncConfirmator, noEncConfirmator types.NoEncConfirmator,
autostart *autostart.App, autostart *autostart.App,
restarter types.Restarter, restarter types.Restarter,
) *FrontendQt { ) *FrontendQt {
tmp := &FrontendQt{ userAgent.SetPlatform(core.QSysInfo_PrettyProductName())
f := &FrontendQt{
version: version, version: version,
buildVersion: buildVersion, buildVersion: buildVersion,
programName: programName, programName: programName,
@ -129,6 +133,7 @@ func New(
settings: settings, settings: settings,
eventListener: eventListener, eventListener: eventListener,
updater: updater, updater: updater,
userAgent: userAgent,
bridge: bridge, bridge: bridge,
noEncConfirmator: noEncConfirmator, noEncConfirmator: noEncConfirmator,
programVer: "v" + version, programVer: "v" + version,
@ -138,13 +143,9 @@ func New(
// Initializing.Done is only called sync.Once. Please keep the increment // Initializing.Done is only called sync.Once. Please keep the increment
// set to 1 // set to 1
tmp.initializing.Add(1) f.initializing.Add(1)
// Nicer string for OS. return f
currentOS := core.QSysInfo_PrettyProductName()
bridge.SetCurrentOS(currentOS)
return tmp
} }
// InstanceExistAlert is a global warning window indicating an instance already exists. // 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 { func (s *FrontendQt) getLastMailClient() string {
return s.bridge.GetCurrentClient() return s.userAgent.String()
} }
func (s *FrontendQt) configureAppleMail(iAccount, iAddress int) { func (s *FrontendQt) configureAppleMail(iAccount, iAddress int) {

View File

@ -25,6 +25,7 @@ import (
"github.com/ProtonMail/go-autostart" "github.com/ProtonMail/go-autostart"
"github.com/ProtonMail/proton-bridge/internal/config/settings" "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/frontend/types"
"github.com/ProtonMail/proton-bridge/internal/locations" "github.com/ProtonMail/proton-bridge/internal/locations"
"github.com/ProtonMail/proton-bridge/internal/updater" "github.com/ProtonMail/proton-bridge/internal/updater"
@ -71,6 +72,7 @@ func New(
settings *settings.Settings, settings *settings.Settings,
eventListener listener.Listener, eventListener listener.Listener,
updater types.Updater, updater types.Updater,
userAgent *useragent.UserAgent,
bridge types.Bridger, bridge types.Bridger,
noEncConfirmator types.NoEncConfirmator, noEncConfirmator types.NoEncConfirmator,
autostart *autostart.App, autostart *autostart.App,

View File

@ -75,8 +75,6 @@ type User interface {
type Bridger interface { type Bridger interface {
UserManager UserManager
GetCurrentClient() string
SetCurrentOS(os string)
ReportBug(osType, osVersion, description, accountName, address, emailClient string) error ReportBug(osType, osVersion, description, accountName, address, emailClient string) error
AllowProxy() AllowProxy()
DisallowProxy() DisallowProxy()

View File

@ -29,7 +29,6 @@ type cacheProvider interface {
} }
type bridger interface { type bridger interface {
SetCurrentClient(clientName, clientVersion string)
GetUser(query string) (bridgeUser, error) GetUser(query string) (bridgeUser, error)
} }

View File

@ -23,13 +23,13 @@ import (
) )
type currentClientSetter interface { type currentClientSetter interface {
SetCurrentClient(name, version string) SetClient(name, version string)
} }
// Extension for IMAP server // Extension for IMAP server
type extension struct { type extension struct {
extID imapserver.ConnExtension extID imapserver.ConnExtension
setter currentClientSetter clientSetter currentClientSetter
} }
func (ext *extension) Capabilities(conn imapserver.Conn) []string { func (ext *extension) Capabilities(conn imapserver.Conn) []string {
@ -45,7 +45,7 @@ func (ext *extension) Command(name string) imapserver.HandlerFactory {
if hdlrID, ok := newIDHandler().(*imapid.Handler); ok { if hdlrID, ok := newIDHandler().(*imapid.Handler); ok {
return &handler{ return &handler{
hdlrID: hdlrID, hdlrID: hdlrID,
setter: ext.setter, clientSetter: ext.clientSetter,
} }
} }
return nil return nil
@ -58,7 +58,7 @@ func (ext *extension) NewConn(conn imapserver.Conn) imapserver.Conn {
type handler struct { type handler struct {
hdlrID *imapid.Handler hdlrID *imapid.Handler
setter currentClientSetter clientSetter currentClientSetter
} }
func (hdlr *handler) Parse(fields []interface{}) error { func (hdlr *handler) Parse(fields []interface{}) error {
@ -69,21 +69,18 @@ func (hdlr *handler) Handle(conn imapserver.Conn) error {
err := hdlr.hdlrID.Handle(conn) err := hdlr.hdlrID.Handle(conn)
if err == nil { if err == nil {
id := hdlr.hdlrID.Command.ID id := hdlr.hdlrID.Command.ID
hdlr.setter.SetCurrentClient( hdlr.clientSetter.SetClient(id[imapid.FieldName], id[imapid.FieldVersion])
id[imapid.FieldName],
id[imapid.FieldVersion],
)
} }
return err return err
} }
// NewExtension returns extension which is adding RFC2871 ID capability, with // NewExtension returns extension which is adding RFC2871 ID capability, with
// direct interface to set information about email client to backend. // 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 { if conExtID, ok := imapid.NewExtension(serverID).(imapserver.ConnExtension); ok {
return &extension{ return &extension{
extID: conExtID, extID: conExtID,
setter: setter, clientSetter: clientSetter,
} }
} }
return nil return nil

View File

@ -28,6 +28,7 @@ import (
imapid "github.com/ProtonMail/go-imap-id" imapid "github.com/ProtonMail/go-imap-id"
"github.com/ProtonMail/proton-bridge/internal/bridge" "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/events"
"github.com/ProtonMail/proton-bridge/internal/imap/id" "github.com/ProtonMail/proton-bridge/internal/imap/id"
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus" "github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
@ -39,6 +40,7 @@ import (
imapmove "github.com/emersion/go-imap-move" imapmove "github.com/emersion/go-imap-move"
imapquota "github.com/emersion/go-imap-quota" imapquota "github.com/emersion/go-imap-quota"
imapunselect "github.com/emersion/go-imap-unselect" imapunselect "github.com/emersion/go-imap-unselect"
"github.com/emersion/go-imap/backend"
imapserver "github.com/emersion/go-imap/server" imapserver "github.com/emersion/go-imap/server"
"github.com/emersion/go-sasl" "github.com/emersion/go-sasl"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -47,6 +49,7 @@ import (
type imapServer struct { type imapServer struct {
panicHandler panicHandler panicHandler panicHandler
server *imapserver.Server server *imapserver.Server
userAgent *useragent.UserAgent
eventListener listener.Listener eventListener listener.Listener
debugClient bool debugClient bool
debugServer bool debugServer bool
@ -55,7 +58,7 @@ type imapServer struct {
} }
// NewIMAPServer constructs a new IMAP server configured with the given options. // 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 := imapserver.New(imapBackend)
s.Addr = fmt.Sprintf("%v:%v", bridge.Host, port) s.Addr = fmt.Sprintf("%v:%v", bridge.Host, port)
s.TLSConfig = tls s.TLSConfig = tls
@ -93,7 +96,7 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por
s.Enable( s.Enable(
imapidle.NewExtension(), imapidle.NewExtension(),
imapmove.NewExtension(), imapmove.NewExtension(),
id.NewExtension(serverID, imapBackend.bridge), id.NewExtension(serverID, userAgent),
imapquota.NewExtension(), imapquota.NewExtension(),
imapappendlimit.NewExtension(), imapappendlimit.NewExtension(),
imapunselect.NewExtension(), imapunselect.NewExtension(),
@ -103,6 +106,7 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por
server := &imapServer{ server := &imapServer{
panicHandler: panicHandler, panicHandler: panicHandler,
server: s, server: s,
userAgent: userAgent,
eventListener: eventListener, eventListener: eventListener,
debugClient: debugClient, debugClient: debugClient,
debugServer: debugServer, debugServer: debugServer,
@ -144,9 +148,10 @@ func (s *imapServer) listenAndServe(retries int) {
return return
} }
err = s.server.Serve(&debugListener{ err = s.server.Serve(&connListener{
Listener: l, Listener: l,
server: s, server: s,
userAgent: s.userAgent,
}) })
// Serve returns error every time, even after closing the server. // Serve returns error every time, even after closing the server.
// User shouldn't be notified about error if server shouldn't be running, // 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. // and remote addresses right after new connection is accepted.
type debugListener struct { type connListener struct {
net.Listener net.Listener
server *imapServer server *imapServer
userAgent *useragent.UserAgent
} }
func (dl *debugListener) Accept() (net.Conn, error) { func (l *connListener) Accept() (net.Conn, error) {
conn, err := dl.Listener.Accept() 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 debugLog := log
if addr := conn.LocalAddr(); addr != nil { if addr := conn.LocalAddr(); addr != nil {
debugLog = debugLog.WithField("loc", addr.String()) debugLog = debugLog.WithField("loc", addr.String())
@ -254,14 +260,18 @@ func (dl *debugListener) Accept() (net.Conn, error) {
} }
var localDebug, remoteDebug io.Writer var localDebug, remoteDebug io.Writer
if dl.server.debugServer { if l.server.debugServer {
localDebug = debugLog.WithField("pkg", "imap/server").WriterLevel(logrus.DebugLevel) 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) 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 return conn, err

View File

@ -23,6 +23,7 @@ import (
"time" "time"
"github.com/ProtonMail/proton-bridge/internal/bridge" "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/events"
"github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/ports" "github.com/ProtonMail/proton-bridge/pkg/ports"
@ -48,6 +49,7 @@ func TestIMAPServerTurnOffAndOnAgain(t *testing.T) {
panicHandler: panicHandler, panicHandler: panicHandler,
server: server, server: server,
eventListener: eventListener, eventListener: eventListener,
userAgent: useragent.New(),
} }
s.isRunning.Store(false) s.isRunning.Store(false)

View File

@ -45,28 +45,21 @@ func init() { // nolint[noinit]
}) })
} }
type userAgentProvider interface {
GetUserAgent() string
}
type Reporter struct { type Reporter struct {
appName string appName string
appVersion string appVersion string
uap userAgentProvider userAgent fmt.Stringer
} }
// NewReporter creates new sentry reporter with appName and appVersion to report. // 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{ return &Reporter{
appName: appName, appName: appName,
appVersion: appVersion, appVersion: appVersion,
userAgent: userAgent,
} }
} }
func (r *Reporter) SetUserAgentProvider(uap userAgentProvider) {
r.uap = uap
}
func (r *Reporter) ReportException(i interface{}) error { func (r *Reporter) ReportException(i interface{}) error {
err := fmt.Errorf("recover: %v", i) err := fmt.Errorf("recover: %v", i)
@ -97,19 +90,11 @@ func (r *Reporter) scopedReport(doReport func()) error {
return nil 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{ tags := map[string]string{
"OS": runtime.GOOS, "OS": runtime.GOOS,
"Client": r.appName, "Client": r.appName,
"Version": r.appVersion, "Version": r.appVersion,
"UserAgent": userAgent, "UserAgent": r.userAgent.String(),
"UserID": "", "UserID": "",
} }

View File

@ -35,8 +35,8 @@ func TestSkipDuringUnwind(t *testing.T) {
}() }()
wantSkippedFunctions := []string{ wantSkippedFunctions := []string{
"github.com/ProtonMail/proton-bridge/pkg/sentry.TestSkipDuringUnwind", "github.com/ProtonMail/proton-bridge/internal/sentry.TestSkipDuringUnwind",
"github.com/ProtonMail/proton-bridge/pkg/sentry.TestSkipDuringUnwind.func1", "github.com/ProtonMail/proton-bridge/internal/sentry.TestSkipDuringUnwind.func1",
} }
r.Equal(t, wantSkippedFunctions, skippedFunctions) r.Equal(t, wantSkippedFunctions, skippedFunctions)
} }
@ -45,8 +45,8 @@ func TestFilterOutPanicHandlers(t *testing.T) {
skippedFunctions = []string{ skippedFunctions = []string{
"github.com/ProtonMail/proton-bridge/pkg/config.(*PanicHandler).HandlePanic", "github.com/ProtonMail/proton-bridge/pkg/config.(*PanicHandler).HandlePanic",
"github.com/ProtonMail/proton-bridge/pkg/config.HandlePanic", "github.com/ProtonMail/proton-bridge/pkg/config.HandlePanic",
"github.com/ProtonMail/proton-bridge/pkg/sentry.ReportSentryCrash", "github.com/ProtonMail/proton-bridge/internal/sentry.ReportSentryCrash",
"github.com/ProtonMail/proton-bridge/pkg/sentry.ReportSentryCrash.func1", "github.com/ProtonMail/proton-bridge/internal/sentry.ReportSentryCrash.func1",
} }
frames := []sentry.Frame{ frames := []sentry.Frame{
@ -57,8 +57,8 @@ func TestFilterOutPanicHandlers(t *testing.T) {
{Module: "main", Function: "run"}, {Module: "main", Function: "run"},
{Module: "github.com/ProtonMail/proton-bridge/pkg/config", Function: "(*PanicHandler).HandlePanic"}, {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/config", Function: "HandlePanic"},
{Module: "github.com/ProtonMail/proton-bridge/pkg/sentry", Function: "ReportSentryCrash"}, {Module: "github.com/ProtonMail/proton-bridge/internal/sentry", Function: "ReportSentryCrash"},
{Module: "github.com/ProtonMail/proton-bridge/pkg/sentry", Function: "ReportSentryCrash.func1"}, {Module: "github.com/ProtonMail/proton-bridge/internal/sentry", Function: "ReportSentryCrash.func1"},
} }
gotFrames := filterOutPanicHandlers(frames) gotFrames := filterOutPanicHandlers(frames)

View File

@ -24,9 +24,9 @@ import (
"sync" "sync"
"time" "time"
"github.com/ProtonMail/proton-bridge/internal/sentry"
"github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/pkg/sentry"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"

View File

@ -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) 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 // MockCredentialsStorer is a mock of CredentialsStorer interface
type MockCredentialsStorer struct { type MockCredentialsStorer struct {
ctrl *gomock.Controller ctrl *gomock.Controller

View File

@ -55,7 +55,6 @@ type ClientManager interface {
DisallowProxy() DisallowProxy()
GetAuthUpdateChannel() chan pmapi.ClientAuth GetAuthUpdateChannel() chan pmapi.ClientAuth
CheckConnection() error CheckConnection() error
SetUserAgent(clientName, clientVersion, os string)
} }
type StoreMaker interface { type StoreMaker interface {

View File

@ -26,12 +26,12 @@ import (
"time" "time"
"github.com/ProtonMail/proton-bridge/internal/events" "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/store"
"github.com/ProtonMail/proton-bridge/internal/users/credentials" "github.com/ProtonMail/proton-bridge/internal/users/credentials"
usersmocks "github.com/ProtonMail/proton-bridge/internal/users/mocks" usersmocks "github.com/ProtonMail/proton-bridge/internal/users/mocks"
"github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/ProtonMail/proton-bridge/pkg/pmapi"
pmapimocks "github.com/ProtonMail/proton-bridge/pkg/pmapi/mocks" pmapimocks "github.com/ProtonMail/proton-bridge/pkg/pmapi/mocks"
"github.com/ProtonMail/proton-bridge/pkg/sentry"
gomock "github.com/golang/mock/gomock" gomock "github.com/golang/mock/gomock"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"

View File

@ -78,13 +78,6 @@ type ClientConfig struct {
// The client application name and version. // The client application name and version.
AppVersion string 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. // The client ID.
ClientID string 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] 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") 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) req.Header.Set("x-pm-appversion", c.cm.config.AppVersion)
if c.uid != "" { if c.uid != "" {

View File

@ -24,6 +24,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -37,6 +38,7 @@ type ClientManager struct { //nolint[maligned]
newClient func(userID string) Client newClient func(userID string) Client
config *ClientConfig config *ClientConfig
userAgent *useragent.UserAgent
roundTripper http.RoundTripper roundTripper http.RoundTripper
clients map[string]Client 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. // 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{ cm = &ClientManager{
config: config, config: config,
userAgent: userAgent,
roundTripper: http.DefaultTransport, roundTripper: http.DefaultTransport,
clients: make(map[string]Client), clients: make(map[string]Client),
@ -118,7 +121,6 @@ func NewClientManager(config *ClientConfig) (cm *ClientManager) {
cm.newClient = func(userID string) Client { cm.newClient = func(userID string) Client {
return newClient(cm, userID) return newClient(cm, userID)
} }
cm.SetUserAgent("", "", "") // Set default user agent.
go cm.watchTokenExpirations() go cm.watchTokenExpirations()
@ -169,16 +171,12 @@ func (cm *ClientManager) SetRoundTripper(rt http.RoundTripper) {
cm.roundTripper = rt cm.roundTripper = rt
} }
func (cm *ClientManager) GetClientConfig() *ClientConfig { func (cm *ClientManager) GetAppVersion() string {
return cm.config return cm.config.AppVersion
}
func (cm *ClientManager) SetUserAgent(clientName, clientVersion, os string) {
cm.config.UserAgent = formatUserAgent(clientName, clientVersion, os)
} }
func (cm *ClientManager) GetUserAgent() string { func (cm *ClientManager) GetUserAgent() string {
return cm.config.UserAgent return cm.userAgent.String()
} }
// GetClient returns a client for the given userID. // GetClient returns a client for the given userID.

View File

@ -17,8 +17,10 @@
package pmapi package pmapi
import "github.com/ProtonMail/proton-bridge/internal/config/useragent"
func newTestClientManager(cfg *ClientConfig) *ClientManager { func newTestClientManager(cfg *ClientConfig) *ClientManager {
cm := NewClientManager(cfg) cm := NewClientManager(cfg, useragent.New())
go func() { go func() {
for range cm.authUpdates { for range cm.authUpdates {

View File

@ -75,17 +75,18 @@ func certFingerprint(cert *x509.Certificate) string {
return fmt.Sprintf(`pin-sha256=%q`, base64.StdEncoding.EncodeToString(hash[:])) return fmt.Sprintf(`pin-sha256=%q`, base64.StdEncoding.EncodeToString(hash[:]))
} }
type clientConfigProvider interface { type clientInfoProvider interface {
GetClientConfig() *ClientConfig GetAppVersion() string
GetUserAgent() string
} }
type tlsReporter struct { type tlsReporter struct {
cm clientConfigProvider cm clientInfoProvider
p *pinChecker p *pinChecker
sentReports []sentReport sentReports []sentReport
} }
func newTLSReporter(p *pinChecker, cm clientConfigProvider) *tlsReporter { func newTLSReporter(p *pinChecker, cm clientInfoProvider) *tlsReporter {
return &tlsReporter{ return &tlsReporter{
cm: cm, cm: cm,
p: p, p: p,
@ -102,13 +103,14 @@ func (r *tlsReporter) reportCertIssue(remoteURI, host, port string, connState tl
certChain = marshalCert7468(connState.PeerCertificates) 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) { if !r.hasRecentlySentReport(report) {
r.recordReport(report) r.recordReport(report)
go report.sendReport(remoteURI, cfg.UserAgent) go report.sendReport(remoteURI, userAgent)
} }
} }

View File

@ -27,12 +27,16 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
type fakeClientConfigProvider struct { type fakeClientInfoProvider struct {
version, useragent string version, useragent string
} }
func (c *fakeClientConfigProvider) GetClientConfig() *ClientConfig { func (c *fakeClientInfoProvider) GetAppVersion() string {
return &ClientConfig{AppVersion: c.version, UserAgent: c.useragent} return c.version
}
func (c *fakeClientInfoProvider) GetUserAgent() string {
return c.useragent
} }
func TestPinCheckerDoubleReport(t *testing.T) { func TestPinCheckerDoubleReport(t *testing.T) {
@ -42,7 +46,7 @@ func TestPinCheckerDoubleReport(t *testing.T) {
reportCounter++ 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. // Report the same issue many times.
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {

View File

@ -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 <https://www.gnu.org/licenses/>.
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)
}

View File

@ -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 address "([^"]*)" of "([^"]*)" has (\d+) message(?:s)?$`, apiMailboxForAddressOfUserHasNumberOfMessages)
s.Step(`^API mailbox "([^"]*)" for "([^"]*)" has messages$`, apiMailboxForUserHasMessages) s.Step(`^API mailbox "([^"]*)" for "([^"]*)" has messages$`, apiMailboxForUserHasMessages)
s.Step(`^API mailbox "([^"]*)" for address "([^"]*)" of "([^"]*)" has messages$`, apiMailboxForAddressOfUserHasMessages) 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 { func apiIsCalled(endpoint string) error {
@ -187,12 +187,11 @@ func getPMAPIMessages(account *accounts.TestAccount, mailboxName string) ([]*pma
return ctx.GetPMAPIController().GetMessages(account.Username(), labelID) return ctx.GetPMAPIController().GetMessages(account.Username(), labelID)
} }
func clientManagerUserAgent(expectedUserAgent string) error { func userAgent(expectedUserAgent string) error {
expectedUserAgent = strings.ReplaceAll(expectedUserAgent, "[GOOS]", runtime.GOOS) expectedUserAgent = strings.ReplaceAll(expectedUserAgent, "[GOOS]", runtime.GOOS)
assert.Eventually(ctx.GetTestingT(), func() bool { assert.Eventually(ctx.GetTestingT(), func() bool {
userAgent := ctx.GetClientManager().GetUserAgent() return ctx.GetUserAgent() == expectedUserAgent
return userAgent == expectedUserAgent
}, 5*time.Second, time.Second) }, 5*time.Second, time.Second)
return nil return nil

View File

@ -21,10 +21,11 @@ import (
"time" "time"
"github.com/ProtonMail/proton-bridge/internal/bridge" "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/constants"
"github.com/ProtonMail/proton-bridge/internal/sentry"
"github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/internal/users"
"github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/sentry"
) )
// GetBridge returns bridge instance. // GetBridge returns bridge instance.
@ -69,7 +70,7 @@ func newBridgeInstance(
eventListener listener.Listener, eventListener listener.Listener,
clientManager users.ClientManager, clientManager users.ClientManager,
) *bridge.Bridge { ) *bridge.Bridge {
sentryReporter := sentry.NewReporter("bridge", constants.Version) sentryReporter := sentry.NewReporter("bridge", constants.Version, useragent.New())
panicHandler := &panicHandler{t: t} panicHandler := &panicHandler{t: t}
updater := newFakeUpdater() updater := newFakeUpdater()
versioner := newFakeVersioner() versioner := newFakeVersioner()

View File

@ -22,6 +22,7 @@ import (
"sync" "sync"
"github.com/ProtonMail/proton-bridge/internal/bridge" "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/constants"
"github.com/ProtonMail/proton-bridge/internal/importexport" "github.com/ProtonMail/proton-bridge/internal/importexport"
"github.com/ProtonMail/proton-bridge/internal/transfer" "github.com/ProtonMail/proton-bridge/internal/transfer"
@ -46,6 +47,7 @@ type TestContext struct {
locations *fakeLocations locations *fakeLocations
settings *fakeSettings settings *fakeSettings
listener listener.Listener listener listener.Listener
userAgent *useragent.UserAgent
testAccounts *accounts.TestAccounts testAccounts *accounts.TestAccounts
// pmapiController is used to control real or fake pmapi clients. // pmapiController is used to control real or fake pmapi clients.
@ -95,11 +97,12 @@ type TestContext struct {
func New(app string) *TestContext { func New(app string) *TestContext {
setLogrusVerbosityFromEnv() setLogrusVerbosityFromEnv()
configName := app userAgent := useragent.New()
if app == "ie" {
configName = "importExport" cm := pmapi.NewClientManager(
} pmapi.GetAPIConfig(getConfigName(app), constants.Version),
cm := pmapi.NewClientManager(pmapi.GetAPIConfig(configName, constants.Version)) userAgent,
)
ctx := &TestContext{ ctx := &TestContext{
t: &bddT{}, t: &bddT{},
@ -107,6 +110,7 @@ func New(app string) *TestContext {
locations: newFakeLocations(), locations: newFakeLocations(),
settings: newFakeSettings(), settings: newFakeSettings(),
listener: listener.New(), listener: listener.New(),
userAgent: userAgent,
pmapiController: newPMAPIController(cm), pmapiController: newPMAPIController(cm),
clientManager: cm, clientManager: cm,
testAccounts: newTestAccounts(), testAccounts: newTestAccounts(),
@ -137,6 +141,14 @@ func New(app string) *TestContext {
return ctx return ctx
} }
func getConfigName(app string) string {
if app == "ie" {
return "importExport"
}
return app
}
// Cleanup runs through all cleanup steps. // 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. // This can be a deferred call so that it is run even if the test steps failed the test.
func (ctx *TestContext) Cleanup() *TestContext { func (ctx *TestContext) Cleanup() *TestContext {
@ -156,6 +168,11 @@ func (ctx *TestContext) GetClientManager() *pmapi.ClientManager {
return ctx.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. // GetTestingT returns testing.T compatible struct.
func (ctx *TestContext) GetTestingT() *bddT { //nolint[golint] func (ctx *TestContext) GetTestingT() *bddT { //nolint[golint]
return ctx.t return ctx.t

View File

@ -59,7 +59,7 @@ func (ctx *TestContext) withIMAPServer() {
tls, _ := tls.New(settingsPath).GetConfig() tls, _ := tls.New(settingsPath).GetConfig()
backend := imap.NewIMAPBackend(ph, ctx.listener, ctx.cache, ctx.bridge) 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() go server.ListenAndServe()
require.NoError(ctx.t, waitForPort(port, 5*time.Second)) require.NoError(ctx.t, waitForPort(port, 5*time.Second))

View File

@ -4,21 +4,23 @@ Feature: User agent
Scenario: Get user agent Scenario: Get user agent
Given there is IMAP client logged in as "user" 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: When IMAP client sends ID with argument:
""" """
"name" "Foo" "version" "1.4.0" "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 Scenario: Update user agent
Given there is IMAP client logged in as "user" 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: When IMAP client sends ID with argument:
""" """
"name" "Foo" "version" "1.4.0" "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: When IMAP client sends ID with argument:
""" """
"name" "Bar" "version" "4.2.0" "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])"