Compare commits

...

24 Commits

Author SHA1 Message Date
da069a0155 chore: Zaehringen Bridge 3.10.0 changelog. 2024-03-06 10:33:17 +01:00
8e49c84a12 chore: changelog update. 2024-03-06 08:19:13 +01:00
754d80d097 feat(GODT-3193): assume text content type on attachments. 2024-03-01 15:25:37 +00:00
63e272e270 feat(GODT-3193): preserve attachment encoding. 2024-03-01 15:25:37 +00:00
54859a34b2 fix(GODT-3290): fix test failing because of leap day. 2024-03-01 10:56:24 +01:00
9b1feed68b feat(GODT-3214): encrypt only with primary key. 2024-02-28 13:42:09 +00:00
c9b6cc162b feat(GODT-3199): add package log field. 2024-02-27 13:07:37 +01:00
bf3c90b8e9 test(GODT-1602): rebased GPA changes. 2024-02-26 16:56:52 +01:00
8d63fb2301 feat(GODT-2662): enable cache on darwin tart. 2024-02-23 10:33:26 +01:00
7953306cc8 feat(GODT-2662): use tart runner for darwin jobs. 2024-02-23 10:00:47 +01:00
37352d44d2 test(GODT-1602): run integration tests against black 🖤 2024-02-19 10:43:35 +00:00
2a1aeb208d test(GODT-3257): quad9 provider test not working on CI. 2024-02-19 10:06:02 +01:00
94fbe260e4 test(GODT-3220): Fix linting issues by deleting a function
-Deleted a function that was no longer used

GODT-3220
2024-02-14 08:57:48 +01:00
6d4937222e test(GODT-3220): Rollback to a test scenario for logging in with an alias address
-Added test scenario for logging in with an alias address

GODT-3220
2024-02-13 10:56:23 +00:00
e33bad7bf1 test(GODT-3220): Add test scenario for sending an HTML msg with public key and multiple attachments to Internal
-Added test scenario for sending an HTML msg with public key and multiple attachments to Internal
- Verified the message on receipient's side

GODT-3220
2024-02-13 10:56:23 +00:00
70fdc91aff test(GODT-3220): Add test scenario for sending a message to multiple bcc accounts
- Added test scenario for sending a message to two bcc accounts
- Verified on recipients' side that the message is received

GODT-3220
2024-02-13 10:56:23 +00:00
bde8e45b37 test(GODT-3220): Add test scenarios for loging in with an alias address
-Added test scenarios for logging in with an alias address and logging in with an alias address that no longer exists

GODT-3220
2024-02-13 10:56:23 +00:00
6cb2d944d0 test(GODT-3220): Add test scenarios for logining in with alias address and loging in with an alias address
-Added a test scenario for logging in with an alias address
-Added a test scenario for logging in with alias address that no longer exists

GODT-3220
2024-02-13 10:56:23 +00:00
cf0f59afc0 test(GODT-3220): Add scenario cannot login with deleted alias 2024-02-13 10:56:23 +00:00
65d8fbbf31 test: keep deleted address in test suite 2024-02-13 10:56:23 +00:00
d919c0accf test(GODT-3220): Add step definition for logging in with alias address
GODT-3220
2024-02-13 10:56:23 +00:00
0ca07066db test(GODT-3220): Create function for getting the test user by address
GODT-3220
2024-02-13 10:56:23 +00:00
7fa1948c21 chore: Ypsilon Bridge 3.9.1 changelog. 2024-02-02 22:20:43 +01:00
413ab1fc1e fix(GODT-3235): update bridge update key 2024-02-02 22:10:28 +01:00
78 changed files with 1565 additions and 538 deletions

View File

@ -3,6 +3,29 @@
Changelog [format](http://keepachangelog.com/en/1.0.0/)
## Zaehringen Bridge 3.10.0
### Added
* GODT-3199: Add package log field.
* GODT-3220: Add more test scenarios.
### Changed
* GODT-3193: Preserve attachment encoding.
* GODT-3214: Encrypt only with primary key.
* GODT-2662: Use tart runner for darwin jobs.
* GODT-1602: Test: run integration tests against black 🖤.
* GODT-3257: Test: quad9 provider test not working on CI.
### Fixed
* GODT-3290: Fix test failing because of leap day.
## Ypsilon Bridge 3.9.1
### Fixed
* GODT-3235: Update bridge update key.
## Ypsilon Bridge 3.9.0
### Added
@ -37,6 +60,12 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
* GODT-3188: Happy new year.
## Xikou Bridge 3.8.2
### Fixed
* GODT-3235: Update bridge update key.
## Xikou Bridge 3.8.1
### Added

View File

@ -11,7 +11,7 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
.PHONY: build build-gui build-nogui build-launcher versioner hasher
# Keep version hardcoded so app build works also without Git repository.
BRIDGE_APP_VERSION?=3.9.0+git
BRIDGE_APP_VERSION?=3.10.0+git
APP_VERSION:=${BRIDGE_APP_VERSION}
APP_FULL_NAME:=Proton Mail Bridge
APP_VENDOR:=Proton AG

View File

@ -13,12 +13,22 @@
- windows-bridge
.env-darwin:
extends:
- .image-darwin-build
before_script:
- export BRIDGE_SYNC_FORCE_MINIMUM_SPEC=1
- !reference [.before-script-darwin-build, before_script]
cache: {}
tags:
- macos-m1-bridge
- !reference [.before-script-darwin-tart-build, before_script]
- !reference [.before-script-git-config, before_script]
- mkdir -p .cache/bin
- export PATH=$(pwd)/.cache/bin:$PATH
- export GOPATH="$CI_PROJECT_DIR/.cache"
variables:
VCPKG_DEFAULT_BINARY_CACHE: ${CI_PROJECT_DIR}/.cache
cache:
key: darwin-go-and-vcpkg
paths:
- .cache
when: 'always'
.env-linux-build:
extends:

4
go.mod
View File

@ -5,9 +5,9 @@ go 1.21
require (
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
github.com/Masterminds/semver/v3 v3.2.0
github.com/ProtonMail/gluon v0.17.1-0.20240102132144-89b40fb6fe7e
github.com/ProtonMail/gluon v0.17.1-0.20240227105633-3734c7694bcd
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
github.com/ProtonMail/go-proton-api v0.4.1-0.20231130083229-e8aa47d7a366
github.com/ProtonMail/go-proton-api v0.4.1-0.20240226161523-ec58ed7ea4b9
github.com/ProtonMail/gopenpgp/v2 v2.7.4-proton
github.com/PuerkitoBio/goquery v1.8.1
github.com/abiosoft/ishell v2.0.0+incompatible

8
go.sum
View File

@ -27,8 +27,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
github.com/ProtonMail/gluon v0.17.1-0.20240102132144-89b40fb6fe7e h1:DR97ydcuS4/EjTTCkp7F9IRCi+ykD1UoAP7UBFtEcRA=
github.com/ProtonMail/gluon v0.17.1-0.20240102132144-89b40fb6fe7e/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo=
github.com/ProtonMail/gluon v0.17.1-0.20240227105633-3734c7694bcd h1:AjJsf5xQGmZPg6GLn+wB+eBoGRopJlG70lQBfSyfX+M=
github.com/ProtonMail/gluon v0.17.1-0.20240227105633-3734c7694bcd/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo=
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
@ -38,8 +38,8 @@ github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7 h1:+j+Kd/
github.com/ProtonMail/go-message v0.13.1-0.20230526094639-b62c999c85b7/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/go-proton-api v0.4.1-0.20231130083229-e8aa47d7a366 h1:W9P5GdDnuGkB3tbzKnXmUrTjIs6zk/K+4lpPTWzsoRE=
github.com/ProtonMail/go-proton-api v0.4.1-0.20231130083229-e8aa47d7a366/go.mod h1:t+hb0BfkmZ9fpvzVRpHC7limoowym6ln/j0XL9a8DDw=
github.com/ProtonMail/go-proton-api v0.4.1-0.20240226161523-ec58ed7ea4b9 h1:tcQpGQljNsZmfuA6L4hAzio8/AIx5OXcU2JUdyX/qxw=
github.com/ProtonMail/go-proton-api v0.4.1-0.20240226161523-ec58ed7ea4b9/go.mod h1:t+hb0BfkmZ9fpvzVRpHC7limoowym6ln/j0XL9a8DDw=
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8=
github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI=

View File

@ -40,7 +40,7 @@ func defaultAPIOptions(
proton.WithAppVersion(constants.AppVersion(version.Original())),
proton.WithCookieJar(cookieJar),
proton.WithTransport(transport),
proton.WithLogger(logrus.StandardLogger()),
proton.WithLogger(logrus.WithField("pkg", "gpa/client")),
proton.WithPanicHandler(panicHandler),
}
}

View File

@ -132,6 +132,8 @@ type Bridge struct {
syncService *syncservice.Service
}
var logPkg = logrus.WithField("pkg", "bridge") //nolint:gochecknoglobals
// New creates a new bridge.
func New(
locator Locator, // the locator to provide paths to store data
@ -322,7 +324,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
// Handle connection up/down events.
bridge.api.AddStatusObserver(func(status proton.Status) {
logrus.Info("API status changed: ", status)
logPkg.Info("API status changed: ", status)
switch {
case status == proton.StatusUp:
@ -337,7 +339,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
// If any call returns a bad version code, we need to update.
bridge.api.AddErrorHandler(proton.AppVersionBadCode, func() {
logrus.Warn("App version is bad")
logPkg.Warn("App version is bad")
bridge.publish(events.UpdateForced{})
})
@ -350,7 +352,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
// Log all manager API requests (client requests are logged separately).
bridge.api.AddPostRequestHook(func(_ *resty.Client, r *resty.Response) error {
if _, ok := proton.ClientIDFromContext(r.Request.Context()); !ok {
logrus.Infof("[MANAGER] %v: %v %v", r.Status(), r.Request.Method, r.Request.URL)
logrus.WithField("pkg", "gpa/manager").Infof("%v: %v %v", r.Status(), r.Request.Method, r.Request.URL)
}
return nil
@ -359,7 +361,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
// Publish a TLS issue event if a TLS issue is encountered.
bridge.tasks.Once(func(ctx context.Context) {
async.RangeContext(ctx, tlsReporter.GetTLSIssueCh(), func(struct{}) {
logrus.Warn("TLS issue encountered")
logPkg.Warn("TLS issue encountered")
bridge.publish(events.TLSIssue{})
})
})
@ -367,7 +369,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
// Publish a raise event if the focus service is called.
bridge.tasks.Once(func(ctx context.Context) {
async.RangeContext(ctx, bridge.focusService.GetRaiseCh(), func(struct{}) {
logrus.Info("Focus service requested raise")
logPkg.Info("Focus service requested raise")
bridge.publish(events.Raise{})
})
})
@ -375,7 +377,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
// Handle any IMAP events that are forwarded to the bridge from gluon.
bridge.tasks.Once(func(ctx context.Context) {
async.RangeContext(ctx, bridge.imapEventCh, func(event imapEvents.Event) {
logrus.WithField("event", fmt.Sprintf("%T", event)).Debug("Received IMAP event")
logPkg.WithField("event", fmt.Sprintf("%T", event)).Debug("Received IMAP event")
bridge.handleIMAPEvent(event)
})
})
@ -383,7 +385,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
// Attempt to load users from the vault when triggered.
bridge.goLoad = bridge.tasks.Trigger(func(ctx context.Context) {
if err := bridge.loadUsers(ctx); err != nil {
logrus.WithError(err).Error("Failed to load users")
logPkg.WithError(err).Error("Failed to load users")
if netErr := new(proton.NetError); !errors.As(err, &netErr) {
sentry.ReportError(bridge.reporter, "Failed to load users", err)
}
@ -396,7 +398,7 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
// Check for updates when triggered.
bridge.goUpdate = bridge.tasks.PeriodicOrTrigger(constants.UpdateCheckInterval, 0, func(ctx context.Context) {
logrus.Info("Checking for updates")
logPkg.Info("Checking for updates")
version, err := bridge.updater.GetVersionInfo(ctx, bridge.api, bridge.vault.GetUpdateChannel())
if err != nil {
@ -434,7 +436,7 @@ func (bridge *Bridge) GetErrors() []error {
}
func (bridge *Bridge) Close(ctx context.Context) {
logrus.Info("Closing bridge")
logPkg.Info("Closing bridge")
// Stop heart beat before closing users.
bridge.heartbeat.stop()
@ -448,7 +450,7 @@ func (bridge *Bridge) Close(ctx context.Context) {
// Close the servers
if err := bridge.serverManager.CloseServers(ctx); err != nil {
logrus.WithError(err).Error("Failed to close servers")
logPkg.WithError(err).Error("Failed to close servers")
}
bridge.syncService.Close()
@ -474,12 +476,12 @@ func (bridge *Bridge) publish(event events.Event) {
bridge.watchersLock.RLock()
defer bridge.watchersLock.RUnlock()
logrus.WithField("event", event).Debug("Publishing event")
logPkg.WithField("event", event).Debug("Publishing event")
for _, watcher := range bridge.watchers {
if watcher.IsWatching(event) {
if ok := watcher.Send(event); !ok {
logrus.WithField("event", event).Warn("Failed to send event to watcher")
logPkg.WithField("event", event).Warn("Failed to send event to watcher")
}
}
}
@ -512,13 +514,13 @@ func (bridge *Bridge) remWatcher(watcher *watcher.Watcher[events.Event]) {
}
func (bridge *Bridge) onStatusUp(_ context.Context) {
logrus.Info("Handling API status up")
logPkg.Info("Handling API status up")
bridge.goLoad()
}
func (bridge *Bridge) onStatusDown(ctx context.Context) {
logrus.Info("Handling API status down")
logPkg.Info("Handling API status down")
for backoff := time.Second; ; backoff = min(backoff*2, 30*time.Second) {
select {
@ -526,10 +528,10 @@ func (bridge *Bridge) onStatusDown(ctx context.Context) {
return
case <-time.After(backoff):
logrus.Info("Pinging API")
logPkg.Info("Pinging API")
if err := bridge.api.Ping(ctx); err != nil {
logrus.WithError(err).Warn("Ping failed, API is still unreachable")
logPkg.WithError(err).Warn("Ping failed, API is still unreachable")
} else {
return
}

View File

@ -34,7 +34,7 @@ import (
// ConfigureAppleMail configures Apple Mail for the given userID and address.
// If configuring Apple Mail for Catalina or newer, it ensures Bridge is using SSL.
func (bridge *Bridge) ConfigureAppleMail(ctx context.Context, userID, address string) error {
logrus.WithFields(logrus.Fields{
logPkg.WithFields(logrus.Fields{
"userID": userID,
"address": logging.Sensitive(address),
}).Info("Configuring Apple Mail")

View File

@ -65,7 +65,11 @@ func (bridge *Bridge) CheckClientState(ctx context.Context, checkFlags bool, pro
if progressCB != nil {
progressCB(fmt.Sprintf("Checking state for user %v", usr.Name()))
}
log := logrus.WithField("user", usr.Name()).WithField("diag", "state-check")
log := logrus.WithFields(logrus.Fields{
"pkg": "bridge/debug",
"user": usr.Name(),
"diag": "state-check",
})
log.Debug("Retrieving all server metadata")
meta, err := usr.GetDiagnosticMetadata(ctx)
if err != nil {
@ -280,7 +284,7 @@ func clientGetMessageIDs(client *goimapclient.Client, mailbox string) (map[strin
internalID, ok := header.GetChecked("X-Pm-Internal-Id")
if !ok {
logrus.Errorf("Message %v does not have internal id", internalID)
logrus.WithField("pkg", "bridge/debug").Errorf("Message %v does not have internal id", internalID)
continue
}

View File

@ -97,7 +97,7 @@ func (h *heartBeatState) start() {
h.taskStarted = true
h.task.PeriodicOrTrigger(h.taskInterval, 0, func(ctx context.Context) {
logrus.Debug("Checking for heartbeat")
logrus.WithField("pkg", "bridge/heartbeat").Debug("Checking for heartbeat")
h.TrySending(ctx)
})
@ -135,7 +135,7 @@ func (bridge *Bridge) SendHeartbeat(ctx context.Context, heartbeat *telemetry.He
if err := bridge.reporter.ReportMessageWithContext("Cannot parse heartbeat data.", reporter.Context{
"error": err,
}); err != nil {
logrus.WithError(err).Error("Failed to parse heartbeat data.")
logrus.WithField("pkg", "bridge/heartbeat").WithError(err).Error("Failed to parse heartbeat data.")
}
return false
}

View File

@ -35,10 +35,12 @@ func (bridge *Bridge) restartIMAP(ctx context.Context) error {
}
func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
log := logrus.WithField("pkg", "bridge/event/imap")
switch event := event.(type) {
case imapEvents.UserAdded:
for labelID, count := range event.Counts {
logrus.WithFields(logrus.Fields{
log.WithFields(logrus.Fields{
"gluonID": event.UserID,
"labelID": labelID,
"count": count,
@ -46,7 +48,7 @@ func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
}
case imapEvents.IMAPID:
logrus.WithFields(logrus.Fields{
log.WithFields(logrus.Fields{
"sessionID": event.SessionID,
"name": event.IMAPID.Name,
"version": event.IMAPID.Version,
@ -57,7 +59,7 @@ func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
}
case imapEvents.LoginFailed:
logrus.WithFields(logrus.Fields{
log.WithFields(logrus.Fields{
"sessionID": event.SessionID,
"username": event.Username,
"pkg": "imap",

View File

@ -27,7 +27,6 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/services/userevents"
"github.com/ProtonMail/proton-bridge/v3/internal/updater"
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
"github.com/sirupsen/logrus"
)
func (bridge *Bridge) GetKeychainApp() (string, error) {
@ -134,7 +133,7 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error
bridge.usersLock.RLock()
defer func() {
logrus.Info("Restarting user event loops")
logPkg.Info("Restarting user event loops")
for _, u := range bridge.users {
u.ResumeEventLoop()
}
@ -149,20 +148,20 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error
waiters := make([]waiter, 0, len(bridge.users))
logrus.Info("Pausing user event loops for gluon dir change")
logPkg.Info("Pausing user event loops for gluon dir change")
for id, u := range bridge.users {
waiters = append(waiters, waiter{w: u.PauseEventLoopWithWaiter(), id: id})
}
logrus.Info("Waiting on user event loop completion")
logPkg.Info("Waiting on user event loop completion")
for _, waiter := range waiters {
if err := waiter.w.WaitPollFinished(ctx); err != nil {
logrus.WithError(err).Errorf("Failed to wait on event loop pause for user %v", waiter.id)
logPkg.WithError(err).Errorf("Failed to wait on event loop pause for user %v", waiter.id)
return fmt.Errorf("failed on event loop pause: %w", err)
}
}
logrus.Info("Changing gluon directory")
logPkg.Info("Changing gluon directory")
return bridge.serverManager.SetGluonDir(ctx, newGluonDir)
}
@ -330,13 +329,13 @@ func (bridge *Bridge) FactoryReset(ctx context.Context) {
// Wipe the vault.
gluonCacheDir, err := bridge.locator.ProvideGluonCachePath()
if err != nil {
logrus.WithError(err).Error("Failed to provide gluon dir")
logPkg.WithError(err).Error("Failed to provide gluon dir")
} else if err := bridge.vault.Reset(gluonCacheDir); err != nil {
logrus.WithError(err).Error("Failed to reset vault")
logPkg.WithError(err).Error("Failed to reset vault")
}
// Lastly, delete all files except the vault.
if err := bridge.locator.Clear(bridge.vault.Path()); err != nil {
logrus.WithError(err).Error("Failed to clear data paths")
logPkg.WithError(err).Error("Failed to clear data paths")
}
}

View File

@ -38,6 +38,8 @@ import (
"github.com/sirupsen/logrus"
)
var logUser = logrus.WithField("pkg", "bridge/user") //nolint:gochecknoglobals
type UserState int
const (
@ -122,7 +124,7 @@ func (bridge *Bridge) QueryUserInfo(query string) (UserInfo, error) {
// LoginAuth begins the login process. It returns an authorized client that might need 2FA.
func (bridge *Bridge) LoginAuth(ctx context.Context, username string, password []byte) (*proton.Client, proton.Auth, error) {
logrus.WithField("username", logging.Sensitive(username)).Info("Authorizing user for login")
logUser.WithField("username", logging.Sensitive(username)).Info("Authorizing user for login")
if username == "crash@bandicoot" {
panic("Your wish is my command.. I crash!")
@ -134,10 +136,10 @@ func (bridge *Bridge) LoginAuth(ctx context.Context, username string, password [
}
if ok := safe.RLockRet(func() bool { return mapHas(bridge.users, auth.UserID) }, bridge.usersLock); ok {
logrus.WithField("userID", auth.UserID).Warn("User already logged in")
logUser.WithField("userID", auth.UserID).Warn("User already logged in")
if err := client.AuthDelete(ctx); err != nil {
logrus.WithError(err).Warn("Failed to delete auth")
logUser.WithError(err).Warn("Failed to delete auth")
}
return nil, proton.Auth{}, ErrUserAlreadyLoggedIn
@ -153,7 +155,7 @@ func (bridge *Bridge) LoginUser(
auth proton.Auth,
keyPass []byte,
) (string, error) {
logrus.WithField("userID", auth.UserID).Info("Logging in authorized user")
logUser.WithField("userID", auth.UserID).Info("Logging in authorized user")
userID, err := try.CatchVal(
func() (string, error) {
@ -165,7 +167,7 @@ func (bridge *Bridge) LoginUser(
// Failure to unlock will allow retries, so we do not delete auth.
if !errors.Is(err, ErrFailedToUnlock) {
if deleteErr := client.AuthDelete(ctx); deleteErr != nil {
logrus.WithError(deleteErr).Error("Failed to delete auth")
logUser.WithError(deleteErr).Error("Failed to delete auth")
}
}
return "", fmt.Errorf("failed to login user: %w", err)
@ -188,7 +190,7 @@ func (bridge *Bridge) LoginFull(
getTOTP func() (string, error),
getKeyPass func() ([]byte, error),
) (string, error) {
logrus.WithField("username", logging.Sensitive(username)).Info("Performing full user login")
logUser.WithField("username", logging.Sensitive(username)).Info("Performing full user login")
client, auth, err := bridge.LoginAuth(ctx, username, password)
if err != nil {
@ -196,7 +198,7 @@ func (bridge *Bridge) LoginFull(
}
if auth.TwoFA.Enabled&proton.HasTOTP != 0 {
logrus.WithField("userID", auth.UserID).Info("Requesting TOTP")
logUser.WithField("userID", auth.UserID).Info("Requesting TOTP")
totp, err := getTOTP()
if err != nil {
@ -211,7 +213,7 @@ func (bridge *Bridge) LoginFull(
var keyPass []byte
if auth.PasswordMode == proton.TwoPasswordMode {
logrus.WithField("userID", auth.UserID).Info("Requesting mailbox password")
logUser.WithField("userID", auth.UserID).Info("Requesting mailbox password")
userKeyPass, err := getKeyPass()
if err != nil {
@ -226,7 +228,7 @@ func (bridge *Bridge) LoginFull(
userID, err := bridge.LoginUser(ctx, client, auth, keyPass)
if err != nil {
if deleteErr := client.AuthDelete(ctx); deleteErr != nil {
logrus.WithError(err).Error("Failed to delete auth")
logUser.WithError(err).Error("Failed to delete auth")
}
return "", err
@ -237,7 +239,7 @@ func (bridge *Bridge) LoginFull(
// LogoutUser logs out the given user.
func (bridge *Bridge) LogoutUser(ctx context.Context, userID string) error {
logrus.WithField("userID", userID).Info("Logging out user")
logUser.WithField("userID", userID).Info("Logging out user")
return safe.LockRet(func() error {
user, ok := bridge.users[userID]
@ -257,7 +259,7 @@ func (bridge *Bridge) LogoutUser(ctx context.Context, userID string) error {
// DeleteUser deletes the given user.
func (bridge *Bridge) DeleteUser(ctx context.Context, userID string) error {
logrus.WithField("userID", userID).Info("Deleting user")
logUser.WithField("userID", userID).Info("Deleting user")
syncConfigDir, err := bridge.locator.ProvideIMAPSyncConfigPath()
if err != nil {
@ -278,7 +280,7 @@ func (bridge *Bridge) DeleteUser(ctx context.Context, userID string) error {
}
if err := bridge.vault.DeleteUser(userID); err != nil {
logrus.WithError(err).Error("Failed to delete vault user")
logUser.WithError(err).Error("Failed to delete vault user")
}
bridge.publish(events.UserDeleted{
@ -291,7 +293,7 @@ func (bridge *Bridge) DeleteUser(ctx context.Context, userID string) error {
// SetAddressMode sets the address mode for the given user.
func (bridge *Bridge) SetAddressMode(ctx context.Context, userID string, mode vault.AddressMode) error {
logrus.WithField("userID", userID).WithField("mode", mode).Info("Setting address mode")
logUser.WithField("userID", userID).WithField("mode", mode).Info("Setting address mode")
return safe.RLockRet(func() error {
user, ok := bridge.users[userID]
@ -327,7 +329,7 @@ func (bridge *Bridge) SetAddressMode(ctx context.Context, userID string, mode va
// SendBadEventUserFeedback passes the feedback to the given user.
func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string, doResync bool) error {
logrus.WithField("userID", userID).WithField("doResync", doResync).Info("Passing bad event feedback to user")
logUser.WithField("userID", userID).WithField("doResync", doResync).Info("Passing bad event feedback to user")
return safe.RLockRet(func() error {
ctx := context.Background()
@ -338,7 +340,7 @@ func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string,
"Failed to handle event: feedback failed: no such user",
reporter.Context{"user_id": userID},
); rerr != nil {
logrus.WithError(rerr).Error("Failed to report feedback failure")
logUser.WithError(rerr).Error("Failed to report feedback failure")
}
return ErrNoSuchUser
@ -349,7 +351,7 @@ func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string,
"Failed to handle event: feedback resync",
reporter.Context{"user_id": userID},
); rerr != nil {
logrus.WithError(rerr).Error("Failed to report feedback failure")
logUser.WithError(rerr).Error("Failed to report feedback failure")
}
return user.BadEventFeedbackResync(ctx)
@ -359,7 +361,7 @@ func (bridge *Bridge) SendBadEventUserFeedback(_ context.Context, userID string,
"Failed to handle event: feedback logout",
reporter.Context{"user_id": userID},
); rerr != nil {
logrus.WithError(rerr).Error("Failed to report feedback failure")
logUser.WithError(rerr).Error("Failed to report feedback failure")
}
bridge.logoutUser(ctx, user, true, false, false)
@ -403,11 +405,11 @@ func (bridge *Bridge) loginUser(ctx context.Context, client *proton.Client, auth
// loadUsers tries to load each user in the vault that isn't already loaded.
func (bridge *Bridge) loadUsers(ctx context.Context) error {
logrus.WithField("count", len(bridge.vault.GetUserIDs())).Info("Loading users")
defer logrus.Info("Finished loading users")
logUser.WithField("count", len(bridge.vault.GetUserIDs())).Info("Loading users")
defer logUser.Info("Finished loading users")
return bridge.vault.ForUser(runtime.NumCPU(), func(user *vault.User) error {
log := logrus.WithField("userID", user.UserID())
log := logUser.WithField("userID", user.UserID())
if user.AuthUID() == "" {
log.Info("User is not connected (skipping)")
@ -451,7 +453,7 @@ func (bridge *Bridge) loadUser(ctx context.Context, user *vault.User) error {
if apiErr := new(proton.APIError); errors.As(err, &apiErr) && (apiErr.Code == proton.AuthRefreshTokenInvalid) {
// The session cannot be refreshed, we sign out the user by clearing his auth secrets.
if err := user.Clear(); err != nil {
logrus.WithError(err).Warn("Failed to clear user secrets")
logUser.WithError(err).Warn("Failed to clear user secrets")
}
}
@ -496,24 +498,24 @@ func (bridge *Bridge) addUser(
if err := bridge.addUserWithVault(ctx, client, apiUser, vaultUser, isNew); err != nil {
if _, ok := err.(*resty.ResponseError); ok || isLogin {
logrus.WithError(err).Error("Failed to add user, clearing its secrets from vault")
logUser.WithError(err).Error("Failed to add user, clearing its secrets from vault")
if err := vaultUser.Clear(); err != nil {
logrus.WithError(err).Error("Failed to clear user secrets")
logUser.WithError(err).Error("Failed to clear user secrets")
}
} else {
logrus.WithError(err).Error("Failed to add user")
logUser.WithError(err).Error("Failed to add user")
}
if err := vaultUser.Close(); err != nil {
logrus.WithError(err).Error("Failed to close vault user")
logUser.WithError(err).Error("Failed to close vault user")
}
if isNew {
logrus.Warn("Deleting newly added vault user")
logUser.Warn("Deleting newly added vault user")
if err := bridge.vault.DeleteUser(apiUser.ID); err != nil {
logrus.WithError(err).Error("Failed to delete vault user")
logUser.WithError(err).Error("Failed to delete vault user")
}
}
@ -567,7 +569,7 @@ func (bridge *Bridge) addUserWithVault(
// For example, if the user's addresses change, we need to update them in gluon.
bridge.tasks.Once(func(ctx context.Context) {
async.RangeContext(ctx, user.GetEventCh(), func(event events.Event) {
logrus.WithFields(logrus.Fields{
logUser.WithFields(logrus.Fields{
"userID": apiUser.ID,
"event": event,
}).Debug("Received user event")
@ -618,14 +620,14 @@ func (bridge *Bridge) logoutUser(ctx context.Context, user *user.User, withAPI,
user.SendConfigStatusAbort(ctx, withTelemetry)
}
logrus.WithFields(logrus.Fields{
logUser.WithFields(logrus.Fields{
"userID": user.ID(),
"withAPI": withAPI,
"withData": withData,
}).Debug("Logging out user")
if err := user.Logout(ctx, withAPI); err != nil {
logrus.WithError(err).Error("Failed to logout user")
logUser.WithError(err).Error("Failed to logout user")
}
bridge.heartbeat.SetNbAccount(len(bridge.users))

View File

@ -58,7 +58,7 @@ func (bridge *Bridge) handleUserBadEvent(ctx context.Context, user *user.User, e
"error": event.Error,
"error_type": internal.ErrCauseType(event.Error),
}); rerr != nil {
logrus.WithError(rerr).Error("Failed to report failed event handling")
logrus.WithField("pkg", "bridge/event").WithError(rerr).Error("Failed to report failed event handling")
}
user.OnBadEvent(ctx)
@ -70,6 +70,6 @@ func (bridge *Bridge) handleUncategorizedErrorEvent(event events.UncategorizedEv
"error_type": internal.ErrCauseType(event.Error),
"error": event.Error,
}); rerr != nil {
logrus.WithError(rerr).Error("Failed to report failed event handling")
logrus.WithField("pkg", "bridge/event").WithError(rerr).Error("Failed to report failed event handling")
}
}

View File

@ -95,6 +95,6 @@ func TestConfigurationProgress_fed_year_change(t *testing.T) {
require.Equal(t, "bridge.any.configuration", req.MeasurementGroup)
require.Equal(t, "bridge_config_progress", req.Event)
require.Equal(t, 370, req.Values.NbDay)
require.True(t, (req.Values.NbDay == 370) || (req.Values.NbDay == 371)) // leap year is accounted for in the simplest manner.
require.Equal(t, 2, req.Values.NbDaySinceLast)
}

View File

@ -29,27 +29,34 @@ type TLSDialer interface {
DialTLSContext(ctx context.Context, network, address string) (conn net.Conn, err error)
}
func SetBasicTransportTimeouts(t *http.Transport) {
t.MaxIdleConns = 100
t.MaxIdleConnsPerHost = 100
t.IdleConnTimeout = 5 * time.Minute
t.ExpectContinueTimeout = 500 * time.Millisecond
// GODT-126: this was initially 10s but logs from users showed a significant number
// were hitting this timeout, possibly due to flaky wifi taking >10s to reconnect.
// Bumping to 30s for now to avoid this problem.
t.ResponseHeaderTimeout = 30 * time.Second
// If we allow up to 30 seconds for response headers, it is reasonable to allow up
// to 30 seconds for the TLS handshake to take place.
t.TLSHandshakeTimeout = 30 * time.Second
}
// CreateTransportWithDialer creates an http.Transport that uses the given dialer to make TLS connections.
func CreateTransportWithDialer(dialer TLSDialer) *http.Transport {
return &http.Transport{
t := &http.Transport{
DialTLSContext: dialer.DialTLSContext,
Proxy: http.ProxyFromEnvironment,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 5 * time.Minute,
ExpectContinueTimeout: 500 * time.Millisecond,
// GODT-126: this was initially 10s but logs from users showed a significant number
// were hitting this timeout, possibly due to flaky wifi taking >10s to reconnect.
// Bumping to 30s for now to avoid this problem.
ResponseHeaderTimeout: 30 * time.Second,
// If we allow up to 30 seconds for response headers, it is reasonable to allow up
// to 30 seconds for the TLS handshake to take place.
TLSHandshakeTimeout: 30 * time.Second,
Proxy: http.ProxyFromEnvironment,
}
SetBasicTransportTimeouts(t)
return t
}
// BasicTLSDialer implements TLSDialer.

View File

@ -144,7 +144,8 @@ func TestProxyProvider_FindProxy_CanReachTimeout(t *testing.T) {
r.Error(t, err)
}
func TestProxyProvider_DoHLookup_Quad9(t *testing.T) {
// DISABLED_TestProxyProvider_DoHLookup_Quad9 cannot run on CI, see GODT-3257.
func DISABLED_TestProxyProvider_DoHLookup_Quad9(t *testing.T) {
p := newProxyProvider(NewBasicTLSDialer(""), "", []string{Quad9Provider, GoogleProvider}, async.NoopPanicHandler{})
records, err := p.dohLookup(context.Background(), proxyQuery, Quad9Provider)

View File

@ -27,5 +27,5 @@ func NewIMAPLogger() *IMAPLogger {
}
func (l *IMAPLogger) Write(p []byte) (n int, err error) {
return logrus.WithField("pkg", "IMAP").WriterLevel(logrus.TraceLevel).Write(p)
return logrus.WithField("pkg", "log/IMAP").WriterLevel(logrus.TraceLevel).Write(p)
}

View File

@ -27,7 +27,7 @@ type SMTPErrorLogger struct {
}
func NewSMTPLogger() *SMTPErrorLogger {
return &SMTPErrorLogger{l: logrus.WithField("pkg", "SMTP")}
return &SMTPErrorLogger{l: logrus.WithField("pkg", "log/SMTP")}
}
func (s *SMTPErrorLogger) Printf(format string, args ...interface{}) {
@ -44,7 +44,7 @@ type SMTPDebugLogger struct {
}
func NewSMTPDebugLogger() *SMTPDebugLogger {
return &SMTPDebugLogger{l: logrus.WithField("pkg", "SMTP")}
return &SMTPDebugLogger{l: logrus.WithField("pkg", "log/SMTP")}
}
func (l *SMTPDebugLogger) Write(p []byte) (n int, err error) {

View File

@ -677,13 +677,18 @@ func (s *Connector) importMessage(
}
if err := s.identityState.WithAddrKR(s.addrID, func(_, addrKR *crypto.KeyRing) error {
primaryKey, errKey := addrKR.FirstKey()
if errKey != nil {
return fmt.Errorf("failed to get primary key for import: %w", errKey)
}
var messageID string
p, err2 := parser.New(bytes.NewReader(literal))
if err2 != nil {
return fmt.Errorf("failed to parse literal: %w", err2)
}
if slices.Contains(labelIDs, proton.DraftsLabel) {
msg, err := s.createDraftWithParser(ctx, p, addrKR, addr)
msg, err := s.createDraftWithParser(ctx, p, primaryKey, addr)
if err != nil {
return fmt.Errorf("failed to create draft: %w", err)
}
@ -699,7 +704,7 @@ func (s *Connector) importMessage(
}
literal = buf.Bytes()
}
str, err := s.client.ImportMessages(ctx, addrKR, 1, 1, []proton.ImportReq{{
str, err := s.client.ImportMessages(ctx, primaryKey, 1, 1, []proton.ImportReq{{
Metadata: proton.ImportMetadata{
AddressID: s.addrID,
LabelIDs: labelIDs,
@ -726,7 +731,7 @@ func (s *Connector) importMessage(
return fmt.Errorf("failed to fetch message: %w", err)
}
if literal, err = message.DecryptAndBuildRFC822(addrKR, full.Message, full.AttData, defaultMessageJobOpts()); err != nil {
if literal, err = message.DecryptAndBuildRFC822(primaryKey, full.Message, full.AttData, defaultMessageJobOpts()); err != nil {
return fmt.Errorf("failed to build message: %w", err)
}

View File

@ -39,6 +39,8 @@ import (
"github.com/sirupsen/logrus"
)
var logIMAP = logrus.WithField("pkg", "server/imap") //nolint:gochecknoglobals
type IMAPSettingsProvider interface {
TLSConfig() *tls.Config
LogClient() bool
@ -79,7 +81,7 @@ func newIMAPServer(
gluonCacheDir = ApplyGluonCachePathSuffix(gluonCacheDir)
gluonConfigDir = ApplyGluonConfigPathSuffix(gluonConfigDir)
logrus.WithFields(logrus.Fields{
logIMAP.WithFields(logrus.Fields{
"gluonStore": gluonCacheDir,
"gluonDB": gluonConfigDir,
"version": version,
@ -88,10 +90,9 @@ func newIMAPServer(
}).Info("Creating IMAP server")
if logClient || logServer {
log := logrus.WithField("protocol", "IMAP")
log.Warning("================================================")
log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
log.Warning("================================================")
logIMAP.Warning("================================================")
logIMAP.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
logIMAP.Warning("================================================")
}
var imapClientLog io.Writer
@ -143,7 +144,7 @@ func newIMAPServer(
tasks.Once(func(ctx context.Context) {
async.RangeContext(ctx, imapServer.GetErrorCh(), func(err error) {
logrus.WithError(err).Error("IMAP server error")
logIMAP.WithError(err).Error("IMAP server error")
})
})
@ -176,7 +177,7 @@ func (*storeBuilder) Delete(path, userID string) error {
}
func moveGluonCacheDir(settings IMAPSettingsProvider, oldGluonDir, newGluonDir string) error {
logrus.Infof("gluon cache moving from %s to %s", oldGluonDir, newGluonDir)
logIMAP.WithField("pkg", "service/imap").Infof("gluon cache moving from %s to %s", oldGluonDir, newGluonDir)
oldCacheDir := ApplyGluonCachePathSuffix(oldGluonDir)
if err := files.CopyDir(oldCacheDir, ApplyGluonCachePathSuffix(newGluonDir)); err != nil {
return fmt.Errorf("failed to copy gluon dir: %w", err)
@ -187,7 +188,7 @@ func moveGluonCacheDir(settings IMAPSettingsProvider, oldGluonDir, newGluonDir s
}
if err := os.RemoveAll(oldCacheDir); err != nil {
logrus.WithError(err).Error("failed to remove old gluon cache dir")
logIMAP.WithError(err).Error("failed to remove old gluon cache dir")
}
return nil

View File

@ -190,16 +190,16 @@ func (sm *Service) run(ctx context.Context, subscription events.Subscription) {
case evt := <-eventSub.GetChannel():
switch evt.(type) {
case events.ConnStatusDown:
logrus.Info("Server Manager, network down stopping listeners")
sm.log.Info("Server Manager, network down stopping listeners")
if err := sm.closeSMTPServer(ctx); err != nil {
logrus.WithError(err).Error("Failed to close SMTP server")
sm.log.WithError(err).Error("Failed to close SMTP server")
}
if err := sm.stopIMAPListener(ctx); err != nil {
logrus.WithError(err)
sm.log.WithError(err)
}
case events.ConnStatusUp:
logrus.Info("Server Manager, network up starting listeners")
sm.log.Info("Server Manager, network up starting listeners")
sm.handleLoadedUserCountChange(ctx)
}
@ -241,12 +241,12 @@ func (sm *Service) run(ctx context.Context, subscription events.Subscription) {
request.Reply(ctx, nil, err)
case *smRequestAddSMTPAccount:
logrus.WithField("user", r.account.UserID()).Debug("Adding SMTP Account")
sm.log.WithField("user", r.account.UserID()).Debug("Adding SMTP Account")
sm.smtpAccounts.AddAccount(r.account)
request.Reply(ctx, nil, nil)
case *smRequestRemoveSMTPAccount:
logrus.WithField("user", r.account.UserID()).Debug("Removing SMTP Account")
sm.log.WithField("user", r.account.UserID()).Debug("Removing SMTP Account")
sm.smtpAccounts.RemoveAccount(r.account)
request.Reply(ctx, nil, nil)
}
@ -255,29 +255,29 @@ func (sm *Service) run(ctx context.Context, subscription events.Subscription) {
}
func (sm *Service) handleLoadedUserCountChange(ctx context.Context) {
logrus.Infof("Validating Listener State %v", sm.loadedUserCount)
sm.log.Infof("Validating Listener State %v", sm.loadedUserCount)
if sm.shouldStartServers() {
if sm.imapListener == nil {
if err := sm.serveIMAP(ctx); err != nil {
logrus.WithError(err).Error("Failed to start IMAP server")
sm.log.WithError(err).Error("Failed to start IMAP server")
}
}
if sm.smtpListener == nil {
if err := sm.restartSMTP(ctx); err != nil {
logrus.WithError(err).Error("Failed to start SMTP server")
sm.log.WithError(err).Error("Failed to start SMTP server")
}
}
} else {
if sm.imapListener != nil {
if err := sm.stopIMAPListener(ctx); err != nil {
logrus.WithError(err).Error("Failed to stop IMAP server")
sm.log.WithError(err).Error("Failed to stop IMAP server")
}
}
if sm.smtpListener != nil {
if err := sm.closeSMTPServer(ctx); err != nil {
logrus.WithError(err).Error("Failed to stop SMTP server")
sm.log.WithError(err).Error("Failed to stop SMTP server")
}
}
}
@ -286,12 +286,12 @@ func (sm *Service) handleLoadedUserCountChange(ctx context.Context) {
func (sm *Service) handleClose(ctx context.Context) {
// Close the IMAP server.
if err := sm.closeIMAPServer(ctx); err != nil {
logrus.WithError(err).Error("Failed to close IMAP server")
sm.log.WithError(err).Error("Failed to close IMAP server")
}
// Close the SMTP server.
if err := sm.closeSMTPServer(ctx); err != nil {
logrus.WithError(err).Error("Failed to close SMTP server")
sm.log.WithError(err).Error("Failed to close SMTP server")
}
// Cancel and wait needs to be called here since the SMTP server does not have a way to exit
@ -325,7 +325,7 @@ func (sm *Service) handleAddIMAPUserImpl(ctx context.Context,
return fmt.Errorf("no imap server instance running")
}
log := logrus.WithFields(logrus.Fields{
log := sm.log.WithFields(logrus.Fields{
"addrID": addrID,
})
log.Info("Adding user to imap server")
@ -341,7 +341,7 @@ func (sm *Service) handleAddIMAPUserImpl(ctx context.Context,
if isNew {
// If the DB was newly created, clear the sync status; gluon's DB was not found.
logrus.Warn("IMAP user DB was newly created, clearing sync status")
sm.log.Warn("IMAP user DB was newly created, clearing sync status")
// Remove the user from IMAP so we can clear the sync status.
if err := sm.imapServer.RemoveUser(ctx, gluonID, false); err != nil {
@ -415,7 +415,7 @@ func (sm *Service) handleRemoveIMAPUser(ctx context.Context, withData bool, idPr
return fmt.Errorf("no imap server instance running")
}
logrus.WithFields(logrus.Fields{
sm.log.WithFields(logrus.Fields{
"withData": withData,
"addresses": addrIDs,
}).Debug("Removing IMAP user")
@ -423,7 +423,7 @@ func (sm *Service) handleRemoveIMAPUser(ctx context.Context, withData bool, idPr
for _, addrID := range addrIDs {
gluonID, ok := idProvider.GetGluonID(addrID)
if !ok {
logrus.Warnf("Could not find Gluon ID for addrID %v", addrID)
sm.log.Warnf("Could not find Gluon ID for addrID %v", addrID)
continue
}
@ -480,7 +480,7 @@ func (sm *Service) closeSMTPServer(ctx context.Context) error {
// even after the server has been closed. So we close the listener ourselves to unblock it.
if sm.smtpListener != nil {
logrus.Info("Closing SMTP Listener")
sm.log.Info("Closing SMTP Listener")
if err := sm.smtpListener.Close(); err != nil {
return fmt.Errorf("failed to close SMTP listener: %w", err)
}
@ -489,9 +489,9 @@ func (sm *Service) closeSMTPServer(ctx context.Context) error {
}
if sm.smtpServer != nil {
logrus.Info("Closing SMTP server")
sm.log.Info("Closing SMTP server")
if err := sm.smtpServer.Close(); err != nil {
logrus.WithError(err).Debug("Failed to close SMTP server (expected -- we close the listener ourselves)")
sm.log.WithError(err).Debug("Failed to close SMTP server (expected -- we close the listener ourselves)")
}
sm.smtpServer = nil
@ -504,7 +504,7 @@ func (sm *Service) closeSMTPServer(ctx context.Context) error {
func (sm *Service) closeIMAPServer(ctx context.Context) error {
if sm.imapListener != nil {
logrus.Info("Closing IMAP Listener")
sm.log.Info("Closing IMAP Listener")
if err := sm.imapListener.Close(); err != nil {
return fmt.Errorf("failed to close IMAP listener: %w", err)
@ -516,7 +516,7 @@ func (sm *Service) closeIMAPServer(ctx context.Context) error {
}
if sm.imapServer != nil {
logrus.Info("Closing IMAP server")
sm.log.Info("Closing IMAP server")
if err := sm.imapServer.Close(ctx); err != nil {
return fmt.Errorf("failed to close IMAP server: %w", err)
}
@ -530,7 +530,7 @@ func (sm *Service) closeIMAPServer(ctx context.Context) error {
}
func (sm *Service) restartIMAP(ctx context.Context) error {
logrus.Info("Restarting IMAP server")
sm.log.Info("Restarting IMAP server")
if sm.imapListener != nil {
if err := sm.imapListener.Close(); err != nil {
@ -550,7 +550,7 @@ func (sm *Service) restartIMAP(ctx context.Context) error {
}
func (sm *Service) restartSMTP(ctx context.Context) error {
logrus.Info("Restarting SMTP server")
sm.log.Info("Restarting SMTP server")
if err := sm.closeSMTPServer(ctx); err != nil {
return fmt.Errorf("failed to close SMTP: %w", err)
@ -569,7 +569,7 @@ func (sm *Service) restartSMTP(ctx context.Context) error {
func (sm *Service) serveSMTP(ctx context.Context) error {
port, err := func() (int, error) {
logrus.WithFields(logrus.Fields{
sm.log.WithFields(logrus.Fields{
"port": sm.smtpSettings.Port(),
"ssl": sm.smtpSettings.UseSSL(),
}).Info("Starting SMTP server")
@ -583,7 +583,7 @@ func (sm *Service) serveSMTP(ctx context.Context) error {
sm.tasks.Once(func(context.Context) {
if err := sm.smtpServer.Serve(smtpListener); err != nil {
logrus.WithError(err).Info("SMTP server stopped")
sm.log.WithError(err).Info("SMTP server stopped")
}
})
@ -615,7 +615,7 @@ func (sm *Service) serveIMAP(ctx context.Context) error {
return 0, fmt.Errorf("no IMAP server instance running")
}
logrus.WithFields(logrus.Fields{
sm.log.WithFields(logrus.Fields{
"port": sm.imapSettings.Port(),
"ssl": sm.imapSettings.UseSSL(),
}).Info("Starting IMAP server")
@ -654,7 +654,7 @@ func (sm *Service) serveIMAP(ctx context.Context) error {
}
func (sm *Service) stopIMAPListener(ctx context.Context) error {
logrus.Info("Stopping IMAP listener")
sm.log.Info("Stopping IMAP listener")
if sm.imapListener != nil {
if err := sm.imapListener.Close(); err != nil {
return err
@ -682,7 +682,7 @@ func (sm *Service) handleSetGluonDir(ctx context.Context, newGluonDir string) er
sm.loadedUserCount = 0
if err := moveGluonCacheDir(sm.imapSettings, currentGluonDir, newGluonDir); err != nil {
logrus.WithError(err).Error("failed to move GluonCacheDir")
sm.log.WithError(err).Error("failed to move GluonCacheDir")
if err := sm.imapSettings.SetCacheDirectory(currentGluonDir); err != nil {
return fmt.Errorf("failed to revert GluonCacheDir: %w", err)

View File

@ -29,6 +29,8 @@ import (
"github.com/sirupsen/logrus"
)
var logSMTP = logrus.WithField("pkg", "server/smtp") //nolint:gochecknoglobals
type SMTPSettingsProvider interface {
TLSConfig() *tls.Config
Log() bool
@ -39,7 +41,7 @@ type SMTPSettingsProvider interface {
}
func newSMTPServer(accounts *smtpservice.Accounts, settings SMTPSettingsProvider) *smtp.Server {
logrus.WithField("logSMTP", settings.Log()).Info("Creating SMTP server")
logSMTP.WithField("logSMTP", settings.Log()).Info("Creating SMTP server")
smtpServer := smtp.NewServer(smtpservice.NewBackend(accounts, settings.Identifier()))
@ -57,10 +59,9 @@ func newSMTPServer(accounts *smtpservice.Accounts, settings SMTPSettingsProvider
})
if settings.Log() {
log := logrus.WithField("protocol", "SMTP")
log.Warning("================================================")
log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
log.Warning("================================================")
logSMTP.Warning("================================================")
logSMTP.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
logSMTP.Warning("================================================")
smtpServer.Debug = logging.NewSMTPDebugLogger()
}

View File

@ -32,137 +32,136 @@ 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
00IsXQemR6I8Qw==
=QiAL
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
=I7Pt
-----END PGP PUBLIC KEY BLOCK-----`

View File

@ -294,7 +294,7 @@ func newImpl(
// Log all requests made by the user.
user.client.AddPostRequestHook(func(_ *resty.Client, r *resty.Response) error {
user.log.Infof("%v: %v %v", r.Status(), r.Request.Method, r.Request.URL)
user.log.WithField("pkg", "gpa/client").Infof("%v: %v %v", r.Status(), r.Request.Method, r.Request.URL)
return nil
})

View File

@ -205,6 +205,10 @@ func convertForeignEncodings(p *parser.Parser) error {
return p.ConvertMetaCharset()
}).
RegisterContentTypeHandler("text/.*", func(p *parser.Part) error {
if p.IsAttachment() {
return nil
}
return p.ConvertToUTF8()
}).
Walk()
@ -548,6 +552,11 @@ func parseAttachment(h message.Header, body []byte) (Attachment, error) {
att.Header = mimeHeader
mimeType, mimeTypeParams, err := pmmime.ParseMediaType(h.Get("Content-Type"))
if err == pmmime.EmptyContentTypeErr {
mimeType = "text/plain"
err = nil
}
if err != nil {
return Attachment{}, err
}

View File

@ -837,6 +837,17 @@ func TestPatchNewLineWithHtmlBreaks(t *testing.T) {
}
}
func TestParseCp1250Attachment(t *testing.T) {
r := require.New(t)
f := getFileReader("text_plain_xml_attachment_cp1250.eml")
m, err := Parse(f)
r.NoError(err)
r.Len(m.Attachments, 1)
r.Equal("text/xml; charset=windows-1250; name=\"cp1250.xml\"", m.Attachments[0].Header.Get("Content-Type"))
}
func getFileReader(filename string) io.Reader {
f, err := os.Open(filepath.Join("testdata", filename))
if err != nil {

View File

@ -0,0 +1,22 @@
From: Sender <sender@pm.me>
To: Receiver <receiver@pm.me>
Content-Type: multipart/mixed; boundary="------------iOaeSk0jVHjMucH3kQFwS1LB"
This is a multi-part message in MIME format.
--------------iOaeSk0jVHjMucH3kQFwS1LB
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit
created by me 😎
--------------iOaeSk0jVHjMucH3kQFwS1LB
Content-Type: text/xml; charset=windows-1250; name="cp1250.xml"
Content-Disposition: attachment; filename="cp1250.xml"
Content-Transfer-Encoding: 8bit
<?xml version="1.0" encoding="WINDOWS-1250" ?>
<pvpoj xmlns="http://schemas.cssz.cz/POJ/PVPOJ2023">
<ulice>Horn<72> n<>m<EFBFBD>st<73></ulice>
<prijmeni><3E>CheckChars<72></prijmeni>
</pvpoj>
--------------iOaeSk0jVHjMucH3kQFwS1LB--

View File

@ -35,6 +35,8 @@ import (
"golang.org/x/text/encoding/htmlindex"
)
var EmptyContentTypeErr = errors.New("empty content type")
func init() {
rfc822.ParseMediaType = ParseMediaType
proton.CharsetReader = CharsetReader
@ -257,7 +259,7 @@ func DecodeCharset(original []byte, contentType string) ([]byte, error) {
// ParseMediaType from MIME doesn't support RFC2231 for non asci / utf8 encodings so we have to pre-parse it.
func ParseMediaType(v string) (string, map[string]string, error) {
if v == "" {
return "", nil, errors.New("empty media type")
return "", nil, EmptyContentTypeErr
}
decoded, err := DecodeHeader(v)
if err != nil {

67
tests/README.md Normal file
View File

@ -0,0 +1,67 @@
# Bridge Integration tests
Tests defined in this folder are using `github.com/cucumber/godog` library to
define scenarios.
The scenarios are defined in `./features/` folder.
The step definition can be found in `./steps_test.go`.
# How to run
All features are run as sub-test of `TestFeatures` in `./bdd_test.go`.
The most simple way to execute is `make test-integration` from project source directory.
There are several environment variables which can be used to control the tests:
* `FEATURES` sets the path to folder / file / line in file to select which
scenarios to run.
FEATURES=${PWD}/tests/features/user/addressmode.feature:162
* `FEATURE_TEST_LOG_LEVEL` the logrus level for tests (affects also testing
bridge instance)
FEATURE_TEST_LOG_LEVEL=trace
* `BRIDGE_API_DEBUG` when enabled
[GPA](https://github.com/ProtonMail/go-proton-api/)
client used in testing bridge instance will log http communication and logrus
is automatically set to `trace`
BRIDGE_API_DEBUG=1
* `GO_PROTON_API_SERVER_LOGGER_ENABLED` GPA mock server will print log line per
each request to stdout (not logrus)
GO_PROTON_API_SERVER_LOGGER_ENABLED=1
* `FEATURE_API_DEBUG` when enabled GPA client for preparation of test
condiditions (see `./ctx_helper_test.go`) will dump http communication to
stdoout.
FEATURE_API_DEBUG=1
* `FEATURE_TEST_LOG_IMAP` when enabled
bridge will dump all (client and server) IMAP communication to logs
and logrus is automatically set to `trace`
FEATURE_TEST_LOG_IMAP=1
* `GLUON_LOG_IMAP_LINE_LIMIT` controls maximal number of lines (by default 1)
which are printed into imap trace log (logrus).
Needs `FEATURE_TEST_LOG_IMAP` enabled to take effect.
GLUON_LOG_IMAP_LINE_LIMIT=1048576
* `FEATURE_TEST_LOG_SMTP` when enabled
bridge will dump all SMTP communication to logs
and logrus is automatically set to `trace`
FEATURE_TEST_LOG_SMTP=1

View File

@ -18,7 +18,6 @@
package tests
import (
"crypto/tls"
"net/http"
"net/url"
"os"
@ -26,6 +25,7 @@ import (
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/go-proton-api/server"
"github.com/ProtonMail/proton-bridge/v3/internal/dialer"
)
type API interface {
@ -73,13 +73,14 @@ func newLiveAPI(hostURL string) API {
panic(err)
}
tr := proton.InsecureTransport()
dialer.SetBasicTransportTimeouts(tr)
tr.Proxy = http.ProxyFromEnvironment
return &liveAPI{
Server: server.New(
server.WithProxyOrigin(hostURL),
server.WithProxyTransport(&http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
Proxy: http.ProxyFromEnvironment,
}),
server.WithProxyTransport(tr),
),
domain: url.Hostname(),
}

View File

@ -132,9 +132,19 @@ func getFeatureTags() string {
tags = ""
case "smoke": // Currently this is just a placeholder, as there are no scenarios tagged with @smoke
tags = "@smoke"
case "black": // Currently this is just a placeholder, as there are no scenarios tagged with @smoke
tags = "~@skip-black"
default:
tags = "~@regression && ~@smoke" // To exclude more add `&& ~@tag`
}
return tags
}
func isBlack() bool {
if len(os.Args) == 0 {
return false
}
return os.Args[len(os.Args)-1] == "black"
}

View File

@ -168,7 +168,7 @@ func newTestBugReport(br *bridge.Bridge) *testBugReport {
Title: "title",
Description: "description",
Username: "username",
Email: "email",
Email: "email@pm.me",
EmailClient: "client",
IncludeLogs: false,
}

View File

@ -23,6 +23,7 @@ import (
"crypto/x509"
"encoding/json"
"fmt"
"net/http"
"net/http/cookiejar"
"os"
"path/filepath"
@ -34,6 +35,7 @@ import (
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
"github.com/ProtonMail/proton-bridge/v3/internal/cookies"
"github.com/ProtonMail/proton-bridge/v3/internal/dialer"
"github.com/ProtonMail/proton-bridge/v3/internal/events"
frontend "github.com/ProtonMail/proton-bridge/v3/internal/frontend/grpc"
"github.com/ProtonMail/proton-bridge/v3/internal/service"
@ -146,6 +148,16 @@ func (t *testCtx) initBridge() (<-chan events.Event, error) {
logrus.SetLevel(logrus.TraceLevel)
}
rt := t.netCtl.NewRoundTripper(&tls.Config{InsecureSkipVerify: true})
if isBlack() {
// GODT-1602 make sure we don't time out test server
t, ok := rt.(*http.Transport)
if !ok {
panic("expecting http.Transport")
}
dialer.SetBasicTransportTimeouts(t)
}
// Create the bridge.
bridge, eventCh, err := bridge.New(
// App stuff
@ -161,7 +173,7 @@ func (t *testCtx) initBridge() (<-chan events.Event, error) {
persister,
useragent.New(),
t.mocks.TLSReporter,
t.netCtl.NewRoundTripper(&tls.Config{InsecureSkipVerify: true}),
rt,
t.mocks.ProxyCtl,
t.mocks.CrashHandler,
t.reporter,

View File

@ -26,14 +26,20 @@ import (
"github.com/ProtonMail/gluon/async"
"github.com/ProtonMail/go-proton-api"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/v3/internal/dialer"
"github.com/bradenaw/juniper/stream"
)
// withProton executes the given function with a proton manager configured to use the test API.
func (t *testCtx) withProton(fn func(*proton.Manager) error) error {
tr := proton.InsecureTransport()
if isBlack() {
dialer.SetBasicTransportTimeouts(tr)
}
m := proton.New(
proton.WithHostURL(t.api.GetHostURL()),
proton.WithTransport(proton.InsecureTransport()),
proton.WithTransport(tr),
proton.WithAppVersion(t.api.GetAppVersion()),
proton.WithDebug(os.Getenv("FEATURE_API_DEBUG") != ""),
)
@ -88,6 +94,15 @@ func (t *testCtx) runQuarkCmd(ctx context.Context, command string, args ...strin
return out, nil
}
func (t *testCtx) decryptID(id string) ([]byte, error) {
return t.runQuarkCmd(context.Background(),
"encryption:id",
"--decrypt",
"--",
id,
)
}
func (t *testCtx) withAddrKR(
ctx context.Context,
c *proton.Client,

View File

@ -91,12 +91,6 @@ func (user *testUser) addAddress(addrID, email string) {
user.addresses = append(user.addresses, newTestAddr(addrID, email))
}
func (user *testUser) remAddress(addrID string) {
user.addresses = xslices.Filter(user.addresses, func(addr *testAddr) bool {
return addr.addrID != addrID
})
}
func (user *testUser) getUserPass() string {
return user.userPass
}
@ -229,7 +223,13 @@ func (t *testCtx) replace(value string) string {
// Create a new user if it doesn't exist yet.
if _, ok := t.userUUIDByName[name]; !ok {
t.userUUIDByName[name] = uuid.NewString()
val := uuid.NewString()
if name != strings.ToLower(name) {
val = "Mixed-Caps-" + val
}
t.userUUIDByName[name] = val
}
return t.userUUIDByName[name]
@ -290,6 +290,18 @@ func (t *testCtx) getUserByID(userID string) *testUser {
return t.userByID[userID]
}
func (t *testCtx) getUserByAddress(email string) *testUser {
for _, user := range t.userByID {
for _, addr := range user.addresses {
if addr.email == email {
return user
}
}
}
panic(fmt.Sprintf("unknown email %q", email))
}
func (t *testCtx) getMBoxID(userID string, name string) string {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

View File

@ -33,7 +33,6 @@ Feature: Configuration Status Telemetry
And config status event "bridge_config_success" is eventually send 1 time
@long-black
Scenario: Config Status Success send only once
Then bridge telemetry feature is enabled
When the user logs in with username "[user:user]" and password "password"
@ -77,4 +76,4 @@ Feature: Configuration Status Telemetry
And bridge stops
And force config status progress to be sent for user"[user:user]"
And bridge starts
Then config status event "bridge_config_progress" is eventually send 1 time
Then config status event "bridge_config_progress" is eventually send 1 time

View File

@ -21,9 +21,9 @@ Feature: Bridge checks for updates
Then bridge sends a manual update event for version "2.4.0"
Scenario: Update is required to continue using bridge
Given there exists an account with username "user" and password "password"
Given there exists an account with username "[user:user]" and password "password"
And bridge is version "2.3.0" and the latest available version is "2.3.0" reachable from "2.3.0"
And the API requires bridge version at least "2.4.0"
When bridge starts
And the user logs in with username "user" and password "password"
Then bridge sends a forced update event
And the user logs in with username "[user:user]" and password "password"
Then bridge sends a forced update event

View File

@ -3,7 +3,6 @@ Feature: A user can authenticate an IMAP client
Given there exists an account with username "[user:user]" and password "password"
And there exists an account with username "[user:user2]" and password "password2"
And the account "[user:user]" has additional address "[alias:alias]@[domain]"
And the account "[user:user2]" has additional disabled address "[alias:alias2]@[domain]"
Then it succeeds
When bridge starts
And the user logs in with username "[user:user]" and password "password"
@ -21,8 +20,12 @@ Feature: A user can authenticate an IMAP client
Scenario: IMAP client can authenticate successfully with secondary address
Given user "[user:user]" connects and authenticates IMAP client "1" with address "[alias:alias]@[domain]"
# Need to find way to setup disabled address on black
@skip-black
Scenario: IMAP client can not authenticate successfully with disable address
Given user "[user:user2]" connects and can not authenticate IMAP client "1" with address "[alias:alias2]@[domain]"
Given the account "[user:user2]" has additional disabled address "[alias:disabled]@[domain]"
And it succeeds
Then user "[user:user2]" connects and can not authenticate IMAP client "1" with address "[alias:disabled]@[domain]"
Scenario: IMAP client can authenticate successfully
When user "[user:user]" connects IMAP client "1"

View File

@ -40,6 +40,8 @@ Feature: IMAP list mailboxes
Then IMAP client "2" counts 20 mailboxes under "Folders"
And IMAP client "2" counts 60 mailboxes under "Labels"
# need to implement _schedule message_ test step for black
@skip-black
Scenario: List with scheduled mail
Given there exists an account with username "[user:user]" and password "password"
And the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Scheduled":

View File

@ -12,6 +12,8 @@ Feature: IMAP get mailbox info
And user "[user:user]" connects and authenticates IMAP client "1"
Then it succeeds
# with black subfolder is not renamed (maybe missing event?)
@skip-black
Scenario: Rename folder with subfolders
When IMAP client "1" renames "Folders/f1" to "Folders/f3"
And it succeeds

View File

@ -6,7 +6,6 @@ Feature: IMAP remove messages from mailbox
| mbox | folder |
| label | label |
And the address "[user:user]@[domain]" of account "[user:user]" has 10 messages in "Folders/mbox"
And the address "[user:user]@[domain]" of account "[user:user]" has 1 messages in "Scheduled"
Then it succeeds
When bridge starts
And the user logs in with username "[user:user]" and password "password"
@ -48,10 +47,3 @@ Feature: IMAP remove messages from mailbox
And it succeeds
And IMAP client "1" expunges
Then it fails
Scenario: Not possible to delete from Scheduled and expunge does nothing
When IMAP client "1" selects "Scheduled"
And IMAP client "1" marks message 1 as deleted
Then it succeeds
And IMAP client "1" expunges
Then it fails

View File

@ -42,6 +42,8 @@ Feature: IMAP Draft messages
And IMAP client "1" eventually sees 1 messages in "Drafts"
And IMAP client "1" does not see header "Reply-To" in message with subject "Basic Draft" in "Drafts"
# The draft event is received from black but it's not processed to IMAP
@skip-black
Scenario: Draft edited remotely
When the following fields were changed in draft 1 for address "[user:user]@[domain]" of account "[user:user]":
| to | subject | body |
@ -52,6 +54,8 @@ Feature: IMAP Draft messages
And IMAP client "1" eventually sees 1 messages in "Drafts"
And IMAP client "1" does not see header "Reply-To" in message with subject "Basic Draft" in "Drafts"
# The draft event is received from black but it's not processed to IMAP
@skip-black
@regression
Scenario: Draft edited remotely and sent from client
When IMAP client "1" selects "Drafts"
@ -103,6 +107,8 @@ Feature: IMAP Draft messages
And IMAP client "1" eventually sees 0 messages in "Drafts"
# The draft event is received from black but it's not processed to IMAP
@skip-black
Scenario: Draft moved to trash remotely
When draft 1 for address "[user:user]@[domain]" of account "[user:user]" was moved to trash
Then IMAP client "1" eventually sees the following messages in "Trash":

View File

@ -15,6 +15,8 @@ Feature: IMAP Fetch
And user "[user:user]" connects and authenticates IMAP client "1"
Then it succeeds
# The date returned from black is server time.. Black is probably correct we need to fix GPA server
@skip-black
Scenario: Fetch very old message
Given IMAP client "1" eventually sees the following messages in "INBOX":
| from | to | subject | date |
@ -22,6 +24,8 @@ Feature: IMAP Fetch
Then IMAP client "1" sees header "X-Original-Date: Sun, 13 Jul 1969 00:00:00 +0000" in message with subject "foo" in "INBOX"
# The date returned from black is server time.. Black is probably correct we need to fix GPA server
@skip-black
Scenario: Fetch from deleted cache
When the user deletes the gluon cache
Then IMAP client "1" eventually sees the following messages in "INBOX":

View File

@ -274,6 +274,8 @@ Feature: IMAP import messages
| Archive |
| Sent |
# The date returned from black is server time.. Black is probably correct we need to fix GPA server
@skip-black
Scenario: Import message without sender to Drafts
When IMAP client "1" appends the following message to "Drafts":
"""
@ -648,4 +650,4 @@ Feature: IMAP import messages
]
}
}
"""
"""

View File

@ -16,9 +16,6 @@ Feature: IMAP move messages
And the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Sent":
| from | to | subject | unread |
| john.doe@mail.com | [user:user]@[domain] | bax | false |
And the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Scheduled":
| from | to | subject | unread |
| john.doe@mail.com | [user:user]@[domain] | sch | false |
Then it succeeds
When bridge starts
And the user logs in with username "[user:user]" and password "password"
@ -124,15 +121,7 @@ Feature: IMAP move messages
| jane.doe@mail.com | name@[domain] | bar | true |
| john.doe@mail.com | [user:user]@[domain] | baz | false |
| john.doe@mail.com | [user:user]@[domain] | bax | false |
| john.doe@mail.com | [user:user]@[domain] | sch | false |
Scenario: Move message from Scheduled is not possible
Given test skips reporter checks
When IMAP client "1" moves the message with subject "sch" from "Scheduled" to "Inbox"
Then it fails
And IMAP client "1" eventually sees the following messages in "Scheduled":
| from | to | subject | unread |
| john.doe@mail.com | [user:user]@[domain] | sch | false |
Scenario: Move message from Inbox to Sent is not possible
Given test skips reporter checks

View File

@ -60,9 +60,49 @@ Feature: IMAP move messages by append and delete (without MOVE support, e.g., Ou
| INBOX | Folders/mbox | DELETE APPEND EXPUNGE |
| INBOX | Spam | DELETE APPEND EXPUNGE |
| INBOX | Trash | DELETE APPEND EXPUNGE |
| Trash | INBOX | DELETE EXPUNGE APPEND |
| Spam | INBOX | DELETE EXPUNGE APPEND |
| INBOX | Archive | DELETE EXPUNGE APPEND |
| INBOX | Folders/mbox | DELETE EXPUNGE APPEND |
| INBOX | Spam | DELETE EXPUNGE APPEND |
| INBOX | Trash | DELETE EXPUNGE APPEND |
# black cannot pass this test, test timimng probably needs to be different. Once fixed it can be merged again
@skip-black
Scenario Outline: Move message from <srcMailbox> to <dstMailbox> by <order>, second batch
When IMAP client "source" appends the following message to "<srcMailbox>":
"""
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
From: sndr1@[domain]
Date: 01 Jan 1980 00:00:00 +0000
To: rcvr1@[domain]
Subject: subj1
body1
"""
Then it succeeds
When IMAP client "source" appends the following message to "<srcMailbox>":
"""
Received: by 2002:0:0:0:0:0:0:0 with SMTP id 0123456789abcdef; Wed, 30 Dec 2020 01:23:45 0000
From: sndr2@[domain]
Date: 01 Jan 1980 00:00:00 +0000
To: rcvr2@[domain]
Subject: subj2
body2
"""
Then it succeeds
And IMAP client "source" selects "<srcMailbox>"
And IMAP client "target" selects "<dstMailbox>"
When IMAP clients "source" and "target" move message with subject "subj2" of "[user:user]" to "<dstMailbox>" by <order>
And IMAP client "source" eventually sees 1 messages in "<srcMailbox>"
And IMAP client "source" eventually sees the following messages in "<srcMailbox>":
| from | to | subject |
| sndr1@[domain] | rcvr1@[domain] | subj1 |
And IMAP client "target" eventually sees 1 messages in "<dstMailbox>"
And IMAP client "target" eventually sees the following messages in "<dstMailbox>":
| from | to | subject |
| sndr2@[domain] | rcvr2@[domain] | subj2 |
Examples:
| srcMailbox | dstMailbox | order |
| Trash | INBOX | DELETE EXPUNGE APPEND |

View File

@ -0,0 +1,56 @@
# need to implement _schedule message_ test step for black
@skip-black
Feature: IMAP interaction with scheduled
Scenario: Not possible to delete from Scheduled and expunge does nothing
Given there exists an account with username "[user:user]" and password "password"
And the account "[user:user]" has the following custom mailboxes:
| name | type |
| mbox | folder |
| label | label |
And the address "[user:user]@[domain]" of account "[user:user]" has 10 messages in "Folders/mbox"
And the address "[user:user]@[domain]" of account "[user:user]" has 1 messages in "Scheduled"
Then it succeeds
When bridge starts
And the user logs in with username "[user:user]" and password "password"
And user "[user:user]" finishes syncing
And user "[user:user]" connects and authenticates IMAP client "1"
Then it succeeds
When IMAP client "1" selects "Scheduled"
And IMAP client "1" marks message 1 as deleted
Then it succeeds
And IMAP client "1" expunges
Then it fails
Scenario: Move message from Scheduled is not possible
Given there exists an account with username "[user:user]" and password "password"
And the account "[user:user]" has the following custom mailboxes:
| name | type |
| mbox | folder |
| label | label |
| label2 | label |
And the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Inbox":
| from | to | subject | unread |
| john.doe@mail.com | [user:user]@[domain] | foo | false |
| jane.doe@mail.com | name@[domain] | bar | true |
And the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Labels/label2":
| from | to | subject | unread |
| john.doe@mail.com | [user:user]@[domain] | baz | false |
And the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Sent":
| from | to | subject | unread |
| john.doe@mail.com | [user:user]@[domain] | bax | false |
And the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Scheduled":
| from | to | subject | unread |
| john.doe@mail.com | [user:user]@[domain] | sch | false |
Then it succeeds
When bridge starts
And the user logs in with username "[user:user]" and password "password"
And user "[user:user]" finishes syncing
And user "[user:user]" connects and authenticates IMAP client "1"
Then it succeeds
Given test skips reporter checks
When IMAP client "1" moves the message with subject "sch" from "Scheduled" to "Inbox"
Then it fails
And IMAP client "1" eventually sees the following messages in "Scheduled":
| from | to | subject | unread |
| john.doe@mail.com | [user:user]@[domain] | sch | false |

View File

@ -6,7 +6,7 @@ Feature: IMAP change state of message in mailbox
| one | folder |
| two | folder |
And the address "[user:user]@[domain]" of account "[user:user]" has 5 messages in "Folders/one"
And the address "[user:user]@[domain]" of account "[user:user]" has 150 messages in "Folders/two"
And the address "[user:user]@[domain]" of account "[user:user]" has 5 messages in "Folders/two"
And the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Inbox":
| from | to | subject | unread |
| a@example.com | b@example.com | one | true |

View File

@ -2,15 +2,11 @@ Feature: A user can authenticate an SMTP client
Background:
Given there exists an account with username "[user:user]" and password "password"
And there exists an account with username "[user:user2]" and password "password2"
And there exists a disabled account with username "[user:user3]" and password "password3"
And the account "[user:user]" has additional address "[alias:alias]@[domain]"
And the account "[user:user2]" has additional disabled address "[alias:alias2]@[domain]"
And the account "[user:user3]" has additional address "[alias:alias3]@[domain]"
Then it succeeds
When bridge starts
And the user logs in with username "[user:user]" and password "password"
And the user logs in with username "[user:user2]" and password "password2"
And the user logs in with username "[user:user3]" and password "password3"
Then it succeeds
Scenario: SMTP client can authenticate successfully
@ -40,8 +36,12 @@ Feature: A user can authenticate an SMTP client
When user "[user:user]" connects and authenticates SMTP client "1" with address "[alias:alias]@[domain]"
Then it succeeds
# Need to find way to setup disabled address on black
@skip-black
Scenario: SMTP client can not authenticate with disabled address
When user "[user:user2]" connects and authenticates SMTP client "1" with address "[alias:alias2]@[domain]"
Given the account "[user:user2]" has additional disabled address "[alias:disabled]@[domain]"
And it succeeds
When user "[user:user2]" connects and authenticates SMTP client "1" with address "[alias:disabled]@[domain]"
Then it fails
Scenario: SMTP Logs out user
@ -55,7 +55,13 @@ Feature: A user can authenticate an SMTP client
When user "[user:user2]" connects SMTP client "2"
Then SMTP client "2" can authenticate
@ignore-live
# Need to find way to setup disabled address on black
@skip-black
Scenario: SMTP Authenticates with secondary address of account with disabled primary address
Given there exists a disabled account with username "[user:user3]" and password "password3"
And the account "[user:user3]" has additional address "[alias:alias3]@[domain]"
And it succeeds
And the user logs in with username "[user:user3]" and password "password3"
And it succeeds
When user "[user:user3]" connects and authenticates SMTP client "1" with address "[alias:alias3]@[domain]"
Then it succeeds

View File

@ -11,7 +11,8 @@ Feature: SMTP sending with attachment
And user "[user:user1]" connects and authenticates IMAP client "1"
Then it succeeds
@long-black
# black has issues with cyrilic char
@skip-black
Scenario: Sending with cyrillic PDF attachment
When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]":
"""
@ -77,7 +78,8 @@ Feature: SMTP sending with attachment
"""
@long-black
# black has issues with cyrilic char
@skip-black
Scenario: Sending with cyrillic docx attachment
When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]":
"""

View File

@ -3,10 +3,12 @@ Feature: SMTP with bcc
Given there exists an account with username "[user:user]" and password "password"
And there exists an account with username "[user:to]" and password "password"
And there exists an account with username "[user:bcc]" and password "password"
And there exists an account with username "[user:bcc2]" and password "password"
Then it succeeds
When bridge starts
And the user logs in with username "[user:user]" and password "password"
And the user logs in with username "[user:bcc]" and password "password"
And the user logs in with username "[user:bcc2]" and password "password"
And user "[user:user]" connects and authenticates SMTP client "1"
Then it succeeds
@ -46,8 +48,6 @@ Feature: SMTP with bcc
}
"""
@long-black
Scenario: Send message only to bcc
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:bcc]@[domain]":
"""
@ -81,3 +81,52 @@ Feature: SMTP with bcc
Then IMAP client "2" eventually sees the following messages in "Inbox":
| from | to | bcc | subject | unread |
| [user:user]@[domain] | | | hello | true |
Scenario: Send message to bcc and bcc2
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:bcc]@[domain], [user:bcc2]@[domain]":
"""
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
From: <[user:user]@[domain]>
Bcc: <[user:bcc]@[domain]>, <[user:bcc2]@[domain]>
Subject: hi
hello
"""
Then it succeeds
When user "[user:user]" connects and authenticates IMAP client "1"
Then IMAP client "1" eventually sees the following message in "Sent" with this structure:
"""
{
"from": "[user:user]@[domain]",
"BCC": "[user:bcc]@[domain]; [user:bcc2]@[domain]",
"subject": "hi",
"content":{
"content-type": "text/plain",
"content-type-charset": "utf-8",
"transfer-encoding": "quoted-printable",
"body-is": "hello"
}
}
"""
When user "[user:bcc]" connects and authenticates IMAP client "2"
Then IMAP client "2" eventually sees the following messages in "Inbox":
| from | to | bcc | subject | unread |
| [user:user]@[domain] | | | hi | true |
When user "[user:bcc2]" connects and authenticates IMAP client "2"
Then IMAP client "2" eventually sees the following messages in "Inbox":
| from | to | bcc | subject | unread |
| [user:user]@[domain] | | | hi | true |

View File

@ -9,7 +9,6 @@ Feature: SMTP sending embedded message
And user "[user:user]" connects and authenticates SMTP client "1"
Then it succeeds
@long-black
Scenario: Send it
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:to]@[domain]":
"""
@ -49,4 +48,4 @@ Feature: SMTP sending embedded message
When user "[user:to]" connects and authenticates IMAP client "2"
Then IMAP client "2" eventually sees the following messages in "Inbox":
| from | to | subject | attachments | unread |
| [user:user]@[domain] | [user:to]@[domain] | Embedded message | embedded.eml | true |
| [user:user]@[domain] | [user:to]@[domain] | Embedded message | embedded.eml | true |

View File

@ -1,7 +1,6 @@
Feature: SMTP wrong messages
Background:
Given there exists an account with username "[user:user]" and password "password"
And the account "[user:user]" has additional disabled address "[user:disabled]@[domain]"
And there exists an account with username "[user:to]" and password "password"
Then it succeeds
When bridge starts
@ -48,13 +47,3 @@ Feature: SMTP wrong messages
"""
Then it fails with error "invalid return path"
Scenario: Send from a valid address that cannot send
When SMTP client "1" sends the following message from "[user:disabled]@[domain]" to "[user:to]@[domain]":
"""
From: Bridge Test Disabled <[user:disabled]@[domain]>
To: Internal Bridge <[user:to]@[domain]>
Hello
"""
And it fails with error "Error: can't send on address: [user:disabled]@[domain]"

View File

@ -0,0 +1,24 @@
Feature: SMTP wrong messages
Background:
Given there exists an account with username "[user:user]" and password "password"
And the account "[user:user]" has additional disabled address "[user:disabled]@[domain]"
And there exists an account with username "[user:to]" and password "password"
Then it succeeds
When bridge starts
And the user logs in with username "[user:user]" and password "password"
And user "[user:user]" connects and authenticates SMTP client "1"
Then it succeeds
# Need to find way to setup disabled address on black
@skip-black
Scenario: Send from a valid address that cannot send
Given the account "[user:user]" has additional disabled address "[user:disabled]@[domain]"
When SMTP client "1" sends the following message from "[user:disabled]@[domain]" to "[user:to]@[domain]":
"""
From: Bridge Test Disabled <[user:disabled]@[domain]>
To: Internal Bridge <[user:to]@[domain]>
Hello
"""
And it fails with error "Error: can't send on address: [user:disabled]@[domain]"

View File

@ -9,6 +9,8 @@ Feature: SMTP sending of plain messages
And user "[user:user]" connects and authenticates SMTP client "1"
Then it succeeds
# black fails to get parent ID
@skip-black
Scenario: HTML message to external account
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "pm.bridge.qa@gmail.com":
"""
@ -49,6 +51,8 @@ Feature: SMTP sending of plain messages
}
"""
# black is changing order of attachments
@skip-black
Scenario: HTML message with inline image to external account
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "pm.bridge.qa@gmail.com":
"""
@ -311,6 +315,8 @@ Feature: SMTP sending of plain messages
}
"""
# black fails to get parent ID
@skip-black
Scenario: HTML message with extremely long line (greater than default 2000 line limit) to external account
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "pm.bridge.qa@gmail.com":
"""
@ -352,15 +358,13 @@ Feature: SMTP sending of plain messages
"""
Scenario: HTML message with Foreign/Nonascii chars in Subject and Body to external
When there exists an account with username "bridgetest" and password "password"
And the user logs in with username "bridgetest" and password "password"
And user "bridgetest" connects and authenticates SMTP client "1"
And SMTP client "1" sends the following EML "html/foreign_ascii_subject_body.eml" from "bridgetest@proton.local" to "pm.bridge.qa@gmail.com"
When user "[user:user]" connects and authenticates SMTP client "1"
And SMTP client "1" sends the following EML "html/foreign_ascii_subject_body.template.eml" from "[user:user]@[domain]" to "pm.bridge.qa@gmail.com"
Then it succeeds
When user "bridgetest" connects and authenticates IMAP client "1"
When user "[user:user]" connects and authenticates IMAP client "1"
Then IMAP client "1" eventually sees the following messages in "Sent":
| from | to | subject |
| bridgetest@proton.local | pm.bridge.qa@gmail.com | Subjεέςτ Ä È |
| from | to | subject |
| [user:user]@[domain] | pm.bridge.qa@gmail.com | Subjεέςτ Ä È |
And the body in the "POST" request to "/mail/v4/messages" is:
"""
{
@ -384,11 +388,13 @@ Feature: SMTP sending of plain messages
# It is expected for the structure check to look a bit different. More info on GODT-3011
@regression
# Black changes order of attachments
@skip-black
Scenario: HTML message with remote content in Body
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:to]@[domain]":
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:user2]@[domain]":
"""
Date: 01 Jan 1980 00:00:00 +0000
To: Internal Bridge Test <[user:to]@[domain]>
To: Internal Bridge Test <[user:user2]@[domain]>
From: Bridge Test <[user:user]@[domain]>
Subject: MESSAGE WITH REMOTE CONTENT SENT
Content-Type: multipart/alternative;
@ -442,7 +448,7 @@ Feature: SMTP sending of plain messages
"""
{
"date": "01 Jan 01 00:00 +0000",
"to": "Internal Bridge Test <[user:to]@[domain]>",
"to": "Internal Bridge Test <[user:user2]@[domain]>",
"from": "Bridge Test <[user:user]@[domain]>",
"subject": "MESSAGE WITH REMOTE CONTENT SENT",
"content": {

File diff suppressed because one or more lines are too long

View File

@ -8,8 +8,6 @@ Feature: SMTP sending two messages
And the user logs in with username "[user:recp]" and password "password"
Then it succeeds
@long-black
Scenario: Send from one account to the other
When user "[user:user]" connects and authenticates SMTP client "1"
And SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:recp]@[domain]":
@ -64,8 +62,6 @@ Feature: SMTP sending two messages
| from | to | subject | body |
| [user:user]@[domain] | [user:recp]@[domain] | One account to the other | hello |
@long-black
Scenario: Send from one account to the other with attachments
When user "[user:user]" connects and authenticates SMTP client "1"
And SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:recp]@[domain]":
@ -137,4 +133,4 @@ Feature: SMTP sending two messages
When user "[user:recp]" connects and authenticates IMAP client "2"
Then IMAP client "2" eventually sees the following messages in "Inbox":
| from | to | subject | body | attachments | unread |
| [user:user]@[domain] | [user:recp]@[domain] | Plain with attachment internal | This is the body | outline-light-instagram-48.png | true |
| [user:user]@[domain] | [user:recp]@[domain] | Plain with attachment internal | This is the body | outline-light-instagram-48.png | true |

View File

@ -171,15 +171,13 @@ Feature: SMTP sending of plain messages
"""
Scenario: Basic message with multiple different attachments to internal account
When there exists an account with username "bridgetest" and password "password"
And the user logs in with username "bridgetest" and password "password"
And user "bridgetest" connects and authenticates SMTP client "1"
And SMTP client "1" sends the following EML "plain/text_plain_multiple_attachments.eml" from "bridgetest@proton.local" to "internalbridgetest@proton.local"
When user "[user:user]" connects and authenticates SMTP client "1"
And SMTP client "1" sends the following EML "plain/text_plain_multiple_attachments.template.eml" from "[user:user]@[domain]" to "[user:to]@[domain]"
Then it succeeds
When user "bridgetest" connects and authenticates IMAP client "1"
When user "[user:user]" connects and authenticates IMAP client "1"
Then IMAP client "1" eventually sees the following messages in "Sent":
| from | to | subject |
| bridgetest@proton.local | internalbridgetest@proton.local | Plain with multiple different attachments |
| from | to | subject |
| [user:user]@[domain] | [user:to]@[domain] | Plain with multiple different attachments |
And the body in the "POST" request to "/mail/v4/messages" is:
"""
{
@ -190,7 +188,7 @@ Feature: SMTP sending of plain messages
},
"ToList": [
{
"Address": "internalbridgetest@proton.local",
"Address": "[user:to]@[domain]",
"Name": "Internal Bridge"
}
],

View File

@ -164,6 +164,8 @@ Feature: SMTP sending of PLAIN messages to Internal recipient
}
"""
# black changes order of attachments
@skip-black
Scenario: Plain message with multiple attachments to Internal
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:to]@[domain]":
"""
@ -660,6 +662,8 @@ Feature: SMTP sending of PLAIN messages to Internal recipient
}
"""
# black is changing order of attachments
@skip-black
Scenario: Forward a Plain message containing various attachments
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:to]@[domain]":
"""

View File

@ -17,7 +17,6 @@ Feature: SMTP sending the same message twice
"""
And it succeeds
@long-black
Scenario: The exact same message is not sent twice
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:to]@[domain]":
"""
@ -37,8 +36,6 @@ Feature: SMTP sending the same message twice
| from | to | subject | body |
| [user:user]@[domain] | [user:to]@[domain] | Hello | World |
@long-black
Scenario: Slight change means different message and is sent twice
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:to]@[domain]":
"""
@ -58,4 +55,4 @@ Feature: SMTP sending the same message twice
Then IMAP client "2" eventually sees the following messages in "Inbox":
| from | to | subject | body |
| [user:user]@[domain] | [user:to]@[domain] | Hello | World |
| [user:user]@[domain] | [user:to]@[domain] | Hello. | World |
| [user:user]@[domain] | [user:to]@[domain] | Hello. | World |

View File

@ -11,7 +11,6 @@ Feature: SMTP send reply
And user "[user:user1]" connects and authenticates IMAP client "1"
Then it succeeds
@long-black
Scenario: Reply with In-Reply-To but no References
# User1 send the initial message.
When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]":
@ -58,7 +57,6 @@ Feature: SMTP send reply
| from | subject | body | in-reply-to | references | reply-to |
| [user:user2]@[domain] | FW - Please Reply | Heya | <something@protonmail.ch> | <something@protonmail.ch> | [user:user2]@[domain] |
@long-black
Scenario: Reply with References but no In-Reply-To
# User1 send the initial message.
When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]":
@ -106,7 +104,6 @@ Feature: SMTP send reply
| [user:user2]@[domain] | FW - Please Reply | Heya | <something@protonmail.ch> | <something@protonmail.ch> | [user:user2]@[domain] |
@long-black
Scenario: Reply with both References and In-Reply-To
# User1 send the initial message.
When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]":
@ -155,7 +152,6 @@ Feature: SMTP send reply
| [user:user2]@[domain] | FW - Please Reply | Heya | <something@protonmail.ch> | <something@protonmail.ch> | [user:user2]@[domain] |
@long-black
Scenario: Reply with In-Reply-To matching several received ExternalID
# User1 send the initial message.
When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]":
@ -216,7 +212,6 @@ Feature: SMTP send reply
| [user:user2]@[domain] | FW - Please Reply | Heya | | |
@long-black
Scenario: Reply with In-Reply-To matching several ExternalID but one sent by us
# User1 send the initial message.
When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]":
@ -279,7 +274,6 @@ Feature: SMTP send reply
| [user:user2]@[domain] | FW - Please Reply | <something@external.com> | <something@external.com> |
| [user:user2]@[domain] | FW - Please Reply Again | <something@external.com> | <something@external.com> |
@long-black
Scenario: Reply with In-Reply-To and X-Forwarded-Message-Id sets forwarded flag
# User1 send the initial message.
When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]":
@ -334,7 +328,8 @@ Feature: SMTP send reply
| from | subject | in-reply-to | references |
| [user:user2]@[domain] | FW - Please Reply | <something@external.com> | <something@external.com> |
@long-black
# black: missing answered flag
@skip-black
Scenario: Reply with In-Reply-To sets answered flag
# User1 send the initial message.
When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]":
@ -386,4 +381,4 @@ Feature: SMTP send reply
# User1 receive the reply.|
And IMAP client "1" eventually sees the following messages in "INBOX":
| from | subject | in-reply-to | references |
| [user:user2]@[domain] | FW - Please Reply | <something@external.com> | <something@external.com> |
| [user:user2]@[domain] | FW - Please Reply | <something@external.com> | <something@external.com> |

View File

@ -2,7 +2,7 @@ Feature: SMTP sending two messages
Background:
Given there exists an account with username "[user:user]" and password "password"
And there exists an account with username "[user:multi]" and password "password"
And the account "[user:multi]" has additional address "[user:multi-alias]@[domain]"
And the account "[user:multi]" has additional address "[alias:multi]@[domain]"
And there exists an account with username "[user:to]" and password "password"
Then it succeeds
When bridge starts
@ -34,7 +34,7 @@ Feature: SMTP sending two messages
Scenario: Send with two addresses of the same user in split mode
When user "[user:multi]" connects and authenticates SMTP client "1" with address "[user:multi]@[domain]"
And user "[user:multi]" connects and authenticates SMTP client "2" with address "[user:multi-alias]@[domain]"
And user "[user:multi]" connects and authenticates SMTP client "2" with address "[alias:multi]@[domain]"
And SMTP client "1" sends the following message from "[user:multi]@[domain]" to "[user:to]@[domain]>":
"""
From: Bridge Test <[user:multi]@[domain]>

View File

@ -111,9 +111,9 @@ Feature: Address mode
| b@[domain] | b@[domain] | two | false |
| c@[domain] | c@[domain] | three | true |
| d@[domain] | d@[domain] | four | false |
Given the account "[user:user]" has additional address "other@[domain]"
Given the account "[user:user]" has additional address "[user:other]@[domain]"
And bridge sends an address created event for user "[user:user]"
When user "[user:user]" connects and authenticates IMAP client "3" with address "other@[domain]"
When user "[user:user]" connects and authenticates IMAP client "3" with address "[user:other]@[domain]"
Then IMAP client "3" eventually sees the following messages in "All Mail":
| from | to | subject | unread |
| a@[domain] | a@[domain] | one | true |
@ -134,11 +134,13 @@ Feature: Address mode
| from | to | subject | unread |
| c@[domain] | c@[domain] | three | true |
| d@[domain] | d@[domain] | four | false |
Given the account "[user:user]" has additional address "other@[domain]"
Given the account "[user:user]" has additional address "[user:other]@[domain]"
And bridge sends an address created event for user "[user:user]"
When user "[user:user]" connects and authenticates IMAP client "3" with address "other@[domain]"
When user "[user:user]" connects and authenticates IMAP client "3" with address "[user:other]@[domain]"
Then IMAP client "3" eventually sees 0 messages in "All Mail"
# Cannot delete primary address on black
@skip-black
Scenario: The user deletes an address while in combined mode
When user "[user:user]" connects and authenticates IMAP client "1" with address "[user:user]@[domain]"
Then IMAP client "1" eventually sees the following messages in "All Mail":
@ -159,6 +161,8 @@ Feature: Address mode
When user "[user:user]" connects IMAP client "3"
Then IMAP client "3" cannot authenticate with address "[alias:alias]@[domain]"
# Cannot delete primary address on black
@skip-black
Scenario: The user deletes an address while in split mode
Given the user sets the address mode of user "[user:user]" to "split"
And user "[user:user]" finishes syncing
@ -179,4 +183,4 @@ Feature: Address mode
Scenario: The user makes an alias the primary address while in combined mode
Scenario: The user makes an alias the primary address while in split mode
Scenario: The user makes an alias the primary address while in split mode

View File

@ -12,6 +12,8 @@ Feature: user's contact
Then it succeeds
# Implement contacts on black
@skip-black
Scenario: Playing with contact settings
When the contact "SuperTester@proton.me" of user "[user:user]" has message format "plain"
When the contact "SuperTester@proton.me" of user "[user:user]" has message format "HTML"

View File

@ -1,8 +1,6 @@
Feature: A user can login
Background:
Given there exists an account with username "[user:user]" and password "password2"
And there exists an account with username "[user:MixedCaps]" and password "password3"
And there exists a disabled account with username "[user:disabled]" and password "password4"
Then it succeeds
And bridge starts
Then it succeeds
@ -24,11 +22,18 @@ Feature: A user can login
When the user logs in with username "[user:user]" and password "password2"
Then user "[user:user]" is not listed
# Mixed caps doesn't work on black
@skip-black
Scenario: Login to account with caps
Given there exists an account with username "[user:MixedCaps]" and password "password3"
And it succeeds
When the user logs in with username "[user:MixedCaps]" and password "password3"
Then user "[user:MixedCaps]" is eventually listed and connected
# Mixed caps doesn't work on black
@skip-black
Scenario: Login to account with disabled primary
Given there exists a disabled account with username "[user:disabled]" and password "password4"
When the user logs in with username "[user:disabled]" and password "password4"
Then user "[user:disabled]" is eventually listed and connected
@ -45,4 +50,9 @@ Feature: A user can login
When the user logs in with username "[user:user]" and password "password2"
And the user logs in with username "[user:additional]" and password "password"
Then user "[user:user]" is eventually listed and connected
And user "[user:additional]" is eventually listed and connected
And user "[user:additional]" is eventually listed and connected
Scenario: Login to account with an alias address
Given the account "[user:user]" has additional address "[user:alias]@[domain]"
When the user logs in with alias address "[user:alias]@[domain]" and password "password2"
Then user "[user:user]" is eventually listed and connected

View File

@ -17,10 +17,10 @@ Feature: A logged out user can login again
Then user "[user:user]" is not listed
Scenario: Bridge password persists after logout/login
Given there exists an account with username "testUser" and password "password"
And the user logs in with username "testUser" and password "password"
And the bridge password of user "testUser" is changed to "YnJpZGdlcGFzc3dvcmQK"
And user "testUser" is deleted
And the user logs in with username "testUser" and password "password"
Then user "testUser" is eventually listed and connected
And the bridge password of user "testUser" is equal to "YnJpZGdlcGFzc3dvcmQK"
Given there exists an account with username "[user:test]" and password "password"
And the user logs in with username "[user:test]" and password "password"
And the bridge password of user "[user:test]" is changed to "YnJpZGdlcGFzc3dvcmQK"
And user "[user:test]" is deleted
And the user logs in with username "[user:test]" and password "password"
Then user "[user:test]" is eventually listed and connected
And the bridge password of user "[user:test]" is equal to "YnJpZGdlcGFzc3dvcmQK"

View File

@ -6,7 +6,7 @@ Feature: The user reports a problem
And the user logs in with username "[user:user]" and password "password"
And user "[user:user]" finishes syncing
Then it succeeds
Scenario: User sends a problem report without logs attached
When the user reports a bug
Then the header in the "POST" multipart request to "/core/v4/reports/bug" has "Title" set to "[Bridge] Bug - title"
@ -22,7 +22,7 @@ Feature: The user reports a problem
And the header in the "POST" multipart request to "/core/v4/reports/bug" has "Username" set to "[user:user]"
And the header in the "POST" multipart request to "/core/v4/reports/bug" has file "logs.zip"
@regression
Scenario: User sends a problem report while signed out of Bridge
When user "[user:user]" logs out
@ -30,7 +30,7 @@ Feature: The user reports a problem
Then it succeeds
And the header in the "POST" multipart request to "/core/v4/reports/bug" has "Username" set to "[user:user]"
And the header in the "POST" multipart request to "/core/v4/reports/bug" has "Email" set to "[user:user]@[domain]"
@regression
Scenario: User sends a problem report with changed Title
When the user reports a bug with field "Title" set to "Testing title"
@ -61,4 +61,4 @@ Feature: The user reports a problem
And the header in the "POST" multipart request to "/core/v4/reports/bug" has "Username" set to "[user:user]"
And the header in the "POST" multipart request to "/core/v4/reports/bug" has "Email" set to "[user:user]@[domain]"
And the header in the "POST" multipart request to "/core/v4/reports/bug" has "Client" set to "Apple Mail"
And the header in the "POST" multipart request to "/core/v4/reports/bug" has file "logs.zip"
And the header in the "POST" multipart request to "/core/v4/reports/bug" has file "logs.zip"

View File

@ -20,7 +20,9 @@ Feature: Bridge can fully synchronize an account with high number of messages, a
Then it succeeds
When bridge starts
Then it succeeds
# Too many messages need to use fixture on black
@skip-black
Scenario: The account is synced when the user logs in and the number of messages is correct
When the user logs in with username "[user:user]" and password "password"
Then bridge sends sync started and finished events for user "[user:user]"

View File

@ -358,6 +358,24 @@ func (s *scenario) imapClientSeesMessageInMailboxWithStructure(clientID, mailbox
return err
}
debug := false
for iFetch := range fetch {
if !debug {
continue
}
fmt.Printf("\n\n\n fetch %d %#v\n evenlope %+v\n",
iFetch, fetch[iFetch],
fetch[iFetch].Envelope,
)
for _, v := range fetch[iFetch].Body {
fmt.Println("body literal", v)
}
fmt.Printf("\n\n\n")
}
haveMessages := xslices.Map(fetch, newMessageStructFromIMAP)
return matchStructure(haveMessages, msgStruct)

View File

@ -1,40 +0,0 @@
// Copyright (c) 2024 Proton AG
//
// This file is part of Proton Mail Bridge.Bridge.
//
// Proton Mail 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.
//
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
package tests
import (
"time"
"github.com/ProtonMail/go-proton-api/server/backend"
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
"github.com/ProtonMail/proton-bridge/v3/internal/user"
)
func init() {
// Use the fast key generation for tests.
backend.GenerateKey = backend.FastGenerateKey
// Use the fast cert generation for tests.
certs.GenerateCert = FastGenerateCert
// Set the event period to 1 second for more responsive tests.
user.EventPeriod = time.Second
// Don't use jitter during tests.
user.EventJitter = 0
}

View File

@ -20,12 +20,29 @@ package tests
import (
"os"
"testing"
"time"
"github.com/ProtonMail/go-proton-api/server/backend"
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
"github.com/ProtonMail/proton-bridge/v3/internal/user"
"github.com/sirupsen/logrus"
"go.uber.org/goleak"
)
func TestMain(m *testing.M) {
// Use the fast key generation for tests.
backend.GenerateKey = backend.FastGenerateKey
// Use the fast cert generation for tests.
certs.GenerateCert = FastGenerateCert
if !isBlack() {
// Set the event period to 1 second for more responsive tests.
user.EventPeriod = time.Second
// Don't use jitter during tests.
user.EventJitter = 0
}
level := os.Getenv("FEATURE_TEST_LOG_LEVEL")
if os.Getenv("BRIDGE_API_DEBUG") != "" {

View File

@ -150,7 +150,7 @@ func (s *scenario) smtpClientSendsTheFollowingEmlFromTo(clientID, file, from, to
return err
}
if err := clientSend(client, from, to, string(b)); err != nil {
if err := clientSend(client, from, to, s.t.replace(string(b))); err != nil {
s.t.pushError(err)
}

View File

@ -101,6 +101,7 @@ func (s *scenario) steps(ctx *godog.ScenarioContext) {
// ==== USER ====
ctx.Step(`^the user logs in with username "([^"]*)" and password "([^"]*)"$`, s.userLogsInWithUsernameAndPassword)
ctx.Step(`^the user logs in with alias address "([^"]*)" and password "([^"]*)"$`, s.userLogsInWithAliasAddressAndPassword)
ctx.Step(`^user "([^"]*)" logs out$`, s.userLogsOut)
ctx.Step(`^user "([^"]*)" is deleted$`, s.userIsDeleted)
ctx.Step(`^the auth of user "([^"]*)" is revoked$`, s.theAuthOfUserIsRevoked)

View File

@ -1,15 +1,15 @@
From: Bridge Test <bridgetest@proton.local>
To: External Bridge <pm.bridge.qa@gmail.com>
Subject: =?UTF-8?B?U3Vias61zq3Pgs+EIMK2IMOEIMOI?=
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
Subjεέςτ ¶ Ä È
</body>
</html>
From: Bridge Test <[user:user]@[domain]>
To: External Bridge <pm.bridge.qa@gmail.com>
Subject: =?UTF-8?B?U3Vias61zq3Pgs+EIMK2IMOEIMOI?=
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: 8bit
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
Subjεέςτ ¶ Ä È
</body>
</html>

View File

@ -1,69 +1,69 @@
From: Bridge Test <bridgetest@proton.local>
To: Internal Bridge <internalbridgetest@proton.local>
Subject: Plain with multiple different attachments
Content-Type: multipart/mixed; boundary="bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606"
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Body of plain text message with multiple attachments
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: application/zip; name="PINProtected.zip"
Content-Disposition: attachment; filename="PINProtected.zip"
Content-Transfer-Encoding: base64
UEsDBBQACAAIAHhlwVYAAAAAAAAAABADAAAMACAAbWVzc2FnZTIudHh0VVQNAAdkdnhk7nZ4
AABQSwUGAAAAAAIAAgC/AAAAewMAAAAA
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document; name="test.docx"
Content-Disposition: attachment; filename="test.docx"
Content-Transfer-Encoding: base64
UEsDBBQABgAIAAAAIQDfpNJsWgEAACAFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIo
AAAAgB4AAHdvcmQvc3R5bGVzLnhtbFBLBQYAAAAACwALAMECAADXKQAAAAA=
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: application/pdf; name="test.pdf"
Content-Disposition: attachment; filename="test.pdf"
Content-Transfer-Encoding: base64
JVBERi0xLjUKJeLjz9MKNyAwIG9iago8PAovVHlwZSAvRm9udERlc2NyaXB0b3IKL0ZvbnRO
MjM0NAolJUVPRgo=
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; name="test.xlsx"
Content-Disposition: attachment; filename="test.xlsx"
Content-Transfer-Encoding: base64
UEsDBBQABgAIAAAAIQBi7p1oXgEAAJAEAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIo
AAoACgCAAgAAexwAAAAA
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: text/xml; charset=UTF-8; name="testxml.xml"
Content-Disposition: attachment; filename="testxml.xml"
Content-Transfer-Encoding: base64
PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHN1aXRl
VUtUZXN0Ii8+CiAgICAgICAgPC9jbGFzc2VzPgogICAgPC90ZXN0PgoKPC9zdWl0ZT4=
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: text/plain; charset=UTF-8; name="update.txt"
Content-Disposition: attachment; filename="update.txt"
Content-Transfer-Encoding: base64
DQpHb2NlQERFU0tUT1AtQ0dONkZENiBNSU5HVzY0IC9jL1Byb2dyYW0gRmlsZXMvUHJvdG9u
NFdqRUw5WkplbnJZcUZucXVvSFBEa0w5VWZFeTA0VlBYRkViVERWLVlQaS1BSWc9PSINCg==
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: text/calendar; charset=UTF-8; name="=?UTF-8?B?6YCZ5piv5ryi5a2X55qE5LiA5YCL5L6L5a2QLmljcw==?="
Content-Disposition: attachment; filename*0*=UTF-8''%E9%80%99%E6%98%AF%E6%BC%A2%E5%AD%97%E7%9A%84%E4%B8%80; filename*1*=%E5%80%8B%E4%BE%8B%E5%AD%90%2E%69%63%73
Content-Transfer-Encoding: base64
QkVHSU46VkNBTEVOREFSCk1FVEhPRDpQVUJMSVNIClZFUlNJT046Mi4wClgtV1ItQ0FMTkFN
RDpWQUxBUk0KRU5EOlZFVkVOVApFTkQ6VkNBTEVOREFSCg==
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606--
From: Bridge Test <[user:user]@[domain]>
To: Internal Bridge <[user:to]@[domain]>
Subject: Plain with multiple different attachments
Content-Type: multipart/mixed; boundary="bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606"
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
Body of plain text message with multiple attachments
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: application/zip; name="PINProtected.zip"
Content-Disposition: attachment; filename="PINProtected.zip"
Content-Transfer-Encoding: base64
UEsDBBQACAAIAHhlwVYAAAAAAAAAABADAAAMACAAbWVzc2FnZTIudHh0VVQNAAdkdnhk7nZ4
AABQSwUGAAAAAAIAAgC/AAAAewMAAAAA
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document; name="test.docx"
Content-Disposition: attachment; filename="test.docx"
Content-Transfer-Encoding: base64
UEsDBBQABgAIAAAAIQDfpNJsWgEAACAFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIo
AAAAgB4AAHdvcmQvc3R5bGVzLnhtbFBLBQYAAAAACwALAMECAADXKQAAAAA=
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: application/pdf; name="test.pdf"
Content-Disposition: attachment; filename="test.pdf"
Content-Transfer-Encoding: base64
JVBERi0xLjUKJeLjz9MKNyAwIG9iago8PAovVHlwZSAvRm9udERlc2NyaXB0b3IKL0ZvbnRO
MjM0NAolJUVPRgo=
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; name="test.xlsx"
Content-Disposition: attachment; filename="test.xlsx"
Content-Transfer-Encoding: base64
UEsDBBQABgAIAAAAIQBi7p1oXgEAAJAEAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIo
AAoACgCAAgAAexwAAAAA
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: text/xml; charset=UTF-8; name="testxml.xml"
Content-Disposition: attachment; filename="testxml.xml"
Content-Transfer-Encoding: base64
PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHN1aXRl
VUtUZXN0Ii8+CiAgICAgICAgPC9jbGFzc2VzPgogICAgPC90ZXN0PgoKPC9zdWl0ZT4=
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: text/plain; charset=UTF-8; name="update.txt"
Content-Disposition: attachment; filename="update.txt"
Content-Transfer-Encoding: base64
DQpHb2NlQERFU0tUT1AtQ0dONkZENiBNSU5HVzY0IC9jL1Byb2dyYW0gRmlsZXMvUHJvdG9u
NFdqRUw5WkplbnJZcUZucXVvSFBEa0w5VWZFeTA0VlBYRkViVERWLVlQaS1BSWc9PSINCg==
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606
Content-Type: text/calendar; charset=UTF-8; name="=?UTF-8?B?6YCZ5piv5ryi5a2X55qE5LiA5YCL5L6L5a2QLmljcw==?="
Content-Disposition: attachment; filename*0*=UTF-8''%E9%80%99%E6%98%AF%E6%BC%A2%E5%AD%97%E7%9A%84%E4%B8%80; filename*1*=%E5%80%8B%E4%BE%8B%E5%AD%90%2E%69%63%73
Content-Transfer-Encoding: base64
QkVHSU46VkNBTEVOREFSCk1FVEhPRDpQVUJMSVNIClZFUlNJT046Mi4wClgtV1ItQ0FMTkFN
RDpWQUxBUk0KRU5EOlZFVkVOVApFTkQ6VkNBTEVOREFSCg==
--bc5bd30245232f31b6c976adcd59bb0069c9b13f986f9e40c2571bb80aa16606--

View File

@ -448,19 +448,19 @@ func eventually(condition func() error) error {
var timerDuration = 30 * time.Second
// Extend to 5min for live API.
if hostURL := os.Getenv("FEATURE_TEST_HOST_URL"); hostURL != "" {
timerDuration = 600 * time.Second
timerDuration = 300 * time.Second
}
timer := time.NewTimer(timerDuration)
defer timer.Stop()
ticker := time.NewTicker(100 * time.Millisecond)
ticker := time.NewTicker(timerDuration / 300)
defer ticker.Stop()
for tick := ticker.C; ; {
select {
case <-timer.C:
return fmt.Errorf("timed out: %w", lastErr)
return fmt.Errorf("eventually timed out: %w", lastErr)
case <-tick:
tick = nil

View File

@ -57,7 +57,7 @@ func (s *scenario) theAccountHasAdditionalAddressWithoutKeys(username, address s
userID := s.t.getUserByName(username).getUserID()
// Decrypt the user's encrypted ID for use with quark.
userDecID, err := s.t.runQuarkCmd(context.Background(), "encryption:id", "--decrypt", userID)
userDecID, err := s.t.decryptID(userID)
if err != nil {
return err
}
@ -66,6 +66,7 @@ func (s *scenario) theAccountHasAdditionalAddressWithoutKeys(username, address s
if _, err := s.t.runQuarkCmd(
context.Background(),
"user:create:address",
"--",
string(userDecID),
s.t.getUserByID(userID).getUserPass(),
@ -88,7 +89,6 @@ func (s *scenario) theAccountHasAdditionalAddressWithoutKeys(username, address s
}
func (s *scenario) theAccountNoLongerHasAdditionalAddress(username, address string) error {
userID := s.t.getUserByName(username).getUserID()
addrID := s.t.getUserByName(username).getAddrID(address)
if err := s.t.withClient(context.Background(), username, func(ctx context.Context, c *proton.Client) error {
@ -101,8 +101,6 @@ func (s *scenario) theAccountNoLongerHasAdditionalAddress(username, address stri
return err
}
s.t.getUserByID(userID).remAddress(addrID)
return nil
}
@ -367,6 +365,42 @@ func (s *scenario) userLogsInWithUsernameAndPassword(username, password string)
return nil
}
func (s *scenario) userLogsInWithAliasAddressAndPassword(alias, password string) error {
smtpEvtCh, cancelSMTP := s.t.bridge.GetEvents(events.SMTPServerReady{})
defer cancelSMTP()
imapEvtCh, cancelIMAP := s.t.bridge.GetEvents(events.IMAPServerReady{})
defer cancelIMAP()
userID, err := s.t.bridge.LoginFull(context.Background(), s.t.getUserByAddress(alias).getName(), []byte(password), nil, nil)
if err != nil {
s.t.pushError(err)
} else {
// We need to wait for server to be up or we won't be able to connect. It should only happen once to avoid
// blocking on multiple Logins.
if !s.t.imapServerStarted {
<-imapEvtCh
s.t.imapServerStarted = true
}
if !s.t.smtpServerStarted {
<-smtpEvtCh
s.t.smtpServerStarted = true
}
if userID != s.t.getUserByAddress(alias).getUserID() {
return errors.New("user ID mismatch")
}
info, err := s.t.bridge.GetUserInfo(userID)
if err != nil {
return err
}
s.t.getUserByID(userID).setBridgePass(string(info.BridgePass))
}
return nil
}
func (s *scenario) userLogsOut(username string) error {
return s.t.bridge.LogoutUser(context.Background(), s.t.getUserByName(username).getUserID())
}
@ -480,7 +514,7 @@ func (s *scenario) addAdditionalAddressToAccount(username, address string, disab
userID := s.t.getUserByName(username).getUserID()
// Decrypt the user's encrypted ID for use with quark.
userDecID, err := s.t.runQuarkCmd(context.Background(), "encryption:id", "--decrypt", userID)
userDecID, err := s.t.decryptID(userID)
if err != nil {
return err
}
@ -494,6 +528,7 @@ func (s *scenario) addAdditionalAddressToAccount(username, address string, disab
}
args = append(args,
"--",
string(userDecID),
s.t.getUserByID(userID).getUserPass(),
address,
@ -524,6 +559,14 @@ func (s *scenario) addAdditionalAddressToAccount(username, address string, disab
func (s *scenario) createUserAccount(username, password string, disabled bool) error {
// Create the user and generate its default address (with keys).
if len(username) == 0 || username[0] == '-' {
panic("username must be non-empty and not start with minus")
}
if len(password) == 0 || password[0] == '-' {
panic("password must be non-empty and not start with minus")
}
args := []string{
"--name", username,
"--password", password,
@ -549,7 +592,7 @@ func (s *scenario) createUserAccount(username, password string, disabled bool) e
}
// Decrypt the user's encrypted ID for use with quark.
userDecID, err := s.t.runQuarkCmd(context.Background(), "encryption:id", "--decrypt", user.ID)
userDecID, err := s.t.decryptID(user.ID)
if err != nil {
return err
}
@ -559,6 +602,7 @@ func (s *scenario) createUserAccount(username, password string, disabled bool) e
context.Background(),
"user:create:subscription",
"--planID", "visionary2022",
"--",
string(userDecID),
); err != nil {
return err