Compare commits

...

26 Commits

Author SHA1 Message Date
Cimbali 743499f6cc
Merge b49ec833b6 into f34a7ff0ed 2024-03-13 14:18:49 -06:00
Jakub Cuth f34a7ff0ed chore: merge Zaehringen to master 2024-03-12 12:27:21 +00:00
Jakub da069a0155 chore: Zaehringen Bridge 3.10.0 changelog. 2024-03-06 10:33:17 +01:00
Jakub 8e49c84a12 chore: changelog update. 2024-03-06 08:19:13 +01:00
Jakub 754d80d097 feat(GODT-3193): assume text content type on attachments. 2024-03-01 15:25:37 +00:00
Jakub 63e272e270 feat(GODT-3193): preserve attachment encoding. 2024-03-01 15:25:37 +00:00
Xavier Michelon 54859a34b2 fix(GODT-3290): fix test failing because of leap day. 2024-03-01 10:56:24 +01:00
Jakub 9b1feed68b feat(GODT-3214): encrypt only with primary key. 2024-02-28 13:42:09 +00:00
Jakub c9b6cc162b feat(GODT-3199): add package log field. 2024-02-27 13:07:37 +01:00
Jakub bf3c90b8e9 test(GODT-1602): rebased GPA changes. 2024-02-26 16:56:52 +01:00
Jakub 8d63fb2301 feat(GODT-2662): enable cache on darwin tart. 2024-02-23 10:33:26 +01:00
Jakub 7953306cc8 feat(GODT-2662): use tart runner for darwin jobs. 2024-02-23 10:00:47 +01:00
Jakub Cuth 37352d44d2 test(GODT-1602): run integration tests against black 🖤 2024-02-19 10:43:35 +00:00
Jakub 2a1aeb208d test(GODT-3257): quad9 provider test not working on CI. 2024-02-19 10:06:02 +01:00
gzafirova 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
gzafirova 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
gzafirova 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
gzafirova 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
gzafirova 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
gzafirova 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
Jakub cf0f59afc0 test(GODT-3220): Add scenario cannot login with deleted alias 2024-02-13 10:56:23 +00:00
Jakub 65d8fbbf31 test: keep deleted address in test suite 2024-02-13 10:56:23 +00:00
Gjorgji Slamkov d919c0accf test(GODT-3220): Add step definition for logging in with alias address
GODT-3220
2024-02-13 10:56:23 +00:00
Gjorgji Slamkov 0ca07066db test(GODT-3220): Create function for getting the test user by address
GODT-3220
2024-02-13 10:56:23 +00:00
Cimbali b49ec833b6 Do not require a functional vcpkg during CMake process
Allows to simplify vendoring for offline build. Toolchain configuration
set from build scripts where vcpkg is already used.
2023-11-23 19:51:04 +00:00
Cimbali c1c68eca36 Allow to look for installed googletest framework
Requires renaming to proper dependency name and adding FIND_PACKAGE_ARGS
argument

This allows to avoid downloading on offline build systems.
Also don’t include googletest framework in install bundle (!)
2023-11-23 19:50:48 +00:00
81 changed files with 1434 additions and 417 deletions

View File

@ -3,6 +3,23 @@
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
@ -43,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.1+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

@ -53,14 +53,6 @@ endif()
set(VCPKG_ROOT "${BRIDGE_REPO_ROOT}/extern/vcpkg")
message(STATUS "VCPKG_ROOT is ${VCPKG_ROOT}")
if (WIN32)
find_program(VCPKG_EXE "${VCPKG_ROOT}/vcpkg.exe")
else()
find_program(VCPKG_EXE "${VCPKG_ROOT}/vcpkg")
endif()
if (NOT VCPKG_EXE)
message(FATAL_ERROR "vcpkg is not installed. Run build.sh (macOS/Linux) or build.ps1 (Windows) first.")
endif()
# For now we support only a single architecture for macOS (ARM64 or x86_64). We need to investigate how to build universal binaries with vcpkg.
if (APPLE)
@ -86,5 +78,3 @@ if (WIN32)
message(STATUS "Building for Intel x64 Windows computers")
set(VCPKG_TARGET_TRIPLET x64-windows)
endif()
set(CMAKE_TOOLCHAIN_FILE "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "toolchain")

View File

@ -63,6 +63,7 @@ $buildDir=(Join-Path $scriptDir "cmake-build-$buildConfig".ToLower())
$vcpkgRoot = (Join-Path $bridgeRepoRootDir "extern/vcpkg" -Resolve)
$vcpkgExe = (Join-Path $vcpkgRoot "vcpkg.exe")
$vcpkgBootstrap = (Join-Path $vcpkgRoot "bootstrap-vcpkg.bat")
$vcpkgToolchain = (Join-Path $vcpkgRoot "scripts/buildsystems/vcpkg.cmake")
function check_exit() {
if ($? -ne $True)
@ -91,6 +92,7 @@ git submodule update --init --recursive $vcpkgRoot
. $vcpkgExe install sentry-native:x64-windows grpc:x64-windows --clean-after-build
. $vcpkgExe upgrade --no-dry-run
. $cmakeExe -G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE="$buildConfig" `
-DCMAKE_TOOLCHAIN_FILE="$vcpkgToolchain" `
-DBRIDGE_APP_FULL_NAME="$bridgeFullName" `
-DBRIDGE_VENDOR="$bridgeVendor" `
-DBRIDGE_REVISION="$REVISION_HASH" `

View File

@ -95,6 +95,7 @@ fi
cmake \
-DCMAKE_BUILD_TYPE="${BUILD_CONFIG}" \
-DCMAKE_TOOLCHAIN_FILE="${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" \
-DBRIDGE_APP_FULL_NAME="${BRIDGE_APP_FULL_NAME}" \
-DBRIDGE_VENDOR="${BRIDGE_VENDOR}" \
-DBRIDGE_REVISION="${BRIDGE_REVISION}" \

View File

@ -174,14 +174,16 @@ endif ()
include(FetchContent)
FetchContent_Declare(
googletest
GTest
URL https://github.com/google/googletest/archive/b796f7d44681514f58a683a3a71ff17c94edb0c1.zip
FIND_PACKAGE_ARGS
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
set(INSTALL_GTEST OFF)
FetchContent_MakeAvailable(googletest)
FetchContent_MakeAvailable(GTest)
enable_testing()

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

@ -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í námìstí</ulice>
<prijmeni>ŠCheckCharsá</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