Compare commits

..

39 Commits

Author SHA1 Message Date
c35ff4f913 Other: James v1.8.10 2021-09-30 22:19:14 +02:00
cd6b5cdcc3 GODT-1348: max 100 conn per host 2021-09-30 21:19:11 +02:00
8f1ca00c9d GODT-1318 go-srp version identifier in go.sum underlying commit is identical 2021-09-30 20:19:07 +02:00
f21f583d05 GODT-1202: Do not update package if it's version older than launcher 2021-09-23 16:24:11 +00:00
e940d9f6fe GODT-1204: Handle importing too big messages 2021-09-23 11:09:56 +00:00
1ed8e939b8 Other: typo 2021-09-20 14:28:57 +02:00
b5d63783f7 GODT-1318: Bump gopenpgp to 2.2.2, go-srp to a843a0b9, go-crypto to 52430bf6 2021-09-17 11:54:55 +02:00
e0113ec267 GODT-219: Update to godog v0.12.1 2021-09-10 13:38:05 +02:00
22d2bcc21d GODT-1205: "RCPT TO" does not contain all addressed from "CC" 2021-09-06 21:13:37 +00:00
91dcb2f773 Other: Bridge James 1.8.9 2021-09-01 12:20:59 +02:00
c676c732ab Merge branch 'release-notes' into devel 2021-09-01 12:13:20 +02:00
444f2d8a12 Other 2021-09-01 07:45:48 +00:00
f10da3c7f0 GODT-1263: Fix crash on invalid or empty header 2021-08-27 13:39:34 +00:00
b8dd9f82bd GODT-1235: Fix 401 response error handling 2021-08-27 12:29:42 +00:00
1157e60972 GODT-1261: Fix building messages with long key 2021-08-16 15:47:14 +02:00
e9e4d8c725 Other: use windows-compatible filename when dumping message in QA builds 2021-08-04 13:05:57 +02:00
186fa24106 Other: Bridge James v 1.8.8 2021-07-21 06:45:47 +02:00
63780b7b8d GODT-1234 Set attachment name 'message.eml' for message/rfc822 attachments. 2021-07-19 14:40:55 +02:00
e3e4769d78 Other: remove dead code 2021-07-19 07:45:56 +02:00
b2e9c4e4e9 Other: Revert "GODT-1224: don't strip trailing newlines from message bodies"
This reverts commit 54161e263fa2f95795fc8623e9dfd2134afb0ae5.
2021-07-19 07:45:29 +02:00
db4cb36538 GODT-1224: don't strip trailing newlines from message bodies 2021-07-19 07:45:12 +02:00
984864553e Other: better keychain logging 2021-07-19 07:44:58 +02:00
2707a5627c Other: prefer empty string check vs nil check 2021-06-25 15:34:48 +02:00
8e0d6d41a6 Other 2021-06-23 08:40:15 +00:00
2b76a45e17 Other 2021-06-21 20:59:37 +00:00
7ab54da8c4 Other: Bridge James 1.8.7 2021-06-17 13:54:43 +02:00
2cce29e858 GODT-1201: bump gopenpgp to 2.1.10 and update crypto time 2021-06-17 05:32:17 +00:00
ef1223391b GODT-1193: Don't doubly encode parts 2021-06-17 07:04:43 +02:00
fb98a797ba Merge branch 'release/james' into devel 2021-06-15 15:56:31 +02:00
6be31bdeb2 Other go.mod 2021-06-15 15:55:02 +02:00
fce5990d19 Other: release notes 1.8.5 2021-06-15 08:57:59 +02:00
1d4ee0c33e Other: Bridge 1.8.6 2021-06-14 09:07:50 +02:00
a3e102e456 GODT-1166: Do not expose current auth token in interface 2021-06-11 17:13:26 +02:00
21dcac9fac GODT-1187: Remove IMAP/SMTP blocking when no internet. 2021-06-11 09:16:47 +00:00
f0ee82fdd2 GODT-1166: Preliminary disable IMAP/SMTP blocking feature. 2021-06-11 09:16:47 +00:00
0c6a098af9 GODT-1166: Reduce the number of auth for live test
- Changed: Do not reauth controller clients.
- Changed: Verbosisty is set only once before run
- Changed: AddUser takes TestAccount as argument
- Added: Setup/clean up before/after test run
- Added: Access to the current refresh token from pmapi.Client interface.
- Added: Context function to add test a user to bridge without login, just call users.FinishLogin.
- Added: PMAPIController.GetAuthClient returns authenticated client for username.
- Added: Persistent clients does not loggout after every scenario.
- Changed: Disabled no-internet tests.
2021-06-11 09:16:47 +00:00
5bf359d34f GODT-1193: don't use message.Read; permit non-UTF-8 charsets 2021-06-11 06:43:21 +00:00
6d784f2444 Other: release notes 1.8.5 2021-06-11 08:33:54 +02:00
835bf1e77f Other: make changelog linter happy 2021-06-09 09:51:31 +02:00
107 changed files with 2582 additions and 1361 deletions

View File

@ -2,6 +2,53 @@
Changelog [format](http://keepachangelog.com/en/1.0.0/)
## [Bridge 1.8.10] James
### Fixed
* GODT-1348: Max 100 conn per host.
* GODT-1204: Handle importing too big messages.
* GODT-1202: Do not update package if it's version older than launcher.
* GODT-1318: Bump gopenpgp to v2.2.2, go-srp to v0.0.1, go-crypto to 52430bf6.
* GODT-219: Update to godog v0.12.1.
* GODT-1205: "RCPT TO" does not contain all addressed from "CC".
* GODT-1103: Cleanup on windows when uninstalling Bridge.
## [Bridge 1.8.9] James
### Fixed
* GODT-1263: Fix crash on invalid or empty header.
* GODT-1235: Fix 401 response error handling.
* GODT-1261: Fix building messages with long key.
* Other: use windows-compatible filename when dumping message in QA builds.
## [Bridge 1.8.8] James
### Changed
* GODT-1234 Set attachment name 'message.eml' for `message/rfc822` attachments.
## [Bridge 1.8.7] James
### Changed
* GODT-1201: Update gopenpgp to 2.1.10.
### Fixed
* GODT-1193: Do not doubly encode parts.
## [Bridge 1.8.6] James
### Removed
* GODT-1187: Remove IMAP/SMTP blocking when no internet.
### Changed
* GODT-1166: Reduce the number of auth for live test.
### Fixed
* GODT-1193: Do not use message.Read permit non-UTF-8 charsets.
## [Bridge 1.8.5] James
### Fixed
@ -14,7 +61,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
### Added
* GODT-1155: Update gopenpgp v2.1.9 and use go-srp.
* GODT-1044: Lite parser for appended messages.
* GODT-1183: Add test for getting contact emails by email
* GODT-1183: Add test for getting contact emails by email.
* GODT-1184: Preserve signatures in externally signed messages.
### Changed

View File

@ -10,7 +10,7 @@ TARGET_OS?=${GOOS}
.PHONY: build build-ie build-nogui build-ie-nogui build-launcher build-launcher-ie versioner hasher
# Keep version hardcoded so app build works also without Git repository.
BRIDGE_APP_VERSION?=1.8.5+git
BRIDGE_APP_VERSION?=1.8.10+git
IE_APP_VERSION?=1.3.3+git
APP_VERSION:=${BRIDGE_APP_VERSION}
SRC_ICO:=logo.ico

View File

@ -24,6 +24,7 @@ import (
"path/filepath"
"runtime"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/internal/constants"
@ -86,7 +87,7 @@ func main() { // nolint[funlen]
versioner := versioner.New(updatesPath)
exe, err := getPathToExecutable(ExeName, versioner, kr, reporter)
exe, err := getPathToUpdatedExecutable(ExeName, versioner, kr, reporter)
if err != nil {
if exe, err = getFallbackExecutable(ExeName, versioner); err != nil {
logrus.WithError(err).Fatal("Failed to find any launchable executable")
@ -142,7 +143,7 @@ func appendLauncherPath(path string, args []string) []string {
return res
}
func getPathToExecutable(
func getPathToUpdatedExecutable(
name string,
versioner *versioner.Versioner,
kr *crypto.KeyRing,
@ -153,6 +154,11 @@ func getPathToExecutable(
return "", errors.Wrap(err, "failed to list available versions")
}
currentVersion, err := semver.StrictNewVersion(constants.Version)
if err != nil {
logrus.WithField("version", constants.Version).WithError(err).Error("Failed to parse current version")
}
for _, version := range versions {
vlog := logrus.WithField("version", version)
@ -170,6 +176,11 @@ func getPathToExecutable(
continue
}
// Skip versions that are less or equal to launcher version.
if currentVersion != nil && !version.SemVer().GreaterThan(currentVersion) {
continue
}
exe, err := version.GetExecutable(name)
if err != nil {
vlog.WithError(err).Error("Failed to get executable")
@ -179,7 +190,7 @@ func getPathToExecutable(
return exe, nil
}
return "", errors.New("no available versions")
return "", errors.New("no available newer versions")
}
func getFallbackExecutable(name string, versioner *versioner.Versioner) (string, error) {

View File

@ -1,12 +1,12 @@
# Encryption
Encryption is done in PMAPI, bridge utils and bridge itself. The best would be to keep encryption
in PMAPI and bridge utils (in pacakge such as messages). All packages are using our high-level
GopenPGP library on top of openpgp.
in PMAPI and bridge utils (in package such as messages). All packages are using our high-level
GopenPGP library on top of OpenPGP.
## `gopenpgp.KeyRing`
We use one `KeyRing` per address. Our usage then contains all keys for specific address. Primary
key is always on the first position, then there old ones to be able to decrypt last e-mail.
Openpgp encrypts given message with all available keys, so we need to first get first (primary)
OpenPGP encrypts given message with all available keys, so we need to first get first (primary)
key for encryption to have message encrypted only once with primary key.

15
go.mod
View File

@ -14,19 +14,20 @@ require (
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1
github.com/Masterminds/semver/v3 v3.1.0
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
github.com/ProtonMail/go-rfc5322 v0.8.0
github.com/ProtonMail/go-srp v0.0.0-20210514134713-bd9454f3fa01
github.com/ProtonMail/go-srp v0.0.1
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
github.com/ProtonMail/gopenpgp/v2 v2.1.9
github.com/ProtonMail/gopenpgp/v2 v2.2.2
github.com/PuerkitoBio/goquery v1.5.1
github.com/abiosoft/ishell v2.0.0+incompatible
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc
github.com/chzyer/logex v1.1.10 // indirect
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
github.com/cucumber/godog v0.8.1
github.com/cucumber/godog v0.12.1
github.com/cucumber/messages-go/v16 v16.0.1
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c
@ -57,19 +58,21 @@ require (
github.com/sirupsen/logrus v1.7.0
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/stretchr/testify v1.6.1
github.com/stretchr/testify v1.7.0
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d // indirect
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d // indirect
github.com/urfave/cli/v2 v2.2.0
github.com/vmihailenco/msgpack/v5 v5.1.3
go.etcd.io/bbolt v1.3.5
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec
)
replace (
github.com/docker/docker-credential-helpers => github.com/ProtonMail/docker-credential-helpers v1.1.0
github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57
)

288
go.sum
View File

@ -1,3 +1,16 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1 h1:j9HaafapDbPbGRDku6e/HRs6KBMcKHiWcm1/9Sbxnl4=
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
@ -8,28 +21,35 @@ github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMd
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57 h1:pHA4K54ifoogVLunGGHi3xyF5Nz4x+Uh3dJuy3NwGQQ=
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a h1:fXK2KsfnkBV9Nh+9SKzHchYjuE9s0vI20JG1mbtEAcc=
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c h1:FP7mMdsXy0ybzar1sJeIcZtaJka0U/ZmLTW4wRpolYk=
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac h1:2xU3QncAiS/W3UlWZTkbNKW5WkLzk6Egl1T0xX+sbjs=
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU=
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:5koQozTDELymYOyFbQ/VSubexAEXzDR8qGM5mO8GRdw=
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:795VPXcRUIQ9JyMNHP4el582VokQfippgjkQP3Gk0r0=
github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753 h1:I8IsYA297x0QLU80G5I6aLYUu3JYNSpo8j5fkXtFDW0=
github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
github.com/ProtonMail/go-rfc5322 v0.8.0 h1:7emrf75n3CDIduQflx7aT1nJa5h/kGsiFKUYX/+IAkU=
github.com/ProtonMail/go-rfc5322 v0.8.0/go.mod h1:BwpTbkJxkMGkc+pC84AXZnwuWOisEULBpfPIyIKS/Us=
github.com/ProtonMail/go-srp v0.0.0-20210514134713-bd9454f3fa01 h1:sRxNvPGnJFh6yWlSr9BpGsSrshFkZLClSm5oIi++a0I=
github.com/ProtonMail/go-srp v0.0.0-20210514134713-bd9454f3fa01/go.mod h1:jOXzdvWTILIJzl83yzi/EZcnnhpI+A/5EyflaeVfi/0=
github.com/ProtonMail/go-srp v0.0.0-20210910093455-a843a0b9adff h1:eiue56XAPSkOpsy5Fwnyz4+Vd7i2cN5D4orc++Irt1g=
github.com/ProtonMail/go-srp v0.0.0-20210910093455-a843a0b9adff/go.mod h1:Uvv5cqSGCs8MTZ8sbKiCkBnaB6/OA3eq2mc77tl2VVA=
github.com/ProtonMail/go-srp v0.0.1 h1:J0O9Zb5XTC6iDrB7feH41cu+TUEB+l7uHctXIK6oS2o=
github.com/ProtonMail/go-srp v0.0.1/go.mod h1:Uvv5cqSGCs8MTZ8sbKiCkBnaB6/OA3eq2mc77tl2VVA=
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ=
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA=
github.com/ProtonMail/gopenpgp/v2 v2.1.9 h1:MdvkFBP8ldOHYOoaVct9LO+Zv5rl6VdeN1QurntRmkc=
github.com/ProtonMail/gopenpgp/v2 v2.1.9/go.mod h1:CHIXesUdnPxIxtJTg2P/cxoA0cvUwIBpZIS8SsY82QA=
github.com/ProtonMail/gopenpgp/v2 v2.2.2 h1:u2m7xt+CZWj88qK1UUNBoXeJCFJwJCZ/Ff4ymGoxEXs=
github.com/ProtonMail/gopenpgp/v2 v2.2.2/go.mod h1:ajUlBGvxMH1UBZnaYO3d1FSVzjiC6kK9XlZYGiDCvpM=
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
@ -38,29 +58,53 @@ github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzg
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc h1:mZca0/HZ/XWXP9txkfdl2GH6mUzBqAlyJz3u5Lg8fuA=
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc/go.mod h1:qqsTQiwdyqxU05iDCsi0oN3P4nrVxAmn8xCtODDSf/U=
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c h1:j/C2kxPfyE0d87/ggAjIsCV5Cdkqmjb+O0W8W+1J+IY=
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cucumber/godog v0.8.1 h1:lVb+X41I4YDreE+ibZ50bdXmySxgRviYFgKY6Aw4XE8=
github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA=
github.com/cronokirby/saferith v0.31.0 h1:TIlhldetKLeGAb19bZvWiuwQEzfzwSPthDEyJ9Ah8xs=
github.com/cronokirby/saferith v0.31.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA=
github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE=
github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw=
github.com/cucumber/godog v0.12.1 h1:IhWVYFKDReM5WsuA9AuRLRPWOyvFNO9UBUKrNfLPais=
github.com/cucumber/godog v0.12.1/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc=
github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -69,6 +113,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a h1:bMdSPm6sssuOFpIaveu3XGAijMS3Tq2S3EqFZmZxidc=
@ -81,21 +126,18 @@ github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26 h1:FiSb8
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26/go.mod h1:+gnnZx3Mg3MnCzZrv0eZdp5puxXQUgGT/6N6L7ShKfM=
github.com/emersion/go-mbox v1.0.2 h1:tE/rT+lEugK9y0myEymCCHnwlZN04hlXPrbKkxRBA5I=
github.com/emersion/go-mbox v1.0.2/go.mod h1:Yp9IVuuOYLEuMv4yjgDHvhb5mHOcYH6x92Oas3QqEZI=
github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
github.com/emersion/go-message v0.12.1-0.20201221184100-40c3f864532b h1:xYuhW6egTaCP+zjbUcfoy/Dr3ASdVPR9W7fmkHvZHPE=
github.com/emersion/go-message v0.12.1-0.20201221184100-40c3f864532b/go.mod h1:N1JWdZQ2WRUalmdHAX308CWBq747VJ8oUorFI3VCBwU=
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.14.0 h1:RYW203p+EcPjL8Z/ZpT9lZ6iOc8MG1MQzEx1UKEkXlA=
github.com/emersion/go-smtp v0.14.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 h1:n9qx98xiS5V4x2WIpPC2rr9mUM5ri9r/YhCEKbhCHro=
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5/go.mod h1:WIi9g8OKJQHXtQbx7GExlo6UAFaui9WDMYabJ+Be4WI=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
@ -105,40 +147,96 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/getsentry/sentry-go v0.8.0 h1:F52cjBVLuiTfdW6p4JFuxlt3pOjKfWYT/aka7cdJ7v0=
github.com/getsentry/sentry-go v0.8.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE=
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-memdb v1.3.0 h1:xdXq34gBOMEloa9rlGStLxmfX/dyIK8htOv36dQUwHU=
github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@ -149,9 +247,13 @@ github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0Gqw
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:g0fAGBisHaEQ0TRq1iBvemFRf+8AEWEmBESSiWB3Vsc=
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
@ -161,12 +263,17 @@ github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubc
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d h1:gVjhBCfVGl32RIBooOANzfw+0UqX8HU+yPlMv8vypcg=
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d/go.mod h1:W6EbaYmb4RldPn0N3gvVHjY1wmU59kbymhW9NATWhwY=
github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -176,12 +283,12 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
github.com/martinlindhe/base36 v1.1.0 h1:cIwvvwYse/0+1CkUPYH5ZvVIYG3JrILmQEIbLuar02Y=
github.com/martinlindhe/base36 v1.1.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
@ -191,61 +298,95 @@ github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758=
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -256,10 +397,17 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk=
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d h1:hAZyEG2swPRWjF0kqqdGERXUazYnRJdAk4a58f14z7Y=
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:7m8PDYDEtEVqfjoUQc2UrFqhG0CDmoVJjRlQxexndFc=
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d h1:AJRoBel/g9cDS+yE8BcN3E+TDD/xNAguG21aoR8DAIE=
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
@ -278,40 +426,72 @@ github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgq
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190910184405-b558ed863381/go.mod h1:p895TfNkDgPEmEQrNiOtIl3j98d/tGU95djDj7NfyjQ=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -319,20 +499,36 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -343,36 +539,86 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec h1:A1qYjneJuzBZZ2gIB8rd6zrfq6l7SoEMJ8EsSilNK/U=
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190909214602-067311248421/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View File

@ -23,13 +23,11 @@ import (
"io"
"net"
"strings"
"sync/atomic"
"time"
imapid "github.com/ProtonMail/go-imap-id"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/imap/id"
"github.com/ProtonMail/proton-bridge/internal/imap/idle"
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
@ -43,43 +41,58 @@ import (
"github.com/emersion/go-imap/backend"
imapserver "github.com/emersion/go-imap/server"
"github.com/emersion/go-sasl"
"github.com/sirupsen/logrus"
)
type imapServer struct {
// Server takes care of IMAP listening serving. It implements serverutil.Server.
type Server struct {
panicHandler panicHandler
server *imapserver.Server
userAgent *useragent.UserAgent
eventListener listener.Listener
debugClient bool
debugServer bool
port int
isRunning atomic.Value
server *imapserver.Server
controller serverutil.Controller
}
// NewIMAPServer constructs a new IMAP server configured with the given options.
func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, port int, tls *tls.Config, imapBackend backend.Backend, userAgent *useragent.UserAgent, eventListener listener.Listener) *imapServer { // nolint[golint]
s := imapserver.New(imapBackend)
s.Addr = fmt.Sprintf("%v:%v", bridge.Host, port)
s.TLSConfig = tls
s.AllowInsecureAuth = true
s.ErrorLog = newServerErrorLogger("server-imap")
s.AutoLogout = 30 * time.Minute
if debugServer {
fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
log.Warning("================================================")
log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
log.Warning("================================================")
func NewIMAPServer(
panicHandler panicHandler,
debugClient, debugServer bool,
port int,
tls *tls.Config,
imapBackend backend.Backend,
userAgent *useragent.UserAgent,
eventListener listener.Listener,
) *Server {
server := &Server{
panicHandler: panicHandler,
userAgent: userAgent,
debugClient: debugClient,
debugServer: debugServer,
port: port,
}
server.server = newGoIMAPServer(tls, imapBackend, server.Address(), userAgent)
server.controller = serverutil.NewController(server, eventListener)
return server
}
func newGoIMAPServer(tls *tls.Config, backend backend.Backend, address string, userAgent *useragent.UserAgent) *imapserver.Server {
server := imapserver.New(backend)
server.TLSConfig = tls
server.AllowInsecureAuth = true
server.ErrorLog = serverutil.NewServerErrorLogger(serverutil.IMAP)
server.AutoLogout = 30 * time.Minute
server.Addr = address
serverID := imapid.ID{
imapid.FieldName: "ProtonMail Bridge",
imapid.FieldVendor: "Proton Technologies AG",
imapid.FieldSupportURL: "https://protonmail.com/support",
}
s.EnableAuth(sasl.Login, func(conn imapserver.Conn) sasl.Server {
server.EnableAuth(sasl.Login, func(conn imapserver.Conn) sasl.Server {
return sasl.NewLoginServer(func(address, password string) error {
user, err := conn.Server().Backend.Login(nil, address, password)
if err != nil {
@ -93,7 +106,7 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por
})
})
s.Enable(
server.Enable(
idle.NewExtension(),
imapmove.NewExtension(),
id.NewExtension(serverID, userAgent),
@ -103,87 +116,35 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por
uidplus.NewExtension(),
)
server := &imapServer{
panicHandler: panicHandler,
server: s,
userAgent: userAgent,
eventListener: eventListener,
debugClient: debugClient,
debugServer: debugServer,
port: port,
}
server.isRunning.Store(false)
return server
}
func (s *imapServer) HandlePanic() { s.panicHandler.HandlePanic() }
func (s *imapServer) IsRunning() bool { return s.isRunning.Load().(bool) }
func (s *imapServer) Port() int { return s.port }
// ListenAndServe will run server and all monitors.
func (s *Server) ListenAndServe() { s.controller.ListenAndServe() }
// ListenAndServe starts the server and keeps it on based on internet
// availability.
func (s *imapServer) ListenAndServe() {
serverutil.ListenAndServe(s, s.eventListener)
}
// Close turns off server and monitors.
func (s *Server) Close() { s.controller.Close() }
// ListenRetryAndServe will start listener. If port is occupied it will try
// again after coolDown time. Once listener is OK it will serve.
func (s *imapServer) ListenRetryAndServe(retries int, retryAfter time.Duration) {
if s.IsRunning() {
return
}
s.isRunning.Store(true)
// Implements serverutil.Server interface.
l := log.WithField("address", s.server.Addr)
l.Info("IMAP server is starting")
listener, err := net.Listen("tcp", s.server.Addr)
if err != nil {
s.isRunning.Store(false)
if retries > 0 {
l.WithError(err).WithField("retries", retries).Warn("IMAP listener failed")
time.Sleep(retryAfter)
s.ListenRetryAndServe(retries-1, retryAfter)
return
}
func (Server) Protocol() serverutil.Protocol { return serverutil.IMAP }
func (s *Server) UseSSL() bool { return false }
func (s *Server) Address() string { return fmt.Sprintf("%s:%d", bridge.Host, s.port) }
func (s *Server) TLSConfig() *tls.Config { return s.server.TLSConfig }
func (s *Server) HandlePanic() { s.panicHandler.HandlePanic() }
l.WithError(err).Error("IMAP listener failed")
s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
return
}
func (s *Server) DebugServer() bool { return s.debugServer }
func (s *Server) DebugClient() bool { return s.debugClient }
err = s.server.Serve(&connListener{
Listener: listener,
server: s,
userAgent: s.userAgent,
})
// Serve returns error every time, even after closing the server.
// User shouldn't be notified about error if server shouldn't be running,
// but it should in case it was not closed by `s.Close()`.
if err != nil && s.IsRunning() {
s.isRunning.Store(false)
l.WithError(err).Error("IMAP server failed")
s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
return
}
defer s.server.Close() //nolint[errcheck]
func (s *Server) SetLoggers(localDebug, remoteDebug io.Writer) {
s.server.Debug = imap.NewDebugWriter(localDebug, remoteDebug)
l.Info("IMAP server stopped")
}
// Stops the server.
func (s *imapServer) Close() {
if !s.IsRunning() {
return
}
s.isRunning.Store(false)
log.Info("Closing IMAP server")
if err := s.server.Close(); err != nil {
log.WithError(err).Error("Failed to close the connection")
if !s.userAgent.HasClient() {
s.userAgent.SetClient("UnknownClient", "0.0.1")
}
}
func (s *imapServer) DisconnectUser(address string) {
func (s *Server) DisconnectUser(address string) {
log.Info("Disconnecting all open IMAP connections for ", address)
s.server.ForEachConn(func(conn imapserver.Conn) {
connUser := conn.Context().User
@ -195,60 +156,5 @@ func (s *imapServer) DisconnectUser(address string) {
})
}
// connListener sets debug loggers on server containing fields with local
// and remote addresses right after new connection is accepted.
type connListener struct {
net.Listener
server *imapServer
userAgent *useragent.UserAgent
}
func (l *connListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err == nil && (l.server.debugServer || l.server.debugClient) {
debugLog := log
if addr := conn.LocalAddr(); addr != nil {
debugLog = debugLog.WithField("loc", addr.String())
}
if addr := conn.RemoteAddr(); addr != nil {
debugLog = debugLog.WithField("rem", addr.String())
}
var localDebug, remoteDebug io.Writer
if l.server.debugServer {
localDebug = debugLog.WithField("pkg", "imap/server").WriterLevel(logrus.DebugLevel)
}
if l.server.debugClient {
remoteDebug = debugLog.WithField("pkg", "imap/client").WriterLevel(logrus.DebugLevel)
}
l.server.server.Debug = imap.NewDebugWriter(localDebug, remoteDebug)
}
if !l.userAgent.HasClient() {
l.userAgent.SetClient("UnknownClient", "0.0.1")
}
return conn, err
}
// serverErrorLogger implements go-imap/logger interface.
type serverErrorLogger struct {
tag string
}
func newServerErrorLogger(tag string) *serverErrorLogger {
return &serverErrorLogger{tag}
}
func (s *serverErrorLogger) Printf(format string, args ...interface{}) {
err := fmt.Sprintf(format, args...)
log.WithField("pkg", s.tag).Error(err)
}
func (s *serverErrorLogger) Println(args ...interface{}) {
err := fmt.Sprintln(args...)
log.WithField("pkg", s.tag).Error(err)
}
func (s *Server) Serve(listener net.Listener) error { return s.server.Serve(listener) }
func (s *Server) StopServe() error { return s.server.Close() }

View File

@ -1,52 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package imap
import (
"fmt"
"testing"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/internal/serverutil/mocks"
imapserver "github.com/emersion/go-imap/server"
"github.com/stretchr/testify/require"
)
func TestIMAPServerTurnOffAndOnAgain(t *testing.T) {
r := require.New(t)
ts := mocks.NewTestServer(12345)
server := imapserver.New(nil)
server.Addr = fmt.Sprintf("%v:%v", bridge.Host, ts.WantPort)
s := &imapServer{
panicHandler: ts.PanicHandler,
server: server,
port: ts.WantPort,
eventListener: ts.EventListener,
userAgent: useragent.New(),
}
s.isRunning.Store(false)
r.True(ts.IsPortFree())
go s.ListenAndServe()
ts.RunServerTests(r)
}

View File

@ -0,0 +1,117 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package serverutil
import (
"crypto/tls"
"fmt"
"net"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/sirupsen/logrus"
)
// Controller will make sure that server is listening and serving and if needed
// users are disconnected.
type Controller interface {
ListenAndServe()
Close()
}
// NewController return simple server controller.
func NewController(s Server, l listener.Listener) Controller {
log := logrus.WithField("pkg", "serverutil").WithField("protocol", s.Protocol())
c := &controller{
server: s,
signals: l,
log: log,
closeDisconnectUsers: make(chan void),
}
if s.DebugServer() {
fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
log.Warning("================================================")
log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
log.Warning("================================================")
}
return c
}
type void struct{}
type controller struct {
server Server
signals listener.Listener
log *logrus.Entry
closeDisconnectUsers chan void
}
func (c *controller) Close() {
c.closeDisconnectUsers <- void{}
if err := c.server.StopServe(); err != nil {
c.log.WithError(err).Error("Issue when closing server")
}
}
// ListenAndServe starts the server and keeps it on based on internet
// availability. It also monitors and disconnect users if requested.
func (c *controller) ListenAndServe() {
go monitorDisconnectedUsers(c.server, c.signals, c.closeDisconnectUsers)
defer c.server.HandlePanic()
l := c.log.WithField("useSSL", c.server.UseSSL()).
WithField("address", c.server.Address())
var listener net.Listener
var err error
if c.server.UseSSL() {
listener, err = tls.Listen("tcp", c.server.Address(), c.server.TLSConfig())
} else {
listener, err = net.Listen("tcp", c.server.Address())
}
if err != nil {
l.WithError(err).Error("Cannot start listner.")
c.signals.Emit(events.ErrorEvent, string(c.server.Protocol())+" failed: "+err.Error())
return
}
// When starting the Bridge, we don't want to retry to notify user
// quickly about the issue. Very probably retry will not help anyway.
l.Info("Starting server")
err = c.server.Serve(&connListener{listener, c.server})
l.WithError(err).Debug("GoSMTP not serving")
}
func monitorDisconnectedUsers(s Server, l listener.Listener, done <-chan void) {
ch := make(chan string)
l.Add(events.CloseConnectionEvent, ch)
for {
select {
case <-done:
return
case address := <-ch:
s.DisconnectUser(address)
}
}
}

View File

@ -15,29 +15,25 @@
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package smtp
package serverutil
import (
"testing"
"github.com/ProtonMail/proton-bridge/internal/serverutil/mocks"
"github.com/stretchr/testify/require"
"github.com/sirupsen/logrus"
)
func TestSMTPServerTurnOffAndOnAgain(t *testing.T) {
r := require.New(t)
ts := mocks.NewTestServer(12342)
s := &Server{
panicHandler: ts.PanicHandler,
port: ts.WantPort,
eventListener: ts.EventListener,
}
s.isRunning.Store(false)
r.True(ts.IsPortFree())
go s.ListenAndServe()
ts.RunServerTests(r)
// ServerErrorLogger implements go-imap/logger interface.
type ServerErrorLogger struct {
l *logrus.Entry
}
func NewServerErrorLogger(protocol Protocol) *ServerErrorLogger {
return &ServerErrorLogger{l: logrus.WithField("protocol", protocol)}
}
func (s *ServerErrorLogger) Printf(format string, args ...interface{}) {
s.l.Errorf(format, args...)
}
func (s *ServerErrorLogger) Println(args ...interface{}) {
s.l.Errorln(args...)
}

View File

@ -0,0 +1,59 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package serverutil
import (
"io"
"net"
"github.com/sirupsen/logrus"
)
// connListener sets debug loggers on server containing fields with local
// and remote addresses right after new connection is accepted.
type connListener struct {
net.Listener
server Server
}
func (l *connListener) Accept() (net.Conn, error) {
conn, err := l.Listener.Accept()
if err == nil && (l.server.DebugServer() || l.server.DebugClient()) {
debugLog := logrus.WithField("pkg", l.server.Protocol())
if addr := conn.LocalAddr(); addr != nil {
debugLog = debugLog.WithField("loc", addr.String())
}
if addr := conn.RemoteAddr(); addr != nil {
debugLog = debugLog.WithField("rem", addr.String())
}
var localDebug, remoteDebug io.Writer
if l.server.DebugServer() {
localDebug = debugLog.WithField("comm", "server").WriterLevel(logrus.DebugLevel)
}
if l.server.DebugClient() {
remoteDebug = debugLog.WithField("comm", "client").WriterLevel(logrus.DebugLevel)
}
l.server.SetLoggers(localDebug, remoteDebug)
}
return conn, err
}

View File

@ -1,150 +0,0 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package mocks
import (
"fmt"
"net/http"
"sync/atomic"
"time"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/ports"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
)
type DummyPanicHandler struct{}
func (ph *DummyPanicHandler) HandlePanic() {}
type TestServer struct {
PanicHandler *DummyPanicHandler
WantPort int
EventListener listener.Listener
isRunning atomic.Value
srv *http.Server
}
func NewTestServer(port int) *TestServer {
s := &TestServer{
PanicHandler: &DummyPanicHandler{},
EventListener: listener.New(),
WantPort: ports.FindFreePortFrom(port),
}
s.isRunning.Store(false)
return s
}
func (s *TestServer) IsPortFree() bool {
return true
}
func (s *TestServer) IsPortOccupied() bool {
return true
}
func (s *TestServer) Emit(event string, try, iEvt int) int {
// Emit has separate go routine so it is needed to wait here to
// prevent event race condition.
time.Sleep(100 * time.Millisecond)
iEvt++
s.EventListener.Emit(event, fmt.Sprintf("%d:%d", try, iEvt))
return iEvt
}
func (s *TestServer) HandlePanic() {}
func (s *TestServer) DisconnectUser(string) {}
func (s *TestServer) Port() int { return s.WantPort }
func (s *TestServer) IsRunning() bool { return s.isRunning.Load().(bool) }
func (s *TestServer) ListenRetryAndServe(retries int, retryAfter time.Duration) {
if s.isRunning.Load().(bool) {
return
}
s.isRunning.Store(true)
// There can be delay when starting server
time.Sleep(200 * time.Millisecond)
s.srv = &http.Server{
Addr: fmt.Sprintf("127.0.0.1:%d", s.WantPort),
}
err := s.srv.ListenAndServe()
if err != nil {
s.isRunning.Store(false)
if retries > 0 {
time.Sleep(retryAfter)
s.ListenRetryAndServe(retries-1, retryAfter)
}
}
if s.IsRunning() {
logrus.Error("Not serving but isRunning is true")
s.isRunning.Store(false)
}
}
func (s *TestServer) Close() {
if !s.isRunning.Load().(bool) {
return
}
s.isRunning.Store(false)
// There can be delay when stopping server
time.Sleep(200 * time.Millisecond)
if err := s.srv.Close(); err != nil {
logrus.WithError(err).Error("Closing dummy server")
}
}
func (s *TestServer) RunServerTests(r *require.Assertions) {
// NOTE About choosing tick durations:
// In order to avoid ticks to synchronise and cause occasional race
// condition we choose the tick duration around 100ms but not exactly
// to have large common multiple.
r.Eventually(s.IsPortOccupied, 5*time.Second, 97*time.Millisecond)
// There was an issue where second time we were not able to restore server.
for try := 0; try < 3; try++ {
i := s.Emit(events.InternetOffEvent, try, 0)
r.Eventually(s.IsPortFree, 10*time.Second, 99*time.Millisecond, "signal off try %d : %d", try, i)
i = s.Emit(events.InternetOnEvent, try, i)
i = s.Emit(events.InternetOffEvent, try, i)
i = s.Emit(events.InternetOffEvent, try, i)
i = s.Emit(events.InternetOffEvent, try, i)
i = s.Emit(events.InternetOffEvent, try, i)
i = s.Emit(events.InternetOnEvent, try, i)
i = s.Emit(events.InternetOnEvent, try, i)
i = s.Emit(events.InternetOffEvent, try, i)
// Wait a bit longer if needed to process all events
r.Eventually(s.IsPortFree, 20*time.Second, 101*time.Millisecond, "again signal off number %d : %d", try, i)
i = s.Emit(events.InternetOnEvent, try, i)
r.Eventually(s.IsPortOccupied, 10*time.Second, 103*time.Millisecond, "signal on number %d : %d", try, i)
i = s.Emit(events.InternetOffEvent, try, i)
i = s.Emit(events.InternetOnEvent, try, i)
i = s.Emit(events.InternetOnEvent, try, i)
r.Eventually(s.IsPortOccupied, 10*time.Second, 107*time.Millisecond, "again signal on number %d : %d", try, i)
}
}

View File

@ -17,19 +17,10 @@
package serverutil
import (
"testing"
type Protocol string
"github.com/ProtonMail/proton-bridge/internal/serverutil/mocks"
"github.com/stretchr/testify/require"
const (
HTTP = Protocol("HTTP")
IMAP = Protocol("IMAP")
SMTP = Protocol("SMTP")
)
func TestServerTurnOffAndOnAgain(t *testing.T) {
r := require.New(t)
s := mocks.NewTestServer(12321)
r.True(s.IsPortFree())
go ListenAndServe(s, s.EventListener)
s.RunServerTests(r)
}

View File

@ -18,115 +18,24 @@
package serverutil
import (
"time"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/ports"
"github.com/sirupsen/logrus"
"crypto/tls"
"io"
"net"
)
// Server which can handle disconnected users and lost internet connection.
// Server can handle disconnected users.
type Server interface {
Protocol() Protocol
UseSSL() bool
Address() string
TLSConfig() *tls.Config
DebugServer() bool
DebugClient() bool
SetLoggers(localDebug, remoteDebug io.Writer)
HandlePanic()
DisconnectUser(string)
ListenRetryAndServe(int, time.Duration)
Close()
Port() int
IsRunning() bool
}
func monitorDisconnectedUsers(s Server, l listener.Listener) {
ch := make(chan string)
l.Add(events.CloseConnectionEvent, ch)
for address := range ch {
s.DisconnectUser(address)
}
}
func redirectInternetEventsToOneChannel(l listener.Listener) (isInternetOn chan bool) {
on := make(chan string)
l.Add(events.InternetOnEvent, on)
off := make(chan string)
l.Add(events.InternetOffEvent, off)
// Redirect two channels into one. When select was used the algorithm
// first read all on channels and then read all off channels.
isInternetOn = make(chan bool, 20)
go func() {
for {
logrus.WithField("try", <-on).Trace("Internet ON")
isInternetOn <- true
}
}()
go func() {
for {
logrus.WithField("try", <-off).Trace("Internet OFF")
isInternetOn <- false
}
}()
return
}
const (
recheckPortAfter = 50 * time.Millisecond
stopPortChecksAfter = 15 * time.Second
retryListenerAfter = 5 * time.Second
)
func monitorInternetConnection(s Server, l listener.Listener) {
isInternetOn := redirectInternetEventsToOneChannel(l)
for {
var expectedIsPortFree bool
if <-isInternetOn {
if s.IsRunning() {
continue
}
go func() {
defer s.HandlePanic()
// We had issues on Mac that from time to time something
// blocked our port for a bit after we closed IMAP server
// due to connection issues.
// Restart always helped, so we do retry to not bother user.
s.ListenRetryAndServe(10, retryListenerAfter)
}()
expectedIsPortFree = false
} else {
if !s.IsRunning() {
continue
}
s.Close()
expectedIsPortFree = true
}
start := time.Now()
for {
isPortFree := ports.IsPortFree(s.Port())
logrus.
WithField("port", s.Port()).
WithField("isFree", isPortFree).
WithField("wantToBeFree", expectedIsPortFree).
Trace("Check port")
if isPortFree == expectedIsPortFree {
break
}
// Safety stop if something went wrong.
if time.Since(start) > stopPortChecksAfter {
logrus.WithField("expectedIsPortFree", expectedIsPortFree).Warn("Server start/stop check timeouted")
break
}
time.Sleep(recheckPortAfter)
}
}
}
// ListenAndServe starts the server and keeps it on based on internet
// availability. It also monitors and disconnect users if requested.
func ListenAndServe(s Server, l listener.Listener) {
go monitorDisconnectedUsers(s, l)
go monitorInternetConnection(s, l)
// When starting the Bridge, we don't want to retry to notify user
// quickly about the issue. Very probably retry will not help anyway.
s.ListenRetryAndServe(0, 0)
Serve(net.Listener) error
StopServe() error
}

View File

@ -0,0 +1,153 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package test
import (
"net/http"
"testing"
"time"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/serverutil"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/stretchr/testify/require"
)
func setup(t *testing.T) (*require.Assertions, *testServer, listener.Listener, serverutil.Controller) {
r := require.New(t)
s := newTestServer()
l := listener.New()
c := serverutil.NewController(s, l)
return r, s, l, c
}
func TestControllerListernServeClose(t *testing.T) {
r, s, l, c := setup(t)
errorCh := l.ProvideChannel(events.ErrorEvent)
r.True(s.portIsFree())
go c.ListenAndServe()
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
r.NoError(s.ping())
r.Nil(s.localDebug)
r.Nil(s.remoteDebug)
c.Close()
r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond)
select {
case msg := <-errorCh:
r.Fail("Expected no error but have %q", msg)
case <-time.Tick(100 * time.Millisecond):
break
}
}
func TestControllerFailOnBusyPort(t *testing.T) {
r, s, l, c := setup(t)
ocupator := http.Server{Addr: s.Address()}
defer ocupator.Close() //nolint[errcheck]
go ocupator.ListenAndServe() //nolint[errcheck]
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
errorCh := l.ProvideChannel(events.ErrorEvent)
go c.ListenAndServe()
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
select {
case <-errorCh:
break
case <-time.Tick(time.Second):
r.Fail("Expected error but have none.")
}
}
func TestControllerCallDisconnectUser(t *testing.T) {
r, s, l, c := setup(t)
go c.ListenAndServe()
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
r.NoError(s.ping())
l.Emit(events.CloseConnectionEvent, "")
r.Eventually(func() bool { return s.calledDisconnected == 1 }, time.Second, 50*time.Millisecond)
c.Close()
r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond)
l.Emit(events.CloseConnectionEvent, "")
r.Equal(1, s.calledDisconnected)
}
func TestDebugClient(t *testing.T) {
r, s, _, c := setup(t)
s.debugServer = false
s.debugClient = true
go c.ListenAndServe()
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
r.NoError(s.ping())
r.Nil(s.localDebug)
r.NotNil(s.remoteDebug)
c.Close()
r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond)
}
func TestDebugServer(t *testing.T) {
r, s, _, c := setup(t)
s.debugServer = true
s.debugClient = false
go c.ListenAndServe()
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
r.NoError(s.ping())
r.NotNil(s.localDebug)
r.Nil(s.remoteDebug)
c.Close()
r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond)
}
func TestDebugBoth(t *testing.T) {
r, s, _, c := setup(t)
s.debugServer = true
s.debugClient = true
go c.ListenAndServe()
r.Eventually(s.portIsOccupied, time.Second, 50*time.Millisecond)
r.NoError(s.ping())
r.NotNil(s.localDebug)
r.NotNil(s.remoteDebug)
c.Close()
r.Eventually(s.portIsFree, time.Second, 50*time.Millisecond)
}

View File

@ -0,0 +1,88 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package test
import (
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"github.com/ProtonMail/proton-bridge/internal/serverutil"
"github.com/ProtonMail/proton-bridge/pkg/ports"
)
func newTestServer() *testServer {
return &testServer{port: 11188}
}
type testServer struct {
http http.Server
useSSL,
debugServer,
debugClient bool
calledDisconnected int
port int
tls *tls.Config
localDebug, remoteDebug io.Writer
}
func (*testServer) Protocol() serverutil.Protocol { return serverutil.HTTP }
func (s *testServer) UseSSL() bool { return s.useSSL }
func (s *testServer) Address() string { return fmt.Sprintf("127.0.0.1:%d", s.port) }
func (s *testServer) TLSConfig() *tls.Config { return s.tls }
func (s *testServer) HandlePanic() {}
func (s *testServer) DebugServer() bool { return s.debugServer }
func (s *testServer) DebugClient() bool { return s.debugClient }
func (s *testServer) SetLoggers(localDebug, remoteDebug io.Writer) {
s.localDebug = localDebug
s.remoteDebug = remoteDebug
}
func (s *testServer) DisconnectUser(string) {
s.calledDisconnected++
}
func (s *testServer) Serve(l net.Listener) error {
return s.http.Serve(l)
}
func (s *testServer) StopServe() error { return s.http.Close() }
func (s *testServer) portIsFree() bool {
return ports.IsPortFree(s.port)
}
func (s *testServer) portIsOccupied() bool {
return !ports.IsPortFree(s.port)
}
func (s *testServer) ping() error {
client := &http.Client{}
resp, err := client.Get("http://" + s.Address() + "/ping")
if err != nil {
return err
}
return resp.Body.Close()
}

View File

@ -48,7 +48,7 @@ func dumpMessageData(b []byte, subject string) {
}
if err := ioutil.WriteFile(
filepath.Join(path, fmt.Sprintf("%v-%v.eml", subject, time.Now().Format(time.RFC3339Nano))),
filepath.Join(path, fmt.Sprintf("%v-%v.eml", subject, time.Now().Unix())),
b,
0600,
); err != nil {

View File

@ -20,72 +20,60 @@ package smtp
import (
"crypto/tls"
"fmt"
"io"
"net"
"sync/atomic"
"time"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/serverutil"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/emersion/go-sasl"
goSMTP "github.com/emersion/go-smtp"
"github.com/sirupsen/logrus"
)
// Server is Bridge SMTP server implementation.
type Server struct {
panicHandler panicHandler
backend goSMTP.Backend
server *goSMTP.Server
eventListener listener.Listener
debug bool
useSSL bool
port int
tls *tls.Config
isRunning atomic.Value
server *goSMTP.Server
controller serverutil.Controller
}
// NewSMTPServer returns an SMTP server configured with the given options.
func NewSMTPServer(panicHandler panicHandler, debug bool, port int, useSSL bool, tls *tls.Config, smtpBackend goSMTP.Backend, eventListener listener.Listener) *Server {
if debug {
fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
log.Warning("================================================")
log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
log.Warning("================================================")
}
func NewSMTPServer(
panicHandler panicHandler,
debug bool, port int, useSSL bool,
tls *tls.Config,
smtpBackend goSMTP.Backend,
eventListener listener.Listener,
) *Server {
server := &Server{
panicHandler: panicHandler,
backend: smtpBackend,
eventListener: eventListener,
debug: debug,
useSSL: useSSL,
port: port,
tls: tls,
}
server.isRunning.Store(false)
server.server = newGoSMTPServer(server)
server.controller = serverutil.NewController(server, eventListener)
return server
}
func (s *Server) HandlePanic() { s.panicHandler.HandlePanic() }
func (s *Server) IsRunning() bool { return s.isRunning.Load().(bool) }
func (s *Server) Port() int { return s.port }
func newGoSMTPServer(debug bool, smtpBackend goSMTP.Backend, port int, tls *tls.Config) *goSMTP.Server {
newSMTP := goSMTP.NewServer(smtpBackend)
newSMTP.Addr = fmt.Sprintf("%v:%v", bridge.Host, port)
newSMTP.TLSConfig = tls
func newGoSMTPServer(s *Server) *goSMTP.Server {
newSMTP := goSMTP.NewServer(s.backend)
newSMTP.Addr = s.Address()
newSMTP.TLSConfig = s.tls
newSMTP.Domain = bridge.Host
newSMTP.ErrorLog = serverutil.NewServerErrorLogger(serverutil.SMTP)
newSMTP.AllowInsecureAuth = true
newSMTP.MaxLineLength = 1 << 16
if debug {
newSMTP.Debug = logrus.
WithField("pkg", "smtp/server").
WriterLevel(logrus.DebugLevel)
}
newSMTP.EnableAuth(sasl.Login, func(conn *goSMTP.Conn) sasl.Server {
return sasl.NewLoginServer(func(address, password string) error {
user, err := conn.Server().Backend.Login(nil, address, password)
@ -100,80 +88,24 @@ func newGoSMTPServer(debug bool, smtpBackend goSMTP.Backend, port int, tls *tls.
return newSMTP
}
// ListenAndServe starts the server and keeps it on based on internet
// availability.
func (s *Server) ListenAndServe() {
serverutil.ListenAndServe(s, s.eventListener)
}
// ListenAndServe will run server and all monitors.
func (s *Server) ListenAndServe() { s.controller.ListenAndServe() }
func (s *Server) ListenRetryAndServe(retries int, retryAfter time.Duration) {
if s.IsRunning() {
return
}
s.isRunning.Store(true)
// Close turns off server and monitors.
func (s *Server) Close() { s.controller.Close() }
s.server = newGoSMTPServer(s.debug, s.backend, s.port, s.tls)
// Implements servertutil.Server interface.
l := log.WithField("useSSL", s.useSSL).WithField("address", s.server.Addr)
l.Info("SMTP server is starting")
func (Server) Protocol() serverutil.Protocol { return serverutil.SMTP }
func (s *Server) UseSSL() bool { return s.useSSL }
func (s *Server) Address() string { return fmt.Sprintf("%s:%d", bridge.Host, s.port) }
func (s *Server) TLSConfig() *tls.Config { return s.tls }
func (s *Server) HandlePanic() { s.panicHandler.HandlePanic() }
var listener net.Listener
var err error
if s.useSSL {
listener, err = tls.Listen("tcp", s.server.Addr, s.server.TLSConfig)
} else {
listener, err = net.Listen("tcp", s.server.Addr)
}
l.WithError(err).Debug("Listener for SMTP created")
if err != nil {
s.isRunning.Store(false)
if retries > 0 {
l.WithError(err).WithField("retries", retries).Warn("SMTP listener failed")
time.Sleep(retryAfter)
s.ListenRetryAndServe(retries-1, retryAfter)
return
}
func (s *Server) DebugServer() bool { return s.debug }
func (s *Server) DebugClient() bool { return s.debug }
l.WithError(err).Error("SMTP listener failed")
s.eventListener.Emit(events.ErrorEvent, "SMTP failed: "+err.Error())
return
}
err = s.server.Serve(listener)
l.WithError(err).Debug("GoSMTP not serving")
// Serve returns error every time, even after closing the server.
// User shouldn't be notified about error if server shouldn't be running,
// but it should in case it was not closed by `s.Close()`.
if err != nil && s.IsRunning() {
s.isRunning.Store(false)
l.WithError(err).Error("SMTP server failed")
s.eventListener.Emit(events.ErrorEvent, "SMTP failed: "+err.Error())
return
}
defer func() {
// Go SMTP server instance can be closed only once. Otherwise
// it returns an error. The error is not export therefore we
// will check the string value.
err := s.server.Close()
if err == nil || err.Error() != "smtp: server already closed" {
l.WithError(err).Warn("Server was not closed")
}
}()
l.Info("SMTP server closed")
}
// Close stops the server.
func (s *Server) Close() {
if !s.IsRunning() {
return
}
s.isRunning.Store(false)
if err := s.server.Close(); err != nil {
log.WithError(err).Error("Cannot close the server")
}
}
func (s *Server) SetLoggers(localDebug, remoteDebug io.Writer) { s.server.Debug = localDebug }
func (s *Server) DisconnectUser(address string) {
log.Info("Disconnecting all open SMTP connections for ", address)
@ -186,3 +118,6 @@ func (s *Server) DisconnectUser(address string) {
}
})
}
func (s *Server) Serve(l net.Listener) error { return s.server.Serve(l) }
func (s *Server) StopServe() error { return s.server.Close() }

View File

@ -362,7 +362,8 @@ func (su *smtpUser) Send(returnPath string, to []string, messageReader io.Reader
req := pmapi.NewSendMessageReq(kr, mimeBody, plainBody, richBody, attkeys)
containsUnencryptedRecipients := false
for _, email := range to {
for _, recipient := range message.Recipients() {
email := recipient.Address
if !looksLikeEmail(email) {
return errors.New(`"` + email + `" is not a valid recipient.`)
}

View File

@ -12,89 +12,89 @@ import (
gomock "github.com/golang/mock/gomock"
)
// MockPanicHandler is a mock of PanicHandler interface
// MockPanicHandler is a mock of PanicHandler interface.
type MockPanicHandler struct {
ctrl *gomock.Controller
recorder *MockPanicHandlerMockRecorder
}
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler.
type MockPanicHandlerMockRecorder struct {
mock *MockPanicHandler
}
// NewMockPanicHandler creates a new mock instance
// NewMockPanicHandler creates a new mock instance.
func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler {
mock := &MockPanicHandler{ctrl: ctrl}
mock.recorder = &MockPanicHandlerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder {
return m.recorder
}
// HandlePanic mocks base method
// HandlePanic mocks base method.
func (m *MockPanicHandler) HandlePanic() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "HandlePanic")
}
// HandlePanic indicates an expected call of HandlePanic
// HandlePanic indicates an expected call of HandlePanic.
func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic))
}
// MockBridgeUser is a mock of BridgeUser interface
// MockBridgeUser is a mock of BridgeUser interface.
type MockBridgeUser struct {
ctrl *gomock.Controller
recorder *MockBridgeUserMockRecorder
}
// MockBridgeUserMockRecorder is the mock recorder for MockBridgeUser
// MockBridgeUserMockRecorder is the mock recorder for MockBridgeUser.
type MockBridgeUserMockRecorder struct {
mock *MockBridgeUser
}
// NewMockBridgeUser creates a new mock instance
// NewMockBridgeUser creates a new mock instance.
func NewMockBridgeUser(ctrl *gomock.Controller) *MockBridgeUser {
mock := &MockBridgeUser{ctrl: ctrl}
mock.recorder = &MockBridgeUserMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockBridgeUser) EXPECT() *MockBridgeUserMockRecorder {
return m.recorder
}
// CloseAllConnections mocks base method
// CloseAllConnections mocks base method.
func (m *MockBridgeUser) CloseAllConnections() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "CloseAllConnections")
}
// CloseAllConnections indicates an expected call of CloseAllConnections
// CloseAllConnections indicates an expected call of CloseAllConnections.
func (mr *MockBridgeUserMockRecorder) CloseAllConnections() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseAllConnections", reflect.TypeOf((*MockBridgeUser)(nil).CloseAllConnections))
}
// CloseConnection mocks base method
// CloseConnection mocks base method.
func (m *MockBridgeUser) CloseConnection(arg0 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "CloseConnection", arg0)
}
// CloseConnection indicates an expected call of CloseConnection
// CloseConnection indicates an expected call of CloseConnection.
func (mr *MockBridgeUserMockRecorder) CloseConnection(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseConnection", reflect.TypeOf((*MockBridgeUser)(nil).CloseConnection), arg0)
}
// GetAddressID mocks base method
// GetAddressID mocks base method.
func (m *MockBridgeUser) GetAddressID(arg0 string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAddressID", arg0)
@ -103,13 +103,13 @@ func (m *MockBridgeUser) GetAddressID(arg0 string) (string, error) {
return ret0, ret1
}
// GetAddressID indicates an expected call of GetAddressID
// GetAddressID indicates an expected call of GetAddressID.
func (mr *MockBridgeUserMockRecorder) GetAddressID(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAddressID", reflect.TypeOf((*MockBridgeUser)(nil).GetAddressID), arg0)
}
// GetClient mocks base method
// GetClient mocks base method.
func (m *MockBridgeUser) GetClient() pmapi.Client {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetClient")
@ -117,13 +117,13 @@ func (m *MockBridgeUser) GetClient() pmapi.Client {
return ret0
}
// GetClient indicates an expected call of GetClient
// GetClient indicates an expected call of GetClient.
func (mr *MockBridgeUserMockRecorder) GetClient() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClient", reflect.TypeOf((*MockBridgeUser)(nil).GetClient))
}
// GetPrimaryAddress mocks base method
// GetPrimaryAddress mocks base method.
func (m *MockBridgeUser) GetPrimaryAddress() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPrimaryAddress")
@ -131,13 +131,13 @@ func (m *MockBridgeUser) GetPrimaryAddress() string {
return ret0
}
// GetPrimaryAddress indicates an expected call of GetPrimaryAddress
// GetPrimaryAddress indicates an expected call of GetPrimaryAddress.
func (mr *MockBridgeUserMockRecorder) GetPrimaryAddress() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPrimaryAddress", reflect.TypeOf((*MockBridgeUser)(nil).GetPrimaryAddress))
}
// GetStoreAddresses mocks base method
// GetStoreAddresses mocks base method.
func (m *MockBridgeUser) GetStoreAddresses() []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetStoreAddresses")
@ -145,13 +145,13 @@ func (m *MockBridgeUser) GetStoreAddresses() []string {
return ret0
}
// GetStoreAddresses indicates an expected call of GetStoreAddresses
// GetStoreAddresses indicates an expected call of GetStoreAddresses.
func (mr *MockBridgeUserMockRecorder) GetStoreAddresses() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStoreAddresses", reflect.TypeOf((*MockBridgeUser)(nil).GetStoreAddresses))
}
// ID mocks base method
// ID mocks base method.
func (m *MockBridgeUser) ID() string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ID")
@ -159,13 +159,13 @@ func (m *MockBridgeUser) ID() string {
return ret0
}
// ID indicates an expected call of ID
// ID indicates an expected call of ID.
func (mr *MockBridgeUserMockRecorder) ID() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockBridgeUser)(nil).ID))
}
// IsCombinedAddressMode mocks base method
// IsCombinedAddressMode mocks base method.
func (m *MockBridgeUser) IsCombinedAddressMode() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsCombinedAddressMode")
@ -173,13 +173,13 @@ func (m *MockBridgeUser) IsCombinedAddressMode() bool {
return ret0
}
// IsCombinedAddressMode indicates an expected call of IsCombinedAddressMode
// IsCombinedAddressMode indicates an expected call of IsCombinedAddressMode.
func (mr *MockBridgeUserMockRecorder) IsCombinedAddressMode() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsCombinedAddressMode", reflect.TypeOf((*MockBridgeUser)(nil).IsCombinedAddressMode))
}
// IsConnected mocks base method
// IsConnected mocks base method.
func (m *MockBridgeUser) IsConnected() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsConnected")
@ -187,13 +187,13 @@ func (m *MockBridgeUser) IsConnected() bool {
return ret0
}
// IsConnected indicates an expected call of IsConnected
// IsConnected indicates an expected call of IsConnected.
func (mr *MockBridgeUserMockRecorder) IsConnected() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsConnected", reflect.TypeOf((*MockBridgeUser)(nil).IsConnected))
}
// Logout mocks base method
// Logout mocks base method.
func (m *MockBridgeUser) Logout() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Logout")
@ -201,13 +201,13 @@ func (m *MockBridgeUser) Logout() error {
return ret0
}
// Logout indicates an expected call of Logout
// Logout indicates an expected call of Logout.
func (mr *MockBridgeUserMockRecorder) Logout() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockBridgeUser)(nil).Logout))
}
// UpdateUser mocks base method
// UpdateUser mocks base method.
func (m *MockBridgeUser) UpdateUser(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateUser", arg0)
@ -215,36 +215,36 @@ func (m *MockBridgeUser) UpdateUser(arg0 context.Context) error {
return ret0
}
// UpdateUser indicates an expected call of UpdateUser
// UpdateUser indicates an expected call of UpdateUser.
func (mr *MockBridgeUserMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockBridgeUser)(nil).UpdateUser), arg0)
}
// MockChangeNotifier is a mock of ChangeNotifier interface
// MockChangeNotifier is a mock of ChangeNotifier interface.
type MockChangeNotifier struct {
ctrl *gomock.Controller
recorder *MockChangeNotifierMockRecorder
}
// MockChangeNotifierMockRecorder is the mock recorder for MockChangeNotifier
// MockChangeNotifierMockRecorder is the mock recorder for MockChangeNotifier.
type MockChangeNotifierMockRecorder struct {
mock *MockChangeNotifier
}
// NewMockChangeNotifier creates a new mock instance
// NewMockChangeNotifier creates a new mock instance.
func NewMockChangeNotifier(ctrl *gomock.Controller) *MockChangeNotifier {
mock := &MockChangeNotifier{ctrl: ctrl}
mock.recorder = &MockChangeNotifierMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockChangeNotifier) EXPECT() *MockChangeNotifierMockRecorder {
return m.recorder
}
// CanDelete mocks base method
// CanDelete mocks base method.
func (m *MockChangeNotifier) CanDelete(arg0 string) (bool, func()) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CanDelete", arg0)
@ -253,67 +253,67 @@ func (m *MockChangeNotifier) CanDelete(arg0 string) (bool, func()) {
return ret0, ret1
}
// CanDelete indicates an expected call of CanDelete
// CanDelete indicates an expected call of CanDelete.
func (mr *MockChangeNotifierMockRecorder) CanDelete(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CanDelete", reflect.TypeOf((*MockChangeNotifier)(nil).CanDelete), arg0)
}
// DeleteMessage mocks base method
// DeleteMessage mocks base method.
func (m *MockChangeNotifier) DeleteMessage(arg0, arg1 string, arg2 uint32) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "DeleteMessage", arg0, arg1, arg2)
}
// DeleteMessage indicates an expected call of DeleteMessage
// DeleteMessage indicates an expected call of DeleteMessage.
func (mr *MockChangeNotifierMockRecorder) DeleteMessage(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessage", reflect.TypeOf((*MockChangeNotifier)(nil).DeleteMessage), arg0, arg1, arg2)
}
// MailboxCreated mocks base method
// MailboxCreated mocks base method.
func (m *MockChangeNotifier) MailboxCreated(arg0, arg1 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "MailboxCreated", arg0, arg1)
}
// MailboxCreated indicates an expected call of MailboxCreated
// MailboxCreated indicates an expected call of MailboxCreated.
func (mr *MockChangeNotifierMockRecorder) MailboxCreated(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MailboxCreated", reflect.TypeOf((*MockChangeNotifier)(nil).MailboxCreated), arg0, arg1)
}
// MailboxStatus mocks base method
// MailboxStatus mocks base method.
func (m *MockChangeNotifier) MailboxStatus(arg0, arg1 string, arg2, arg3, arg4 uint32) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "MailboxStatus", arg0, arg1, arg2, arg3, arg4)
}
// MailboxStatus indicates an expected call of MailboxStatus
// MailboxStatus indicates an expected call of MailboxStatus.
func (mr *MockChangeNotifierMockRecorder) MailboxStatus(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MailboxStatus", reflect.TypeOf((*MockChangeNotifier)(nil).MailboxStatus), arg0, arg1, arg2, arg3, arg4)
}
// Notice mocks base method
// Notice mocks base method.
func (m *MockChangeNotifier) Notice(arg0, arg1 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Notice", arg0, arg1)
}
// Notice indicates an expected call of Notice
// Notice indicates an expected call of Notice.
func (mr *MockChangeNotifierMockRecorder) Notice(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Notice", reflect.TypeOf((*MockChangeNotifier)(nil).Notice), arg0, arg1)
}
// UpdateMessage mocks base method
// UpdateMessage mocks base method.
func (m *MockChangeNotifier) UpdateMessage(arg0, arg1 string, arg2, arg3 uint32, arg4 *pmapi.Message, arg5 bool) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "UpdateMessage", arg0, arg1, arg2, arg3, arg4, arg5)
}
// UpdateMessage indicates an expected call of UpdateMessage
// UpdateMessage indicates an expected call of UpdateMessage.
func (mr *MockChangeNotifierMockRecorder) UpdateMessage(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMessage", reflect.TypeOf((*MockChangeNotifier)(nil).UpdateMessage), arg0, arg1, arg2, arg3, arg4, arg5)

View File

@ -11,54 +11,54 @@ import (
gomock "github.com/golang/mock/gomock"
)
// MockListener is a mock of Listener interface
// MockListener is a mock of Listener interface.
type MockListener struct {
ctrl *gomock.Controller
recorder *MockListenerMockRecorder
}
// MockListenerMockRecorder is the mock recorder for MockListener
// MockListenerMockRecorder is the mock recorder for MockListener.
type MockListenerMockRecorder struct {
mock *MockListener
}
// NewMockListener creates a new mock instance
// NewMockListener creates a new mock instance.
func NewMockListener(ctrl *gomock.Controller) *MockListener {
mock := &MockListener{ctrl: ctrl}
mock.recorder = &MockListenerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockListener) EXPECT() *MockListenerMockRecorder {
return m.recorder
}
// Add mocks base method
// Add mocks base method.
func (m *MockListener) Add(arg0 string, arg1 chan<- string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Add", arg0, arg1)
}
// Add indicates an expected call of Add
// Add indicates an expected call of Add.
func (mr *MockListenerMockRecorder) Add(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockListener)(nil).Add), arg0, arg1)
}
// Emit mocks base method
// Emit mocks base method.
func (m *MockListener) Emit(arg0, arg1 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Emit", arg0, arg1)
}
// Emit indicates an expected call of Emit
// Emit indicates an expected call of Emit.
func (mr *MockListenerMockRecorder) Emit(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Emit", reflect.TypeOf((*MockListener)(nil).Emit), arg0, arg1)
}
// ProvideChannel mocks base method
// ProvideChannel mocks base method.
func (m *MockListener) ProvideChannel(arg0 string) <-chan string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ProvideChannel", arg0)
@ -66,55 +66,55 @@ func (m *MockListener) ProvideChannel(arg0 string) <-chan string {
return ret0
}
// ProvideChannel indicates an expected call of ProvideChannel
// ProvideChannel indicates an expected call of ProvideChannel.
func (mr *MockListenerMockRecorder) ProvideChannel(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProvideChannel", reflect.TypeOf((*MockListener)(nil).ProvideChannel), arg0)
}
// Remove mocks base method
// Remove mocks base method.
func (m *MockListener) Remove(arg0 string, arg1 chan<- string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Remove", arg0, arg1)
}
// Remove indicates an expected call of Remove
// Remove indicates an expected call of Remove.
func (mr *MockListenerMockRecorder) Remove(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockListener)(nil).Remove), arg0, arg1)
}
// RetryEmit mocks base method
// RetryEmit mocks base method.
func (m *MockListener) RetryEmit(arg0 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "RetryEmit", arg0)
}
// RetryEmit indicates an expected call of RetryEmit
// RetryEmit indicates an expected call of RetryEmit.
func (mr *MockListenerMockRecorder) RetryEmit(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryEmit", reflect.TypeOf((*MockListener)(nil).RetryEmit), arg0)
}
// SetBuffer mocks base method
// SetBuffer mocks base method.
func (m *MockListener) SetBuffer(arg0 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetBuffer", arg0)
}
// SetBuffer indicates an expected call of SetBuffer
// SetBuffer indicates an expected call of SetBuffer.
func (mr *MockListenerMockRecorder) SetBuffer(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBuffer", reflect.TypeOf((*MockListener)(nil).SetBuffer), arg0)
}
// SetLimit mocks base method
// SetLimit mocks base method.
func (m *MockListener) SetLimit(arg0 string, arg1 time.Duration) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetLimit", arg0, arg1)
}
// SetLimit indicates an expected call of SetLimit
// SetLimit indicates an expected call of SetLimit.
func (mr *MockListenerMockRecorder) SetLimit(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLimit", reflect.TypeOf((*MockListener)(nil).SetLimit), arg0, arg1)

View File

@ -12,65 +12,65 @@ import (
gomock "github.com/golang/mock/gomock"
)
// MockPanicHandler is a mock of PanicHandler interface
// MockPanicHandler is a mock of PanicHandler interface.
type MockPanicHandler struct {
ctrl *gomock.Controller
recorder *MockPanicHandlerMockRecorder
}
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler.
type MockPanicHandlerMockRecorder struct {
mock *MockPanicHandler
}
// NewMockPanicHandler creates a new mock instance
// NewMockPanicHandler creates a new mock instance.
func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler {
mock := &MockPanicHandler{ctrl: ctrl}
mock.recorder = &MockPanicHandlerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder {
return m.recorder
}
// HandlePanic mocks base method
// HandlePanic mocks base method.
func (m *MockPanicHandler) HandlePanic() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "HandlePanic")
}
// HandlePanic indicates an expected call of HandlePanic
// HandlePanic indicates an expected call of HandlePanic.
func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic))
}
// MockIMAPClientProvider is a mock of IMAPClientProvider interface
// MockIMAPClientProvider is a mock of IMAPClientProvider interface.
type MockIMAPClientProvider struct {
ctrl *gomock.Controller
recorder *MockIMAPClientProviderMockRecorder
}
// MockIMAPClientProviderMockRecorder is the mock recorder for MockIMAPClientProvider
// MockIMAPClientProviderMockRecorder is the mock recorder for MockIMAPClientProvider.
type MockIMAPClientProviderMockRecorder struct {
mock *MockIMAPClientProvider
}
// NewMockIMAPClientProvider creates a new mock instance
// NewMockIMAPClientProvider creates a new mock instance.
func NewMockIMAPClientProvider(ctrl *gomock.Controller) *MockIMAPClientProvider {
mock := &MockIMAPClientProvider{ctrl: ctrl}
mock.recorder = &MockIMAPClientProviderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockIMAPClientProvider) EXPECT() *MockIMAPClientProviderMockRecorder {
return m.recorder
}
// Authenticate mocks base method
// Authenticate mocks base method.
func (m *MockIMAPClientProvider) Authenticate(arg0 sasl.Client) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Authenticate", arg0)
@ -78,13 +78,13 @@ func (m *MockIMAPClientProvider) Authenticate(arg0 sasl.Client) error {
return ret0
}
// Authenticate indicates an expected call of Authenticate
// Authenticate indicates an expected call of Authenticate.
func (mr *MockIMAPClientProviderMockRecorder) Authenticate(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authenticate", reflect.TypeOf((*MockIMAPClientProvider)(nil).Authenticate), arg0)
}
// Capability mocks base method
// Capability mocks base method.
func (m *MockIMAPClientProvider) Capability() (map[string]bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Capability")
@ -93,13 +93,13 @@ func (m *MockIMAPClientProvider) Capability() (map[string]bool, error) {
return ret0, ret1
}
// Capability indicates an expected call of Capability
// Capability indicates an expected call of Capability.
func (mr *MockIMAPClientProviderMockRecorder) Capability() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Capability", reflect.TypeOf((*MockIMAPClientProvider)(nil).Capability))
}
// Fetch mocks base method
// Fetch mocks base method.
func (m *MockIMAPClientProvider) Fetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem, arg2 chan *imap.Message) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Fetch", arg0, arg1, arg2)
@ -107,13 +107,13 @@ func (m *MockIMAPClientProvider) Fetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem,
return ret0
}
// Fetch indicates an expected call of Fetch
// Fetch indicates an expected call of Fetch.
func (mr *MockIMAPClientProviderMockRecorder) Fetch(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fetch", reflect.TypeOf((*MockIMAPClientProvider)(nil).Fetch), arg0, arg1, arg2)
}
// List mocks base method
// List mocks base method.
func (m *MockIMAPClientProvider) List(arg0, arg1 string, arg2 chan *imap.MailboxInfo) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", arg0, arg1, arg2)
@ -121,13 +121,13 @@ func (m *MockIMAPClientProvider) List(arg0, arg1 string, arg2 chan *imap.Mailbox
return ret0
}
// List indicates an expected call of List
// List indicates an expected call of List.
func (mr *MockIMAPClientProviderMockRecorder) List(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockIMAPClientProvider)(nil).List), arg0, arg1, arg2)
}
// Login mocks base method
// Login mocks base method.
func (m *MockIMAPClientProvider) Login(arg0, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Login", arg0, arg1)
@ -135,13 +135,13 @@ func (m *MockIMAPClientProvider) Login(arg0, arg1 string) error {
return ret0
}
// Login indicates an expected call of Login
// Login indicates an expected call of Login.
func (mr *MockIMAPClientProviderMockRecorder) Login(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockIMAPClientProvider)(nil).Login), arg0, arg1)
}
// Select mocks base method
// Select mocks base method.
func (m *MockIMAPClientProvider) Select(arg0 string, arg1 bool) (*imap.MailboxStatus, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Select", arg0, arg1)
@ -150,13 +150,13 @@ func (m *MockIMAPClientProvider) Select(arg0 string, arg1 bool) (*imap.MailboxSt
return ret0, ret1
}
// Select indicates an expected call of Select
// Select indicates an expected call of Select.
func (mr *MockIMAPClientProviderMockRecorder) Select(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*MockIMAPClientProvider)(nil).Select), arg0, arg1)
}
// State mocks base method
// State mocks base method.
func (m *MockIMAPClientProvider) State() imap.ConnState {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "State")
@ -164,13 +164,13 @@ func (m *MockIMAPClientProvider) State() imap.ConnState {
return ret0
}
// State indicates an expected call of State
// State indicates an expected call of State.
func (mr *MockIMAPClientProviderMockRecorder) State() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "State", reflect.TypeOf((*MockIMAPClientProvider)(nil).State))
}
// Support mocks base method
// Support mocks base method.
func (m *MockIMAPClientProvider) Support(arg0 string) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Support", arg0)
@ -179,13 +179,13 @@ func (m *MockIMAPClientProvider) Support(arg0 string) (bool, error) {
return ret0, ret1
}
// Support indicates an expected call of Support
// Support indicates an expected call of Support.
func (mr *MockIMAPClientProviderMockRecorder) Support(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Support", reflect.TypeOf((*MockIMAPClientProvider)(nil).Support), arg0)
}
// SupportAuth mocks base method
// SupportAuth mocks base method.
func (m *MockIMAPClientProvider) SupportAuth(arg0 string) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SupportAuth", arg0)
@ -194,13 +194,13 @@ func (m *MockIMAPClientProvider) SupportAuth(arg0 string) (bool, error) {
return ret0, ret1
}
// SupportAuth indicates an expected call of SupportAuth
// SupportAuth indicates an expected call of SupportAuth.
func (mr *MockIMAPClientProviderMockRecorder) SupportAuth(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SupportAuth", reflect.TypeOf((*MockIMAPClientProvider)(nil).SupportAuth), arg0)
}
// UidFetch mocks base method
// UidFetch mocks base method.
func (m *MockIMAPClientProvider) UidFetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem, arg2 chan *imap.Message) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UidFetch", arg0, arg1, arg2)
@ -208,7 +208,7 @@ func (m *MockIMAPClientProvider) UidFetch(arg0 *imap.SeqSet, arg1 []imap.FetchIt
return ret0
}
// UidFetch indicates an expected call of UidFetch
// UidFetch indicates an expected call of UidFetch.
func (mr *MockIMAPClientProviderMockRecorder) UidFetch(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UidFetch", reflect.TypeOf((*MockIMAPClientProvider)(nil).UidFetch), arg0, arg1, arg2)

View File

@ -18,6 +18,7 @@
package credentials
import (
"errors"
"fmt"
"sort"
"sync"
@ -228,21 +229,28 @@ func (s *Store) Get(userID string) (creds *Credentials, err error) {
return s.get(userID)
}
func (s *Store) get(userID string) (creds *Credentials, err error) {
func (s *Store) get(userID string) (*Credentials, error) {
log := log.WithField("user", userID)
_, secret, err := s.secrets.Get(userID)
if err != nil {
log.WithError(err).Warn("Could not get credentials from native keychain")
return
return nil, err
}
if secret == "" {
return nil, errors.New("secret is empty")
}
credentials := &Credentials{UserID: userID}
if err = credentials.Unmarshal(secret); err != nil {
err = fmt.Errorf("backend/credentials: malformed secret: %v", err)
_ = s.secrets.Delete(userID)
log.WithError(err).Error("Could not unmarshal secret")
return
if err := credentials.Unmarshal(secret); err != nil {
log.WithError(fmt.Errorf("malformed secret: %w", err)).Error("Could not unmarshal secret")
if err := s.secrets.Delete(userID); err != nil {
log.WithError(err).Error("Failed to remove malformed secret")
}
return nil, err
}
return credentials, nil

View File

@ -11,54 +11,54 @@ import (
gomock "github.com/golang/mock/gomock"
)
// MockListener is a mock of Listener interface
// MockListener is a mock of Listener interface.
type MockListener struct {
ctrl *gomock.Controller
recorder *MockListenerMockRecorder
}
// MockListenerMockRecorder is the mock recorder for MockListener
// MockListenerMockRecorder is the mock recorder for MockListener.
type MockListenerMockRecorder struct {
mock *MockListener
}
// NewMockListener creates a new mock instance
// NewMockListener creates a new mock instance.
func NewMockListener(ctrl *gomock.Controller) *MockListener {
mock := &MockListener{ctrl: ctrl}
mock.recorder = &MockListenerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockListener) EXPECT() *MockListenerMockRecorder {
return m.recorder
}
// Add mocks base method
// Add mocks base method.
func (m *MockListener) Add(arg0 string, arg1 chan<- string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Add", arg0, arg1)
}
// Add indicates an expected call of Add
// Add indicates an expected call of Add.
func (mr *MockListenerMockRecorder) Add(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockListener)(nil).Add), arg0, arg1)
}
// Emit mocks base method
// Emit mocks base method.
func (m *MockListener) Emit(arg0, arg1 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Emit", arg0, arg1)
}
// Emit indicates an expected call of Emit
// Emit indicates an expected call of Emit.
func (mr *MockListenerMockRecorder) Emit(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Emit", reflect.TypeOf((*MockListener)(nil).Emit), arg0, arg1)
}
// ProvideChannel mocks base method
// ProvideChannel mocks base method.
func (m *MockListener) ProvideChannel(arg0 string) <-chan string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ProvideChannel", arg0)
@ -66,55 +66,55 @@ func (m *MockListener) ProvideChannel(arg0 string) <-chan string {
return ret0
}
// ProvideChannel indicates an expected call of ProvideChannel
// ProvideChannel indicates an expected call of ProvideChannel.
func (mr *MockListenerMockRecorder) ProvideChannel(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProvideChannel", reflect.TypeOf((*MockListener)(nil).ProvideChannel), arg0)
}
// Remove mocks base method
// Remove mocks base method.
func (m *MockListener) Remove(arg0 string, arg1 chan<- string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "Remove", arg0, arg1)
}
// Remove indicates an expected call of Remove
// Remove indicates an expected call of Remove.
func (mr *MockListenerMockRecorder) Remove(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockListener)(nil).Remove), arg0, arg1)
}
// RetryEmit mocks base method
// RetryEmit mocks base method.
func (m *MockListener) RetryEmit(arg0 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "RetryEmit", arg0)
}
// RetryEmit indicates an expected call of RetryEmit
// RetryEmit indicates an expected call of RetryEmit.
func (mr *MockListenerMockRecorder) RetryEmit(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryEmit", reflect.TypeOf((*MockListener)(nil).RetryEmit), arg0)
}
// SetBuffer mocks base method
// SetBuffer mocks base method.
func (m *MockListener) SetBuffer(arg0 string) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetBuffer", arg0)
}
// SetBuffer indicates an expected call of SetBuffer
// SetBuffer indicates an expected call of SetBuffer.
func (mr *MockListenerMockRecorder) SetBuffer(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetBuffer", reflect.TypeOf((*MockListener)(nil).SetBuffer), arg0)
}
// SetLimit mocks base method
// SetLimit mocks base method.
func (m *MockListener) SetLimit(arg0 string, arg1 time.Duration) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetLimit", arg0, arg1)
}
// SetLimit indicates an expected call of SetLimit
// SetLimit indicates an expected call of SetLimit.
func (mr *MockListenerMockRecorder) SetLimit(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLimit", reflect.TypeOf((*MockListener)(nil).SetLimit), arg0, arg1)

View File

@ -12,30 +12,30 @@ import (
gomock "github.com/golang/mock/gomock"
)
// MockLocator is a mock of Locator interface
// MockLocator is a mock of Locator interface.
type MockLocator struct {
ctrl *gomock.Controller
recorder *MockLocatorMockRecorder
}
// MockLocatorMockRecorder is the mock recorder for MockLocator
// MockLocatorMockRecorder is the mock recorder for MockLocator.
type MockLocatorMockRecorder struct {
mock *MockLocator
}
// NewMockLocator creates a new mock instance
// NewMockLocator creates a new mock instance.
func NewMockLocator(ctrl *gomock.Controller) *MockLocator {
mock := &MockLocator{ctrl: ctrl}
mock.recorder = &MockLocatorMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockLocator) EXPECT() *MockLocatorMockRecorder {
return m.recorder
}
// Clear mocks base method
// Clear mocks base method.
func (m *MockLocator) Clear() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Clear")
@ -43,71 +43,71 @@ func (m *MockLocator) Clear() error {
return ret0
}
// Clear indicates an expected call of Clear
// Clear indicates an expected call of Clear.
func (mr *MockLocatorMockRecorder) Clear() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockLocator)(nil).Clear))
}
// MockPanicHandler is a mock of PanicHandler interface
// MockPanicHandler is a mock of PanicHandler interface.
type MockPanicHandler struct {
ctrl *gomock.Controller
recorder *MockPanicHandlerMockRecorder
}
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler
// MockPanicHandlerMockRecorder is the mock recorder for MockPanicHandler.
type MockPanicHandlerMockRecorder struct {
mock *MockPanicHandler
}
// NewMockPanicHandler creates a new mock instance
// NewMockPanicHandler creates a new mock instance.
func NewMockPanicHandler(ctrl *gomock.Controller) *MockPanicHandler {
mock := &MockPanicHandler{ctrl: ctrl}
mock.recorder = &MockPanicHandlerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockPanicHandler) EXPECT() *MockPanicHandlerMockRecorder {
return m.recorder
}
// HandlePanic mocks base method
// HandlePanic mocks base method.
func (m *MockPanicHandler) HandlePanic() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "HandlePanic")
}
// HandlePanic indicates an expected call of HandlePanic
// HandlePanic indicates an expected call of HandlePanic.
func (mr *MockPanicHandlerMockRecorder) HandlePanic() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockPanicHandler)(nil).HandlePanic))
}
// MockCredentialsStorer is a mock of CredentialsStorer interface
// MockCredentialsStorer is a mock of CredentialsStorer interface.
type MockCredentialsStorer struct {
ctrl *gomock.Controller
recorder *MockCredentialsStorerMockRecorder
}
// MockCredentialsStorerMockRecorder is the mock recorder for MockCredentialsStorer
// MockCredentialsStorerMockRecorder is the mock recorder for MockCredentialsStorer.
type MockCredentialsStorerMockRecorder struct {
mock *MockCredentialsStorer
}
// NewMockCredentialsStorer creates a new mock instance
// NewMockCredentialsStorer creates a new mock instance.
func NewMockCredentialsStorer(ctrl *gomock.Controller) *MockCredentialsStorer {
mock := &MockCredentialsStorer{ctrl: ctrl}
mock.recorder = &MockCredentialsStorerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCredentialsStorer) EXPECT() *MockCredentialsStorerMockRecorder {
return m.recorder
}
// Add mocks base method
// Add mocks base method.
func (m *MockCredentialsStorer) Add(arg0, arg1, arg2, arg3 string, arg4 []byte, arg5 []string) (*credentials.Credentials, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Add", arg0, arg1, arg2, arg3, arg4, arg5)
@ -116,13 +116,13 @@ func (m *MockCredentialsStorer) Add(arg0, arg1, arg2, arg3 string, arg4 []byte,
return ret0, ret1
}
// Add indicates an expected call of Add
// Add indicates an expected call of Add.
func (mr *MockCredentialsStorerMockRecorder) Add(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockCredentialsStorer)(nil).Add), arg0, arg1, arg2, arg3, arg4, arg5)
}
// Delete mocks base method
// Delete mocks base method.
func (m *MockCredentialsStorer) Delete(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", arg0)
@ -130,13 +130,13 @@ func (m *MockCredentialsStorer) Delete(arg0 string) error {
return ret0
}
// Delete indicates an expected call of Delete
// Delete indicates an expected call of Delete.
func (mr *MockCredentialsStorerMockRecorder) Delete(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockCredentialsStorer)(nil).Delete), arg0)
}
// Get mocks base method
// Get mocks base method.
func (m *MockCredentialsStorer) Get(arg0 string) (*credentials.Credentials, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", arg0)
@ -145,13 +145,13 @@ func (m *MockCredentialsStorer) Get(arg0 string) (*credentials.Credentials, erro
return ret0, ret1
}
// Get indicates an expected call of Get
// Get indicates an expected call of Get.
func (mr *MockCredentialsStorerMockRecorder) Get(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockCredentialsStorer)(nil).Get), arg0)
}
// List mocks base method
// List mocks base method.
func (m *MockCredentialsStorer) List() ([]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List")
@ -160,13 +160,13 @@ func (m *MockCredentialsStorer) List() ([]string, error) {
return ret0, ret1
}
// List indicates an expected call of List
// List indicates an expected call of List.
func (mr *MockCredentialsStorerMockRecorder) List() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockCredentialsStorer)(nil).List))
}
// Logout mocks base method
// Logout mocks base method.
func (m *MockCredentialsStorer) Logout(arg0 string) (*credentials.Credentials, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Logout", arg0)
@ -175,13 +175,13 @@ func (m *MockCredentialsStorer) Logout(arg0 string) (*credentials.Credentials, e
return ret0, ret1
}
// Logout indicates an expected call of Logout
// Logout indicates an expected call of Logout.
func (mr *MockCredentialsStorerMockRecorder) Logout(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Logout", reflect.TypeOf((*MockCredentialsStorer)(nil).Logout), arg0)
}
// SwitchAddressMode mocks base method
// SwitchAddressMode mocks base method.
func (m *MockCredentialsStorer) SwitchAddressMode(arg0 string) (*credentials.Credentials, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SwitchAddressMode", arg0)
@ -190,13 +190,13 @@ func (m *MockCredentialsStorer) SwitchAddressMode(arg0 string) (*credentials.Cre
return ret0, ret1
}
// SwitchAddressMode indicates an expected call of SwitchAddressMode
// SwitchAddressMode indicates an expected call of SwitchAddressMode.
func (mr *MockCredentialsStorerMockRecorder) SwitchAddressMode(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SwitchAddressMode", reflect.TypeOf((*MockCredentialsStorer)(nil).SwitchAddressMode), arg0)
}
// UpdateEmails mocks base method
// UpdateEmails mocks base method.
func (m *MockCredentialsStorer) UpdateEmails(arg0 string, arg1 []string) (*credentials.Credentials, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateEmails", arg0, arg1)
@ -205,13 +205,13 @@ func (m *MockCredentialsStorer) UpdateEmails(arg0 string, arg1 []string) (*crede
return ret0, ret1
}
// UpdateEmails indicates an expected call of UpdateEmails
// UpdateEmails indicates an expected call of UpdateEmails.
func (mr *MockCredentialsStorerMockRecorder) UpdateEmails(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEmails", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateEmails), arg0, arg1)
}
// UpdatePassword mocks base method
// UpdatePassword mocks base method.
func (m *MockCredentialsStorer) UpdatePassword(arg0 string, arg1 []byte) (*credentials.Credentials, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdatePassword", arg0, arg1)
@ -220,13 +220,13 @@ func (m *MockCredentialsStorer) UpdatePassword(arg0 string, arg1 []byte) (*crede
return ret0, ret1
}
// UpdatePassword indicates an expected call of UpdatePassword
// UpdatePassword indicates an expected call of UpdatePassword.
func (mr *MockCredentialsStorerMockRecorder) UpdatePassword(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePassword", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdatePassword), arg0, arg1)
}
// UpdateToken mocks base method
// UpdateToken mocks base method.
func (m *MockCredentialsStorer) UpdateToken(arg0, arg1, arg2 string) (*credentials.Credentials, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateToken", arg0, arg1, arg2)
@ -235,36 +235,36 @@ func (m *MockCredentialsStorer) UpdateToken(arg0, arg1, arg2 string) (*credentia
return ret0, ret1
}
// UpdateToken indicates an expected call of UpdateToken
// UpdateToken indicates an expected call of UpdateToken.
func (mr *MockCredentialsStorerMockRecorder) UpdateToken(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateToken", reflect.TypeOf((*MockCredentialsStorer)(nil).UpdateToken), arg0, arg1, arg2)
}
// MockStoreMaker is a mock of StoreMaker interface
// MockStoreMaker is a mock of StoreMaker interface.
type MockStoreMaker struct {
ctrl *gomock.Controller
recorder *MockStoreMakerMockRecorder
}
// MockStoreMakerMockRecorder is the mock recorder for MockStoreMaker
// MockStoreMakerMockRecorder is the mock recorder for MockStoreMaker.
type MockStoreMakerMockRecorder struct {
mock *MockStoreMaker
}
// NewMockStoreMaker creates a new mock instance
// NewMockStoreMaker creates a new mock instance.
func NewMockStoreMaker(ctrl *gomock.Controller) *MockStoreMaker {
mock := &MockStoreMaker{ctrl: ctrl}
mock.recorder = &MockStoreMakerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockStoreMaker) EXPECT() *MockStoreMakerMockRecorder {
return m.recorder
}
// New mocks base method
// New mocks base method.
func (m *MockStoreMaker) New(arg0 store.BridgeUser) (*store.Store, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "New", arg0)
@ -273,13 +273,13 @@ func (m *MockStoreMaker) New(arg0 store.BridgeUser) (*store.Store, error) {
return ret0, ret1
}
// New indicates an expected call of New
// New indicates an expected call of New.
func (mr *MockStoreMakerMockRecorder) New(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "New", reflect.TypeOf((*MockStoreMaker)(nil).New), arg0)
}
// Remove mocks base method
// Remove mocks base method.
func (m *MockStoreMaker) Remove(arg0 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Remove", arg0)
@ -287,7 +287,7 @@ func (m *MockStoreMaker) Remove(arg0 string) error {
return ret0
}
// Remove indicates an expected call of Remove
// Remove indicates an expected call of Remove.
func (mr *MockStoreMakerMockRecorder) Remove(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockStoreMaker)(nil).Remove), arg0)

View File

@ -279,26 +279,6 @@ func (u *User) GetStoreAddresses() []string {
return u.creds.EmailList()
}
// getStoreAddresses returns a user's used addresses (with the original address in first place).
func (u *User) getStoreAddresses() []string { // nolint[unused]
addrInfo, err := u.store.GetAddressInfo()
if err != nil {
u.log.WithError(err).Error("Failed getting address info from store")
return nil
}
addresses := []string{}
for _, addr := range addrInfo {
addresses = append(addresses, addr.Address)
}
if u.IsCombinedAddressMode() {
return addresses[:1]
}
return addresses
}
// GetAddresses returns list of all addresses.
func (u *User) GetAddresses() []string {
u.lock.RLock()

View File

@ -59,6 +59,10 @@ func (v *Version) Equal(version *semver.Version) bool {
return v.version.Equal(version)
}
func (v *Version) SemVer() *semver.Version {
return v.version
}
// VerifyFiles verifies all files in the version directory.
func (v *Version) VerifyFiles(kr *crypto.KeyRing) error {
fileBytes, err := ioutil.ReadFile(filepath.Join(v.path, sumFile)) // nolint[gosec]

View File

@ -33,6 +33,7 @@ import (
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/text/encoding/htmlindex"
)
func newTestFetcher(
@ -66,6 +67,10 @@ func newTestMessage(
arm, err := enc.GetArmored()
require.NoError(t, err)
return newRawTestMessage(messageID, addressID, mimeType, arm, date)
}
func newRawTestMessage(messageID, addressID, mimeType, body string, date time.Time) *pmapi.Message {
return &pmapi.Message{
ID: messageID,
AddressID: addressID,
@ -74,7 +79,7 @@ func newTestMessage(
"Content-Type": {mimeType},
"Date": {date.In(time.UTC).Format(time.RFC1123Z)},
},
Body: arm,
Body: body,
Time: date.Unix(),
}
}
@ -298,6 +303,25 @@ func decryptsTo(kr *crypto.KeyRing, want string) decryptsToMatcher {
return decryptsToMatcher{kr: kr, want: want}
}
type decodesToMatcher struct {
charset string
want string
}
func (matcher decodesToMatcher) match(t *testing.T, have string) {
enc, err := htmlindex.Get(matcher.charset)
require.NoError(t, err)
dec, err := enc.NewDecoder().String(have)
require.NoError(t, err)
assert.Equal(t, matcher.want, dec)
}
func decodesTo(charset string, want string) decodesToMatcher {
return decodesToMatcher{charset: charset, want: want}
}
type verifiesAgainstMatcher struct {
kr *crypto.KeyRing
sig *crypto.PGPSignature

View File

@ -20,7 +20,6 @@ package message
import (
"bytes"
"encoding/base64"
"io/ioutil"
"mime"
"net/mail"
"strings"
@ -31,6 +30,7 @@ import (
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/emersion/go-message"
"github.com/emersion/go-message/textproto"
"github.com/pkg/errors"
)
@ -280,27 +280,30 @@ func buildPGPMIMEFallbackRFC822(msg *pmapi.Message, opts JobOptions) ([]byte, er
func writeMultipartSignedRFC822(header message.Header, body []byte, sig pmapi.Signature) ([]byte, error) { //nolint[funlen]
buf := new(bytes.Buffer)
boundary := newBoundary("").gen()
header.SetContentType("multipart/signed", map[string]string{
"micalg": sig.Hash,
"protocol": "application/pgp-signature",
"boundary": boundary,
})
w, err := message.CreateWriter(buf, header)
if err := textproto.WriteHeader(buf, header.Header); err != nil {
return nil, err
}
mw := textproto.NewMultipartWriter(buf)
if err := mw.SetBoundary(boundary); err != nil {
return nil, err
}
bodyHeader, bodyData, err := readHeaderBody(body)
if err != nil {
return nil, err
}
ent, err := message.Read(bytes.NewReader(body))
if err != nil {
return nil, err
}
bodyPart, err := w.CreatePart(ent.Header)
if err != nil {
return nil, err
}
bodyData, err := ioutil.ReadAll(ent.Body)
bodyPart, err := mw.CreatePart(*bodyHeader)
if err != nil {
return nil, err
}
@ -309,17 +312,13 @@ func writeMultipartSignedRFC822(header message.Header, body []byte, sig pmapi.Si
return nil, err
}
if err := bodyPart.Close(); err != nil {
return nil, err
}
var sigHeader message.Header
sigHeader.SetContentType("application/pgp-signature", map[string]string{"name": "OpenPGP_signature.asc"})
sigHeader.SetContentDisposition("attachment", map[string]string{"filename": "OpenPGP_signature"})
sigHeader.Set("Content-Description", "OpenPGP digital signature")
sigPart, err := w.CreatePart(sigHeader)
sigPart, err := mw.CreatePart(sigHeader.Header)
if err != nil {
return nil, err
}
@ -333,11 +332,7 @@ func writeMultipartSignedRFC822(header message.Header, body []byte, sig pmapi.Si
return nil, err
}
if err := sigPart.Close(); err != nil {
return nil, err
}
if err := w.Close(); err != nil {
if err := mw.Close(); err != nil {
return nil, err
}
@ -347,32 +342,28 @@ func writeMultipartSignedRFC822(header message.Header, body []byte, sig pmapi.Si
func writeMultipartEncryptedRFC822(header message.Header, body []byte) ([]byte, error) {
buf := new(bytes.Buffer)
ent, err := message.Read(bytes.NewReader(body))
bodyHeader, bodyData, err := readHeaderBody(body)
if err != nil {
return nil, err
}
entFields := ent.Header.Fields()
// If parsed header is empty then either it is malformed or it is missing.
// Anyway message could not be considered multipart/mixed anymore since there will be no boundary.
if bodyHeader.Len() == 0 {
header.Del("Content-Type")
}
entFields := bodyHeader.Fields()
for entFields.Next() {
header.Set(entFields.Key(), entFields.Value())
}
w, err := message.CreateWriter(buf, header)
if err != nil {
if err := textproto.WriteHeader(buf, header.Header); err != nil {
return nil, err
}
bodyData, err := ioutil.ReadAll(ent.Body)
if err != nil {
return nil, err
}
if _, err := w.Write(bodyData); err != nil {
return nil, err
}
if err := w.Close(); err != nil {
if _, err := buf.Write(bodyData); err != nil {
return nil, err
}
@ -495,7 +486,7 @@ func getAttachmentPartHeader(att *pmapi.Attachment) message.Header {
hdr.SetContentDisposition(att.Disposition, map[string]string{"filename": mime.QEncoding.Encode("utf-8", att.Name)})
// Use base64 for all attachments except embedded RFC822 messages.
if att.MIMEType != "message/rfc822" {
if att.MIMEType != rfc822Message {
hdr.Set("Content-Transfer-Encoding", "base64")
} else {
hdr.Del("Content-Transfer-Encoding")
@ -509,7 +500,10 @@ func toMessageHeader(hdr mail.Header) message.Header {
for key, val := range hdr {
for _, val := range val {
res.Add(key, val)
// Using AddRaw instead of Add to save key-value pair as byte buffer within Header.
// This buffer is used latter on in message writer to construct message and avoid crash
// when key length is more than 76 characters long.
res.AddRaw([]byte(key + ": " + val + "\r\n"))
}
}

View File

@ -25,6 +25,7 @@ import (
"testing"
"time"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/pkg/message/mocks"
tests "github.com/ProtonMail/proton-bridge/test"
"github.com/golang/mock/gomock"
@ -51,6 +52,27 @@ func TestBuildPlainMessage(t *testing.T) {
expectTransferEncoding(is(`quoted-printable`))
}
func TestBuildPlainMessageWithLongKey(t *testing.T) {
m := gomock.NewController(t)
defer m.Finish()
b := NewBuilder(1, 1, 1)
defer b.Done()
kr := tests.MakeKeyRing(t)
msg := newTestMessage(t, kr, "messageID", "addressID", "text/plain", "body", time.Now())
msg.Header["ReallyVeryVeryVeryVeryVeryLongLongLongLongLongLongLongKeyThatWillHaveNotSoLongValue"] = []string{"value"}
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
require.NoError(t, err)
section(t, res).
expectContentType(is(`text/plain`)).
expectBody(is(`body`)).
expectTransferEncoding(is(`quoted-printable`)).
expectHeader(`ReallyVeryVeryVeryVeryVeryLongLongLongLongLongLongLongKeyThatWillHaveNotSoLongValue`, is(`value`))
}
func TestBuildHTMLMessage(t *testing.T) {
m := gomock.NewController(t)
defer m.Finish()
@ -98,6 +120,151 @@ func TestBuildPlainEncryptedMessage(t *testing.T) {
expectBody(contains(`Where do fruits go on vacation? Pear-is!`))
}
func TestBuildPlainEncryptedMessageMissingHeader(t *testing.T) {
m := gomock.NewController(t)
defer m.Finish()
b := NewBuilder(1, 1, 1)
defer b.Done()
body := readerToString(getFileReader("plaintext-missing-header.eml"))
kr := tests.MakeKeyRing(t)
msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Now())
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
require.NoError(t, err)
section(t, res).
expectContentType(is(`text/plain`)).
expectBody(is("How do we know that the ocean is friendly? It waves!\r\n"))
}
func TestBuildPlainEncryptedMessageInvalidHeader(t *testing.T) {
m := gomock.NewController(t)
defer m.Finish()
b := NewBuilder(1, 1, 1)
defer b.Done()
body := readerToString(getFileReader("plaintext-invalid-header.eml"))
kr := tests.MakeKeyRing(t)
msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Now())
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
require.NoError(t, err)
section(t, res).
expectContentType(is(`text/plain`)).
expectBody(is("MalformedKey Value\r\n\r\nHow do we know that the ocean is friendly? It waves!\r\n"))
}
func TestBuildPlainSignedEncryptedMessageMissingHeader(t *testing.T) {
m := gomock.NewController(t)
defer m.Finish()
b := NewBuilder(1, 1, 1)
defer b.Done()
body := readerToString(getFileReader("plaintext-missing-header.eml"))
kr := tests.MakeKeyRing(t)
sig := tests.MakeKeyRing(t)
enc, err := kr.Encrypt(crypto.NewPlainMessageFromString(body), sig)
require.NoError(t, err)
arm, err := enc.GetArmored()
require.NoError(t, err)
msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC))
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
require.NoError(t, err)
section(t, res).
expectContentType(is(`multipart/signed`)).
expectContentTypeParam(`micalg`, is(`SHA-256`)). // NOTE: Maybe this is bad... should probably be pgp-sha256
expectContentTypeParam(`protocol`, is(`application/pgp-signature`)).
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
section(t, res, 1).
expectContentType(is(`text/plain`)).
expectBody(is("How do we know that the ocean is friendly? It waves!\r\n"))
section(t, res, 2).
expectContentType(is(`application/pgp-signature`)).
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
expectContentDisposition(is(`attachment`)).
expectContentDispositionParam(`filename`, is(`OpenPGP_signature`))
}
func TestBuildPlainSignedEncryptedMessageInvalidHeader(t *testing.T) {
m := gomock.NewController(t)
defer m.Finish()
b := NewBuilder(1, 1, 1)
defer b.Done()
body := readerToString(getFileReader("plaintext-invalid-header.eml"))
kr := tests.MakeKeyRing(t)
sig := tests.MakeKeyRing(t)
enc, err := kr.Encrypt(crypto.NewPlainMessageFromString(body), sig)
require.NoError(t, err)
arm, err := enc.GetArmored()
require.NoError(t, err)
msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC))
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
require.NoError(t, err)
section(t, res).
expectContentType(is(`multipart/signed`)).
expectContentTypeParam(`micalg`, is(`SHA-256`)). // NOTE: Maybe this is bad... should probably be pgp-sha256
expectContentTypeParam(`protocol`, is(`application/pgp-signature`)).
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
section(t, res, 1).
expectContentType(is(`text/plain`)).
expectBody(is("MalformedKey Value\r\n\r\nHow do we know that the ocean is friendly? It waves!\r\n"))
section(t, res, 2).
expectContentType(is(`application/pgp-signature`)).
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
expectContentDisposition(is(`attachment`)).
expectContentDispositionParam(`filename`, is(`OpenPGP_signature`))
}
func TestBuildPlainEncryptedLatin2Message(t *testing.T) {
m := gomock.NewController(t)
defer m.Finish()
b := NewBuilder(1, 1, 1)
defer b.Done()
body := readerToString(getFileReader("pgp-mime-body-plaintext-latin2.eml"))
kr := tests.MakeKeyRing(t)
msg := newTestMessage(t, kr, "messageID", "addressID", "multipart/mixed", body, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC))
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
require.NoError(t, err)
section(t, res).
expectContentType(is(`text/plain`)).
expectContentTypeParam("charset", is(`iso-8859-2`)).
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`)).
expectHeader(`Subject`, is(`plain no pubkey no sign`)).
expectHeader(`From`, is(`"pm.bridge.qa" <pm.bridge.qa@gmail.com>`)).
expectHeader(`To`, is(`schizofrenic@pm.me`)).
expectBody(decodesTo("iso-8859-2", "řšřšřš\r\n"))
}
func TestBuildHTMLEncryptedMessage(t *testing.T) {
m := gomock.NewController(t)
defer m.Finish()
@ -127,6 +294,89 @@ func TestBuildHTMLEncryptedMessage(t *testing.T) {
expectBody(contains(`Where do boats go when they're sick`))
}
func TestBuildPlainSignedMessage(t *testing.T) {
m := gomock.NewController(t)
defer m.Finish()
b := NewBuilder(1, 1, 1)
defer b.Done()
body := readerToString(getFileReader("text_plain.eml"))
kr := tests.MakeKeyRing(t)
sig := tests.MakeKeyRing(t)
enc, err := kr.Encrypt(crypto.NewPlainMessageFromString(body), sig)
require.NoError(t, err)
arm, err := enc.GetArmored()
require.NoError(t, err)
msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC))
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
require.NoError(t, err)
section(t, res).
expectContentType(is(`multipart/signed`)).
expectContentTypeParam(`micalg`, is(`SHA-256`)). // NOTE: Maybe this is bad... should probably be pgp-sha256
expectContentTypeParam(`protocol`, is(`application/pgp-signature`)).
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
section(t, res, 1).
expectContentType(is(`text/plain`)).
expectBody(is(`body`)).
expectSection(verifiesAgainst(sig, section(t, res, 2).signature()))
section(t, res, 2).
expectContentType(is(`application/pgp-signature`)).
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
expectContentDisposition(is(`attachment`)).
expectContentDispositionParam(`filename`, is(`OpenPGP_signature`))
}
func TestBuildPlainSignedBase64Message(t *testing.T) {
m := gomock.NewController(t)
defer m.Finish()
b := NewBuilder(1, 1, 1)
defer b.Done()
body := readerToString(getFileReader("text_plain_base64.eml"))
kr := tests.MakeKeyRing(t)
sig := tests.MakeKeyRing(t)
enc, err := kr.Encrypt(crypto.NewPlainMessageFromString(body), sig)
require.NoError(t, err)
arm, err := enc.GetArmored()
require.NoError(t, err)
msg := newRawTestMessage("messageID", "addressID", "multipart/mixed", arm, time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC))
res, err := b.NewJob(context.Background(), newTestFetcher(m, kr, msg), msg.ID).GetResult()
require.NoError(t, err)
section(t, res).
expectContentType(is(`multipart/signed`)).
expectContentTypeParam(`micalg`, is(`SHA-256`)). // NOTE: Maybe this is bad... should probably be pgp-sha256
expectContentTypeParam(`protocol`, is(`application/pgp-signature`)).
expectDate(is(`Wed, 01 Jan 2020 00:00:00 +0000`))
section(t, res, 1).
expectContentType(is(`text/plain`)).
expectTransferEncoding(is(`base64`)).
expectBody(is(`body`)).
expectSection(verifiesAgainst(sig, section(t, res, 2).signature()))
section(t, res, 2).
expectContentType(is(`application/pgp-signature`)).
expectContentTypeParam(`name`, is(`OpenPGP_signature.asc`)).
expectContentDisposition(is(`attachment`)).
expectContentDispositionParam(`filename`, is(`OpenPGP_signature`))
}
func TestBuildSignedPlainEncryptedMessage(t *testing.T) {
m := gomock.NewController(t)
defer m.Finish()

View File

@ -22,6 +22,7 @@ import (
"bytes"
"io"
"io/ioutil"
"unicode"
"github.com/emersion/go-message/textproto"
"github.com/pkg/errors"
@ -37,8 +38,7 @@ func HeaderLines(header []byte) [][]byte {
forEachLine(bufio.NewReader(bytes.NewReader(header)), func(line []byte) {
l := bytes.SplitN(line, []byte(`: `), 2)
isLineContinuation := quote%2 != 0 || // no quotes opened
len(l) != 2 || // it doesn't have colon
(len(l) == 2 && !bytes.Equal(bytes.TrimSpace(l[0]), l[0])) // has white space in front of header field
!bytes.Equal(bytes.TrimLeftFunc(l[0], unicode.IsSpace), l[0]) // has whitespace indent at beginning
switch {
case len(bytes.TrimSpace(line)) == 0:
lines = append(lines, line)
@ -85,17 +85,40 @@ func readHeaderBody(b []byte) (*textproto.Header, []byte, error) {
return nil, nil, err
}
lines := HeaderLines(rawHeader)
var header textproto.Header
for _, line := range HeaderLines(rawHeader) {
if len(bytes.TrimSpace(line)) > 0 {
header.AddRaw(line)
// We assume that everything before first occurrence of empty line is header.
// If header is invalid for any reason or empty - put everything as body and let header be empty.
if !isHeaderValid(lines) {
return &header, b, nil
}
// We add lines in reverse so that calling textproto.WriteHeader later writes with the correct order.
for i := len(lines) - 1; i >= 0; i-- {
if len(bytes.TrimSpace(lines[i])) > 0 {
header.AddRaw(lines[i])
}
}
return &header, body, nil
}
func isHeaderValid(headerLines [][]byte) bool {
if len(headerLines) == 0 {
return false
}
for _, line := range headerLines {
if (bytes.IndexByte(line, ':') == -1) && (len(bytes.TrimSpace(line)) > 0) {
return false
}
}
return true
}
func splitHeaderBody(b []byte) ([]byte, []byte, error) {
br := bufio.NewReader(bytes.NewReader(b))

View File

@ -79,3 +79,34 @@ Content-ID: <>
[]byte("Content-ID: <>\n"),
}, HeaderLines([]byte(header)))
}
func TestReadHeaderBody(t *testing.T) {
const data = "key: value\r\n\r\nbody\n"
header, body, err := readHeaderBody([]byte(data))
assert.NoError(t, err)
assert.Equal(t, 1, header.Len())
assert.Equal(t, "value", header.Get("key"))
assert.Equal(t, []byte("body\n"), body)
}
func TestReadHeaderBodyWithoutHeader(t *testing.T) {
const data = "body\n"
header, body, err := readHeaderBody([]byte(data))
assert.NoError(t, err)
assert.Equal(t, 0, header.Len())
assert.Equal(t, []byte(data), body)
}
func TestReadHeaderBodyInvalidHeader(t *testing.T) {
const data = "value\r\n\r\nbody\n"
header, body, err := readHeaderBody([]byte(data))
assert.NoError(t, err)
assert.Equal(t, 0, header.Len())
assert.Equal(t, []byte(data), body)
}

View File

@ -14,30 +14,30 @@ import (
gomock "github.com/golang/mock/gomock"
)
// MockFetcher is a mock of Fetcher interface
// MockFetcher is a mock of Fetcher interface.
type MockFetcher struct {
ctrl *gomock.Controller
recorder *MockFetcherMockRecorder
}
// MockFetcherMockRecorder is the mock recorder for MockFetcher
// MockFetcherMockRecorder is the mock recorder for MockFetcher.
type MockFetcherMockRecorder struct {
mock *MockFetcher
}
// NewMockFetcher creates a new mock instance
// NewMockFetcher creates a new mock instance.
func NewMockFetcher(ctrl *gomock.Controller) *MockFetcher {
mock := &MockFetcher{ctrl: ctrl}
mock.recorder = &MockFetcherMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockFetcher) EXPECT() *MockFetcherMockRecorder {
return m.recorder
}
// GetAttachment mocks base method
// GetAttachment mocks base method.
func (m *MockFetcher) GetAttachment(arg0 context.Context, arg1 string) (io.ReadCloser, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAttachment", arg0, arg1)
@ -46,13 +46,13 @@ func (m *MockFetcher) GetAttachment(arg0 context.Context, arg1 string) (io.ReadC
return ret0, ret1
}
// GetAttachment indicates an expected call of GetAttachment
// GetAttachment indicates an expected call of GetAttachment.
func (mr *MockFetcherMockRecorder) GetAttachment(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAttachment", reflect.TypeOf((*MockFetcher)(nil).GetAttachment), arg0, arg1)
}
// GetMessage mocks base method
// GetMessage mocks base method.
func (m *MockFetcher) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Message, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetMessage", arg0, arg1)
@ -61,13 +61,13 @@ func (m *MockFetcher) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Mess
return ret0, ret1
}
// GetMessage indicates an expected call of GetMessage
// GetMessage indicates an expected call of GetMessage.
func (mr *MockFetcherMockRecorder) GetMessage(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockFetcher)(nil).GetMessage), arg0, arg1)
}
// KeyRingForAddressID mocks base method
// KeyRingForAddressID mocks base method.
func (m *MockFetcher) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "KeyRingForAddressID", arg0)
@ -76,7 +76,7 @@ func (m *MockFetcher) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error)
return ret0, ret1
}
// KeyRingForAddressID indicates an expected call of KeyRingForAddressID
// KeyRingForAddressID indicates an expected call of KeyRingForAddressID.
func (mr *MockFetcherMockRecorder) KeyRingForAddressID(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyRingForAddressID", reflect.TypeOf((*MockFetcher)(nil).KeyRingForAddressID), arg0)

View File

@ -528,6 +528,9 @@ func parseAttachment(h message.Header) (*pmapi.Attachment, error) {
if att.Name == "" {
att.Name = mimeTypeParams["name"]
}
if att.Name == "" && mimeType == rfc822Message {
att.Name = "message.eml"
}
if att.Name == "" {
att.Name = "attachment.bin"
}

View File

@ -222,6 +222,22 @@ func TestParseTextPlainWithOctetAttachmentGoodFilename(t *testing.T) {
assert.Equal(t, "😁😂.txt", m.Attachments[0].Name)
}
func TestParseTextPlainWithRFC822Attachment(t *testing.T) {
f := getFileReader("text_plain_rfc822_attachment.eml")
m, _, plainBody, attReaders, err := Parse(f)
require.NoError(t, err)
assert.Equal(t, `"Sender" <sender@pm.me>`, m.Sender.String())
assert.Equal(t, `"Receiver" <receiver@pm.me>`, m.ToList[0].String())
assert.Equal(t, "body", m.Body)
assert.Equal(t, "body", plainBody)
assert.Len(t, attReaders, 1)
assert.Equal(t, "message.eml", m.Attachments[0].Name)
}
func TestParseTextPlainWithOctetAttachmentBadFilename(t *testing.T) {
f := getFileReader("text_plain_octet_attachment_bad_2231_filename.eml")

View File

@ -103,7 +103,7 @@ func (bs *BodyStructure) parseAllChildSections(r io.Reader, currentPath []int, s
mediaType, params, _ := pmmime.ParseMediaType(info.Header.Get("Content-Type"))
// If multipart, call getAllParts, else read to count lines.
if (strings.HasPrefix(mediaType, "multipart/") || mediaType == "message/rfc822") && params["boundary"] != "" {
if (strings.HasPrefix(mediaType, "multipart/") || mediaType == rfc822Message) && params["boundary"] != "" {
nextPath := getChildPath(currentPath)
var br *boundaryReader

View File

@ -0,0 +1,8 @@
Subject: plain no pubkey no sign
From: "pm.bridge.qa" <pm.bridge.qa@gmail.com>
To: schizofrenic@pm.me
Message-ID: <564b9c7c-91eb-6508-107a-35108f383a44@gmail.com>
Content-Type: text/plain; charset=iso-8859-2; format=flowed
Content-Language: en-US
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>

View File

@ -0,0 +1,3 @@
MalformedKey Value
How do we know that the ocean is friendly? It waves!

View File

@ -0,0 +1 @@
How do we know that the ocean is friendly? It waves!

View File

@ -0,0 +1,5 @@
From: Sender <sender@pm.me>
To: Receiver <receiver@pm.me>
Content-Transfer-Encoding: base64
Ym9keQ==

View File

@ -0,0 +1,16 @@
From: Sender <sender@pm.me>
To: Receiver <receiver@pm.me>
Content-Type: multipart/mixed; boundary=longrandomstring
--longrandomstring
body
--longrandomstring
Content-Type: message/rfc822
Content-Disposition: attachment
From: Sender <sender@pm.me>
To: Receiver <receiver@pm.me>
inner body
--longrandomstring--

View File

@ -217,3 +217,13 @@ func randomString(length int) string {
return base64.StdEncoding.EncodeToString(noise)[:length]
}
func (c *client) GetCurrentAuth() *Auth {
return &Auth{
UserID: c.user.ID,
AuthRefresh: AuthRefresh{
UID: c.uid,
RefreshToken: c.ref,
},
}
}

View File

@ -143,6 +143,51 @@ func Test401RevokedAuth(t *testing.T) {
r.EqualError(t, err, ErrUnauthorized.Error())
}
func Test401RevokedAuthTokenUpdate(t *testing.T) {
var oldAuth = &AuthRefresh{
UID: "UID",
AccessToken: "oldAcc",
RefreshToken: "oldRef",
ExpiresIn: 3600,
}
var newAuth = &AuthRefresh{
UID: "UID",
AccessToken: "newAcc",
RefreshToken: "newRef",
}
mux := http.NewServeMux()
mux.HandleFunc("/auth/refresh", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(newAuth); err != nil {
panic(err)
}
})
mux.HandleFunc("/addresses", func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") == ("Bearer " + oldAuth.AccessToken) {
w.WriteHeader(http.StatusUnauthorized)
return
}
if r.Header.Get("Authorization") == ("Bearer " + newAuth.AccessToken) {
w.WriteHeader(http.StatusOK)
return
}
})
ts := httptest.NewServer(mux)
c := New(Config{HostURL: ts.URL}).
NewClient(oldAuth.UID, oldAuth.AccessToken, oldAuth.RefreshToken, time.Now().Add(time.Hour))
// The request will fail with 401, triggering a refresh. After the refresh it should succeed.
_, err := c.GetAddresses(context.Background())
r.NoError(t, err)
}
func TestAuth2FA(t *testing.T) {
twoFACode := "code"

View File

@ -84,6 +84,8 @@ func (c *client) r(ctx context.Context) (*resty.Request, error) {
return r, nil
}
// do executes fn and may repeate it in case "401 Unauthorized" error is returned.
// Note: fn may be called more than once.
func (c *client) do(ctx context.Context, fn func(*resty.Request) (*resty.Response, error)) (*resty.Response, error) {
r, err := c.r(ctx)
if err != nil {
@ -102,6 +104,12 @@ func (c *client) do(ctx context.Context, fn func(*resty.Request) (*resty.Respons
return nil, err
}
// We need to reconstruct request since access token is changed with authRefresh.
r, err := c.r(ctx)
if err != nil {
return nil, err
}
return wrapNoConnection(fn(r))
}

View File

@ -64,29 +64,6 @@ var errVerificationFailed = errors.New("signature verification failed")
// ================= Public utility functions ======================
func (c *client) EncryptAndSignCards(cards []Card) ([]Card, error) {
var err error
for i := range cards {
card := &cards[i]
if isEncryptedCardType(card.Type) {
if isSignedCardType(card.Type) {
if card.Signature, err = c.sign(card.Data); err != nil {
return nil, err
}
}
if card.Data, err = c.encrypt(card.Data, nil); err != nil {
return nil, err
}
} else if isSignedCardType(card.Type) {
if card.Signature, err = c.sign(card.Data); err != nil {
return nil, err
}
}
}
return cards, nil
}
func (c *client) DecryptAndVerifyCards(cards []Card) ([]Card, error) {
for i := range cards {
card := &cards[i]

View File

@ -209,20 +209,6 @@ var testCardsCleartext = []Card{
},
}
func TestClient_Encrypt(t *testing.T) {
c := newClient(newManager(Config{}), "")
c.userKeyRing = testPrivateKeyRing
cardEncrypted, err := c.EncryptAndSignCards(testCardsCleartext)
r.Nil(t, err)
// Result is always different, so the best way is to test it by decrypting again.
// Another test for decrypting will help us to be sure it's working.
cardCleartext, err := c.DecryptAndVerifyCards(cardEncrypted)
r.Nil(t, err)
r.Equal(t, testCardsCleartext[0].Data, cardCleartext[0].Data)
}
func TestClient_Decrypt(t *testing.T) {
c := newClient(newManager(Config{}), "")
c.userKeyRing = testPrivateKeyRing

View File

@ -35,7 +35,9 @@ func CreateTransportWithDialer(dialer TLSDialer) *http.Transport {
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

View File

@ -27,7 +27,10 @@ import (
"github.com/go-resty/resty/v2"
)
const MaxImportMessageRequestLength = 10
const (
MaxImportMessageRequestLength = 10
MaxImportMessageRequestSize = 25 * 1024 * 1024 // 25 MB total limit
)
type ImportMsgReq struct {
Metadata *ImportMetadata // Metadata about the message to import.
@ -91,6 +94,14 @@ func (c *client) Import(ctx context.Context, reqs ImportMsgReqs) ([]*ImportMsgRe
return nil, errors.New("request is too long")
}
remainingSize := MaxImportMessageRequestSize
for _, req := range reqs {
remainingSize -= len(req.Message)
if remainingSize < 0 {
return nil, errors.New("request size is too big")
}
}
fields, err := reqs.buildMultipartFormData()
if err != nil {
return nil, err

View File

@ -23,6 +23,7 @@ import (
"fmt"
"io"
"io/ioutil"
"math/rand"
"mime/multipart"
"net/http"
"testing"
@ -115,3 +116,32 @@ func TestClient_Import(t *testing.T) { // nolint[funlen]
r.Equal(t, 1, len(imported))
r.Equal(t, testImportRes, imported[0])
}
func TestClientImportBigSize(t *testing.T) {
s, c := newTestClient(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
r.FailNow(t, "request is not dropped")
}))
defer s.Close()
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const size = MaxImportMessageRequestSize + 1
msg := make([]byte, size)
for i := 0; i < size; i++ {
msg[i] = letterBytes[rand.Intn(len(letterBytes))]
}
importRequest := []*ImportMsgReq{
{
Metadata: &ImportMetadata{
AddressID: "addressID",
Unread: Boolean(false),
Flags: FlagReceived | FlagImported,
LabelIDs: []string{ArchiveLabel},
},
Message: msg,
},
}
_, err := c.Import(context.Background(), importRequest)
r.EqualError(t, err, "request size is too big")
}

View File

@ -36,8 +36,8 @@ type PMKey struct {
Fingerprint string
PrivateKey *crypto.Key
Primary int
Token *string `json:",omitempty"`
Signature *string `json:",omitempty"`
Token string
Signature string
}
type clearable []byte
@ -84,12 +84,12 @@ func (key PMKey) getPassphraseFromToken(kr *crypto.KeyRing) (passphrase []byte,
return nil, errors.New("no user key was provided")
}
msg, err := crypto.NewPGPMessageFromArmored(*key.Token)
msg, err := crypto.NewPGPMessageFromArmored(key.Token)
if err != nil {
return
}
sig, err := crypto.NewPGPSignatureFromArmored(*key.Signature)
sig, err := crypto.NewPGPSignatureFromArmored(key.Signature)
if err != nil {
return
}
@ -137,7 +137,7 @@ func (keys *PMKeys) UnlockAll(passphrase []byte, userKey *crypto.KeyRing) (kr *c
for _, key := range *keys {
var secret []byte
if key.Token == nil || key.Signature == nil {
if key.Token == "" || key.Signature == "" {
secret = passphrase
} else if secret, err = key.getPassphraseFromToken(userKey); err != nil {
return
@ -166,10 +166,6 @@ func (keys *PMKeys) UnlockAll(passphrase []byte, userKey *crypto.KeyRing) (kr *c
// ErrNoKeyringAvailable represents an error caused by a keyring being nil or having no entities.
var ErrNoKeyringAvailable = errors.New("no keyring available")
func (c *client) encrypt(plain string, signer *crypto.KeyRing) (armored string, err error) {
return encrypt(c.userKeyRing, plain, signer)
}
func encrypt(encrypter *crypto.KeyRing, plain string, signer *crypto.KeyRing) (armored string, err error) {
if encrypter == nil {
return "", ErrNoKeyringAvailable
@ -209,18 +205,6 @@ func decrypt(decrypter *crypto.KeyRing, armored string) (plainBody []byte, err e
return plainMessage.GetBinary(), nil
}
func (c *client) sign(plain string) (armoredSignature string, err error) {
if c.userKeyRing == nil {
return "", ErrNoKeyringAvailable
}
plainMessage := crypto.NewPlainMessageFromString(plain)
pgpSignature, err := c.userKeyRing.SignDetached(plainMessage)
if err != nil {
return
}
return pgpSignature.GetArmored()
}
func (c *client) verify(plain, amroredSignature string) (err error) {
plainMessage := crypto.NewPlainMessageFromString(plain)
pgpSignature, err := crypto.NewPGPSignatureFromArmored(amroredSignature)

View File

@ -43,7 +43,7 @@ func New(cfg Config) Manager {
func newManager(cfg Config) *manager {
m := &manager{
cfg: cfg,
rc: resty.New(),
rc: resty.New().EnableTrace(),
locker: &sync.Mutex{},
}
@ -59,6 +59,8 @@ func newManager(cfg Config) *manager {
// wrapped in JSON. If error is returned, `handleRequestFailure` is called,
// otherwise `handleRequestSuccess` is called.
m.rc.SetError(&Error{})
m.rc.OnAfterResponse(logConnReuse)
m.rc.OnAfterResponse(updateTime)
m.rc.OnAfterResponse(m.catchAPIError)
m.rc.OnAfterResponse(m.handleRequestSuccess)
m.rc.OnError(m.handleRequestFailure)

View File

@ -429,6 +429,14 @@ func (m *Message) Has(flag int64) bool {
return (m.Flags & flag) == flag
}
func (m *Message) Recipients() []*mail.Address {
var recipients []*mail.Address
recipients = append(recipients, m.ToList...)
recipients = append(recipients, m.CCList...)
recipients = append(recipients, m.BCCList...)
return recipients
}
// MessagesCount contains message counts for one label.
type MessagesCount struct {
LabelID string

View File

@ -17,42 +17,42 @@ import (
logrus "github.com/sirupsen/logrus"
)
// MockClient is a mock of Client interface
// MockClient is a mock of Client interface.
type MockClient struct {
ctrl *gomock.Controller
recorder *MockClientMockRecorder
}
// MockClientMockRecorder is the mock recorder for MockClient
// MockClientMockRecorder is the mock recorder for MockClient.
type MockClientMockRecorder struct {
mock *MockClient
}
// NewMockClient creates a new mock instance
// NewMockClient creates a new mock instance.
func NewMockClient(ctrl *gomock.Controller) *MockClient {
mock := &MockClient{ctrl: ctrl}
mock.recorder = &MockClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockClient) EXPECT() *MockClientMockRecorder {
return m.recorder
}
// AddAuthRefreshHandler mocks base method
// AddAuthRefreshHandler mocks base method.
func (m *MockClient) AddAuthRefreshHandler(arg0 pmapi.AuthRefreshHandler) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "AddAuthRefreshHandler", arg0)
}
// AddAuthRefreshHandler indicates an expected call of AddAuthRefreshHandler
// AddAuthRefreshHandler indicates an expected call of AddAuthRefreshHandler.
func (mr *MockClientMockRecorder) AddAuthRefreshHandler(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddAuthRefreshHandler", reflect.TypeOf((*MockClient)(nil).AddAuthRefreshHandler), arg0)
}
// Addresses mocks base method
// Addresses mocks base method.
func (m *MockClient) Addresses() pmapi.AddressList {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Addresses")
@ -60,13 +60,13 @@ func (m *MockClient) Addresses() pmapi.AddressList {
return ret0
}
// Addresses indicates an expected call of Addresses
// Addresses indicates an expected call of Addresses.
func (mr *MockClientMockRecorder) Addresses() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Addresses", reflect.TypeOf((*MockClient)(nil).Addresses))
}
// Auth2FA mocks base method
// Auth2FA mocks base method.
func (m *MockClient) Auth2FA(arg0 context.Context, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Auth2FA", arg0, arg1)
@ -74,13 +74,13 @@ func (m *MockClient) Auth2FA(arg0 context.Context, arg1 string) error {
return ret0
}
// Auth2FA indicates an expected call of Auth2FA
// Auth2FA indicates an expected call of Auth2FA.
func (mr *MockClientMockRecorder) Auth2FA(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Auth2FA", reflect.TypeOf((*MockClient)(nil).Auth2FA), arg0, arg1)
}
// AuthDelete mocks base method
// AuthDelete mocks base method.
func (m *MockClient) AuthDelete(arg0 context.Context) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AuthDelete", arg0)
@ -88,13 +88,13 @@ func (m *MockClient) AuthDelete(arg0 context.Context) error {
return ret0
}
// AuthDelete indicates an expected call of AuthDelete
// AuthDelete indicates an expected call of AuthDelete.
func (mr *MockClientMockRecorder) AuthDelete(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthDelete", reflect.TypeOf((*MockClient)(nil).AuthDelete), arg0)
}
// AuthSalt mocks base method
// AuthSalt mocks base method.
func (m *MockClient) AuthSalt(arg0 context.Context) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AuthSalt", arg0)
@ -103,13 +103,13 @@ func (m *MockClient) AuthSalt(arg0 context.Context) (string, error) {
return ret0, ret1
}
// AuthSalt indicates an expected call of AuthSalt
// AuthSalt indicates an expected call of AuthSalt.
func (mr *MockClientMockRecorder) AuthSalt(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthSalt", reflect.TypeOf((*MockClient)(nil).AuthSalt), arg0)
}
// CountMessages mocks base method
// CountMessages mocks base method.
func (m *MockClient) CountMessages(arg0 context.Context, arg1 string) ([]*pmapi.MessagesCount, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CountMessages", arg0, arg1)
@ -118,13 +118,13 @@ func (m *MockClient) CountMessages(arg0 context.Context, arg1 string) ([]*pmapi.
return ret0, ret1
}
// CountMessages indicates an expected call of CountMessages
// CountMessages indicates an expected call of CountMessages.
func (mr *MockClientMockRecorder) CountMessages(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountMessages", reflect.TypeOf((*MockClient)(nil).CountMessages), arg0, arg1)
}
// CreateAttachment mocks base method
// CreateAttachment mocks base method.
func (m *MockClient) CreateAttachment(arg0 context.Context, arg1 *pmapi.Attachment, arg2, arg3 io.Reader) (*pmapi.Attachment, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateAttachment", arg0, arg1, arg2, arg3)
@ -133,13 +133,13 @@ func (m *MockClient) CreateAttachment(arg0 context.Context, arg1 *pmapi.Attachme
return ret0, ret1
}
// CreateAttachment indicates an expected call of CreateAttachment
// CreateAttachment indicates an expected call of CreateAttachment.
func (mr *MockClientMockRecorder) CreateAttachment(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAttachment", reflect.TypeOf((*MockClient)(nil).CreateAttachment), arg0, arg1, arg2, arg3)
}
// CreateDraft mocks base method
// CreateDraft mocks base method.
func (m *MockClient) CreateDraft(arg0 context.Context, arg1 *pmapi.Message, arg2 string, arg3 int) (*pmapi.Message, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateDraft", arg0, arg1, arg2, arg3)
@ -148,13 +148,13 @@ func (m *MockClient) CreateDraft(arg0 context.Context, arg1 *pmapi.Message, arg2
return ret0, ret1
}
// CreateDraft indicates an expected call of CreateDraft
// CreateDraft indicates an expected call of CreateDraft.
func (mr *MockClientMockRecorder) CreateDraft(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateDraft", reflect.TypeOf((*MockClient)(nil).CreateDraft), arg0, arg1, arg2, arg3)
}
// CreateLabel mocks base method
// CreateLabel mocks base method.
func (m *MockClient) CreateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmapi.Label, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateLabel", arg0, arg1)
@ -163,13 +163,13 @@ func (m *MockClient) CreateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmap
return ret0, ret1
}
// CreateLabel indicates an expected call of CreateLabel
// CreateLabel indicates an expected call of CreateLabel.
func (mr *MockClientMockRecorder) CreateLabel(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLabel", reflect.TypeOf((*MockClient)(nil).CreateLabel), arg0, arg1)
}
// CurrentUser mocks base method
// CurrentUser mocks base method.
func (m *MockClient) CurrentUser(arg0 context.Context) (*pmapi.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CurrentUser", arg0)
@ -178,13 +178,13 @@ func (m *MockClient) CurrentUser(arg0 context.Context) (*pmapi.User, error) {
return ret0, ret1
}
// CurrentUser indicates an expected call of CurrentUser
// CurrentUser indicates an expected call of CurrentUser.
func (mr *MockClientMockRecorder) CurrentUser(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CurrentUser", reflect.TypeOf((*MockClient)(nil).CurrentUser), arg0)
}
// DecryptAndVerifyCards mocks base method
// DecryptAndVerifyCards mocks base method.
func (m *MockClient) DecryptAndVerifyCards(arg0 []pmapi.Card) ([]pmapi.Card, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecryptAndVerifyCards", arg0)
@ -193,13 +193,13 @@ func (m *MockClient) DecryptAndVerifyCards(arg0 []pmapi.Card) ([]pmapi.Card, err
return ret0, ret1
}
// DecryptAndVerifyCards indicates an expected call of DecryptAndVerifyCards
// DecryptAndVerifyCards indicates an expected call of DecryptAndVerifyCards.
func (mr *MockClientMockRecorder) DecryptAndVerifyCards(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecryptAndVerifyCards", reflect.TypeOf((*MockClient)(nil).DecryptAndVerifyCards), arg0)
}
// DeleteLabel mocks base method
// DeleteLabel mocks base method.
func (m *MockClient) DeleteLabel(arg0 context.Context, arg1 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteLabel", arg0, arg1)
@ -207,13 +207,13 @@ func (m *MockClient) DeleteLabel(arg0 context.Context, arg1 string) error {
return ret0
}
// DeleteLabel indicates an expected call of DeleteLabel
// DeleteLabel indicates an expected call of DeleteLabel.
func (mr *MockClientMockRecorder) DeleteLabel(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLabel", reflect.TypeOf((*MockClient)(nil).DeleteLabel), arg0, arg1)
}
// DeleteMessages mocks base method
// DeleteMessages mocks base method.
func (m *MockClient) DeleteMessages(arg0 context.Context, arg1 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DeleteMessages", arg0, arg1)
@ -221,13 +221,13 @@ func (m *MockClient) DeleteMessages(arg0 context.Context, arg1 []string) error {
return ret0
}
// DeleteMessages indicates an expected call of DeleteMessages
// DeleteMessages indicates an expected call of DeleteMessages.
func (mr *MockClientMockRecorder) DeleteMessages(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteMessages", reflect.TypeOf((*MockClient)(nil).DeleteMessages), arg0, arg1)
}
// EmptyFolder mocks base method
// EmptyFolder mocks base method.
func (m *MockClient) EmptyFolder(arg0 context.Context, arg1, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EmptyFolder", arg0, arg1, arg2)
@ -235,13 +235,13 @@ func (m *MockClient) EmptyFolder(arg0 context.Context, arg1, arg2 string) error
return ret0
}
// EmptyFolder indicates an expected call of EmptyFolder
// EmptyFolder indicates an expected call of EmptyFolder.
func (mr *MockClientMockRecorder) EmptyFolder(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EmptyFolder", reflect.TypeOf((*MockClient)(nil).EmptyFolder), arg0, arg1, arg2)
}
// GetAddresses mocks base method
// GetAddresses mocks base method.
func (m *MockClient) GetAddresses(arg0 context.Context) (pmapi.AddressList, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAddresses", arg0)
@ -250,13 +250,13 @@ func (m *MockClient) GetAddresses(arg0 context.Context) (pmapi.AddressList, erro
return ret0, ret1
}
// GetAddresses indicates an expected call of GetAddresses
// GetAddresses indicates an expected call of GetAddresses.
func (mr *MockClientMockRecorder) GetAddresses(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAddresses", reflect.TypeOf((*MockClient)(nil).GetAddresses), arg0)
}
// GetAttachment mocks base method
// GetAttachment mocks base method.
func (m *MockClient) GetAttachment(arg0 context.Context, arg1 string) (io.ReadCloser, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAttachment", arg0, arg1)
@ -265,13 +265,13 @@ func (m *MockClient) GetAttachment(arg0 context.Context, arg1 string) (io.ReadCl
return ret0, ret1
}
// GetAttachment indicates an expected call of GetAttachment
// GetAttachment indicates an expected call of GetAttachment.
func (mr *MockClientMockRecorder) GetAttachment(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAttachment", reflect.TypeOf((*MockClient)(nil).GetAttachment), arg0, arg1)
}
// GetContactByID mocks base method
// GetContactByID mocks base method.
func (m *MockClient) GetContactByID(arg0 context.Context, arg1 string) (pmapi.Contact, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetContactByID", arg0, arg1)
@ -280,13 +280,13 @@ func (m *MockClient) GetContactByID(arg0 context.Context, arg1 string) (pmapi.Co
return ret0, ret1
}
// GetContactByID indicates an expected call of GetContactByID
// GetContactByID indicates an expected call of GetContactByID.
func (mr *MockClientMockRecorder) GetContactByID(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContactByID", reflect.TypeOf((*MockClient)(nil).GetContactByID), arg0, arg1)
}
// GetContactEmailByEmail mocks base method
// GetContactEmailByEmail mocks base method.
func (m *MockClient) GetContactEmailByEmail(arg0 context.Context, arg1 string, arg2, arg3 int) ([]pmapi.ContactEmail, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetContactEmailByEmail", arg0, arg1, arg2, arg3)
@ -295,13 +295,13 @@ func (m *MockClient) GetContactEmailByEmail(arg0 context.Context, arg1 string, a
return ret0, ret1
}
// GetContactEmailByEmail indicates an expected call of GetContactEmailByEmail
// GetContactEmailByEmail indicates an expected call of GetContactEmailByEmail.
func (mr *MockClientMockRecorder) GetContactEmailByEmail(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetContactEmailByEmail", reflect.TypeOf((*MockClient)(nil).GetContactEmailByEmail), arg0, arg1, arg2, arg3)
}
// GetEvent mocks base method
// GetEvent mocks base method.
func (m *MockClient) GetEvent(arg0 context.Context, arg1 string) (*pmapi.Event, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetEvent", arg0, arg1)
@ -310,13 +310,13 @@ func (m *MockClient) GetEvent(arg0 context.Context, arg1 string) (*pmapi.Event,
return ret0, ret1
}
// GetEvent indicates an expected call of GetEvent
// GetEvent indicates an expected call of GetEvent.
func (mr *MockClientMockRecorder) GetEvent(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEvent", reflect.TypeOf((*MockClient)(nil).GetEvent), arg0, arg1)
}
// GetMailSettings mocks base method
// GetMailSettings mocks base method.
func (m *MockClient) GetMailSettings(arg0 context.Context) (pmapi.MailSettings, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetMailSettings", arg0)
@ -325,13 +325,13 @@ func (m *MockClient) GetMailSettings(arg0 context.Context) (pmapi.MailSettings,
return ret0, ret1
}
// GetMailSettings indicates an expected call of GetMailSettings
// GetMailSettings indicates an expected call of GetMailSettings.
func (mr *MockClientMockRecorder) GetMailSettings(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMailSettings", reflect.TypeOf((*MockClient)(nil).GetMailSettings), arg0)
}
// GetMessage mocks base method
// GetMessage mocks base method.
func (m *MockClient) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Message, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetMessage", arg0, arg1)
@ -340,13 +340,13 @@ func (m *MockClient) GetMessage(arg0 context.Context, arg1 string) (*pmapi.Messa
return ret0, ret1
}
// GetMessage indicates an expected call of GetMessage
// GetMessage indicates an expected call of GetMessage.
func (mr *MockClientMockRecorder) GetMessage(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockClient)(nil).GetMessage), arg0, arg1)
}
// GetPublicKeysForEmail mocks base method
// GetPublicKeysForEmail mocks base method.
func (m *MockClient) GetPublicKeysForEmail(arg0 context.Context, arg1 string) ([]pmapi.PublicKey, bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetPublicKeysForEmail", arg0, arg1)
@ -356,13 +356,13 @@ func (m *MockClient) GetPublicKeysForEmail(arg0 context.Context, arg1 string) ([
return ret0, ret1, ret2
}
// GetPublicKeysForEmail indicates an expected call of GetPublicKeysForEmail
// GetPublicKeysForEmail indicates an expected call of GetPublicKeysForEmail.
func (mr *MockClientMockRecorder) GetPublicKeysForEmail(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicKeysForEmail", reflect.TypeOf((*MockClient)(nil).GetPublicKeysForEmail), arg0, arg1)
}
// Import mocks base method
// Import mocks base method.
func (m *MockClient) Import(arg0 context.Context, arg1 pmapi.ImportMsgReqs) ([]*pmapi.ImportMsgRes, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Import", arg0, arg1)
@ -371,13 +371,13 @@ func (m *MockClient) Import(arg0 context.Context, arg1 pmapi.ImportMsgReqs) ([]*
return ret0, ret1
}
// Import indicates an expected call of Import
// Import indicates an expected call of Import.
func (mr *MockClientMockRecorder) Import(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Import", reflect.TypeOf((*MockClient)(nil).Import), arg0, arg1)
}
// IsUnlocked mocks base method
// IsUnlocked mocks base method.
func (m *MockClient) IsUnlocked() bool {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsUnlocked")
@ -385,13 +385,13 @@ func (m *MockClient) IsUnlocked() bool {
return ret0
}
// IsUnlocked indicates an expected call of IsUnlocked
// IsUnlocked indicates an expected call of IsUnlocked.
func (mr *MockClientMockRecorder) IsUnlocked() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsUnlocked", reflect.TypeOf((*MockClient)(nil).IsUnlocked))
}
// KeyRingForAddressID mocks base method
// KeyRingForAddressID mocks base method.
func (m *MockClient) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "KeyRingForAddressID", arg0)
@ -400,13 +400,13 @@ func (m *MockClient) KeyRingForAddressID(arg0 string) (*crypto.KeyRing, error) {
return ret0, ret1
}
// KeyRingForAddressID indicates an expected call of KeyRingForAddressID
// KeyRingForAddressID indicates an expected call of KeyRingForAddressID.
func (mr *MockClientMockRecorder) KeyRingForAddressID(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "KeyRingForAddressID", reflect.TypeOf((*MockClient)(nil).KeyRingForAddressID), arg0)
}
// LabelMessages mocks base method
// LabelMessages mocks base method.
func (m *MockClient) LabelMessages(arg0 context.Context, arg1 []string, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LabelMessages", arg0, arg1, arg2)
@ -414,13 +414,13 @@ func (m *MockClient) LabelMessages(arg0 context.Context, arg1 []string, arg2 str
return ret0
}
// LabelMessages indicates an expected call of LabelMessages
// LabelMessages indicates an expected call of LabelMessages.
func (mr *MockClientMockRecorder) LabelMessages(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LabelMessages", reflect.TypeOf((*MockClient)(nil).LabelMessages), arg0, arg1, arg2)
}
// ListLabels mocks base method
// ListLabels mocks base method.
func (m *MockClient) ListLabels(arg0 context.Context) ([]*pmapi.Label, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListLabels", arg0)
@ -429,13 +429,13 @@ func (m *MockClient) ListLabels(arg0 context.Context) ([]*pmapi.Label, error) {
return ret0, ret1
}
// ListLabels indicates an expected call of ListLabels
// ListLabels indicates an expected call of ListLabels.
func (mr *MockClientMockRecorder) ListLabels(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLabels", reflect.TypeOf((*MockClient)(nil).ListLabels), arg0)
}
// ListMessages mocks base method
// ListMessages mocks base method.
func (m *MockClient) ListMessages(arg0 context.Context, arg1 *pmapi.MessagesFilter) ([]*pmapi.Message, int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListMessages", arg0, arg1)
@ -445,13 +445,13 @@ func (m *MockClient) ListMessages(arg0 context.Context, arg1 *pmapi.MessagesFilt
return ret0, ret1, ret2
}
// ListMessages indicates an expected call of ListMessages
// ListMessages indicates an expected call of ListMessages.
func (mr *MockClientMockRecorder) ListMessages(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMessages", reflect.TypeOf((*MockClient)(nil).ListMessages), arg0, arg1)
}
// MarkMessagesRead mocks base method
// MarkMessagesRead mocks base method.
func (m *MockClient) MarkMessagesRead(arg0 context.Context, arg1 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MarkMessagesRead", arg0, arg1)
@ -459,13 +459,13 @@ func (m *MockClient) MarkMessagesRead(arg0 context.Context, arg1 []string) error
return ret0
}
// MarkMessagesRead indicates an expected call of MarkMessagesRead
// MarkMessagesRead indicates an expected call of MarkMessagesRead.
func (mr *MockClientMockRecorder) MarkMessagesRead(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkMessagesRead", reflect.TypeOf((*MockClient)(nil).MarkMessagesRead), arg0, arg1)
}
// MarkMessagesUnread mocks base method
// MarkMessagesUnread mocks base method.
func (m *MockClient) MarkMessagesUnread(arg0 context.Context, arg1 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MarkMessagesUnread", arg0, arg1)
@ -473,13 +473,13 @@ func (m *MockClient) MarkMessagesUnread(arg0 context.Context, arg1 []string) err
return ret0
}
// MarkMessagesUnread indicates an expected call of MarkMessagesUnread
// MarkMessagesUnread indicates an expected call of MarkMessagesUnread.
func (mr *MockClientMockRecorder) MarkMessagesUnread(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkMessagesUnread", reflect.TypeOf((*MockClient)(nil).MarkMessagesUnread), arg0, arg1)
}
// ReloadKeys mocks base method
// ReloadKeys mocks base method.
func (m *MockClient) ReloadKeys(arg0 context.Context, arg1 []byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReloadKeys", arg0, arg1)
@ -487,13 +487,13 @@ func (m *MockClient) ReloadKeys(arg0 context.Context, arg1 []byte) error {
return ret0
}
// ReloadKeys indicates an expected call of ReloadKeys
// ReloadKeys indicates an expected call of ReloadKeys.
func (mr *MockClientMockRecorder) ReloadKeys(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReloadKeys", reflect.TypeOf((*MockClient)(nil).ReloadKeys), arg0, arg1)
}
// ReorderAddresses mocks base method
// ReorderAddresses mocks base method.
func (m *MockClient) ReorderAddresses(arg0 context.Context, arg1 []string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReorderAddresses", arg0, arg1)
@ -501,13 +501,13 @@ func (m *MockClient) ReorderAddresses(arg0 context.Context, arg1 []string) error
return ret0
}
// ReorderAddresses indicates an expected call of ReorderAddresses
// ReorderAddresses indicates an expected call of ReorderAddresses.
func (mr *MockClientMockRecorder) ReorderAddresses(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReorderAddresses", reflect.TypeOf((*MockClient)(nil).ReorderAddresses), arg0, arg1)
}
// SendMessage mocks base method
// SendMessage mocks base method.
func (m *MockClient) SendMessage(arg0 context.Context, arg1 string, arg2 *pmapi.SendMessageReq) (*pmapi.Message, *pmapi.Message, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SendMessage", arg0, arg1, arg2)
@ -517,13 +517,13 @@ func (m *MockClient) SendMessage(arg0 context.Context, arg1 string, arg2 *pmapi.
return ret0, ret1, ret2
}
// SendMessage indicates an expected call of SendMessage
// SendMessage indicates an expected call of SendMessage.
func (mr *MockClientMockRecorder) SendMessage(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockClient)(nil).SendMessage), arg0, arg1, arg2)
}
// UnlabelMessages mocks base method
// UnlabelMessages mocks base method.
func (m *MockClient) UnlabelMessages(arg0 context.Context, arg1 []string, arg2 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UnlabelMessages", arg0, arg1, arg2)
@ -531,13 +531,13 @@ func (m *MockClient) UnlabelMessages(arg0 context.Context, arg1 []string, arg2 s
return ret0
}
// UnlabelMessages indicates an expected call of UnlabelMessages
// UnlabelMessages indicates an expected call of UnlabelMessages.
func (mr *MockClientMockRecorder) UnlabelMessages(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlabelMessages", reflect.TypeOf((*MockClient)(nil).UnlabelMessages), arg0, arg1, arg2)
}
// Unlock mocks base method
// Unlock mocks base method.
func (m *MockClient) Unlock(arg0 context.Context, arg1 []byte) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Unlock", arg0, arg1)
@ -545,13 +545,13 @@ func (m *MockClient) Unlock(arg0 context.Context, arg1 []byte) error {
return ret0
}
// Unlock indicates an expected call of Unlock
// Unlock indicates an expected call of Unlock.
func (mr *MockClientMockRecorder) Unlock(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unlock", reflect.TypeOf((*MockClient)(nil).Unlock), arg0, arg1)
}
// UpdateLabel mocks base method
// UpdateLabel mocks base method.
func (m *MockClient) UpdateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmapi.Label, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateLabel", arg0, arg1)
@ -560,13 +560,13 @@ func (m *MockClient) UpdateLabel(arg0 context.Context, arg1 *pmapi.Label) (*pmap
return ret0, ret1
}
// UpdateLabel indicates an expected call of UpdateLabel
// UpdateLabel indicates an expected call of UpdateLabel.
func (mr *MockClientMockRecorder) UpdateLabel(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLabel", reflect.TypeOf((*MockClient)(nil).UpdateLabel), arg0, arg1)
}
// UpdateUser mocks base method
// UpdateUser mocks base method.
func (m *MockClient) UpdateUser(arg0 context.Context) (*pmapi.User, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateUser", arg0)
@ -575,72 +575,72 @@ func (m *MockClient) UpdateUser(arg0 context.Context) (*pmapi.User, error) {
return ret0, ret1
}
// UpdateUser indicates an expected call of UpdateUser
// UpdateUser indicates an expected call of UpdateUser.
func (mr *MockClientMockRecorder) UpdateUser(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateUser", reflect.TypeOf((*MockClient)(nil).UpdateUser), arg0)
}
// MockManager is a mock of Manager interface
// MockManager is a mock of Manager interface.
type MockManager struct {
ctrl *gomock.Controller
recorder *MockManagerMockRecorder
}
// MockManagerMockRecorder is the mock recorder for MockManager
// MockManagerMockRecorder is the mock recorder for MockManager.
type MockManagerMockRecorder struct {
mock *MockManager
}
// NewMockManager creates a new mock instance
// NewMockManager creates a new mock instance.
func NewMockManager(ctrl *gomock.Controller) *MockManager {
mock := &MockManager{ctrl: ctrl}
mock.recorder = &MockManagerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockManager) EXPECT() *MockManagerMockRecorder {
return m.recorder
}
// AddConnectionObserver mocks base method
// AddConnectionObserver mocks base method.
func (m *MockManager) AddConnectionObserver(arg0 pmapi.ConnectionObserver) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "AddConnectionObserver", arg0)
}
// AddConnectionObserver indicates an expected call of AddConnectionObserver
// AddConnectionObserver indicates an expected call of AddConnectionObserver.
func (mr *MockManagerMockRecorder) AddConnectionObserver(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddConnectionObserver", reflect.TypeOf((*MockManager)(nil).AddConnectionObserver), arg0)
}
// AllowProxy mocks base method
// AllowProxy mocks base method.
func (m *MockManager) AllowProxy() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "AllowProxy")
}
// AllowProxy indicates an expected call of AllowProxy
// AllowProxy indicates an expected call of AllowProxy.
func (mr *MockManagerMockRecorder) AllowProxy() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllowProxy", reflect.TypeOf((*MockManager)(nil).AllowProxy))
}
// DisallowProxy mocks base method
// DisallowProxy mocks base method.
func (m *MockManager) DisallowProxy() {
m.ctrl.T.Helper()
m.ctrl.Call(m, "DisallowProxy")
}
// DisallowProxy indicates an expected call of DisallowProxy
// DisallowProxy indicates an expected call of DisallowProxy.
func (mr *MockManagerMockRecorder) DisallowProxy() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisallowProxy", reflect.TypeOf((*MockManager)(nil).DisallowProxy))
}
// DownloadAndVerify mocks base method
// DownloadAndVerify mocks base method.
func (m *MockManager) DownloadAndVerify(arg0 *crypto.KeyRing, arg1, arg2 string) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DownloadAndVerify", arg0, arg1, arg2)
@ -649,13 +649,13 @@ func (m *MockManager) DownloadAndVerify(arg0 *crypto.KeyRing, arg1, arg2 string)
return ret0, ret1
}
// DownloadAndVerify indicates an expected call of DownloadAndVerify
// DownloadAndVerify indicates an expected call of DownloadAndVerify.
func (mr *MockManagerMockRecorder) DownloadAndVerify(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadAndVerify", reflect.TypeOf((*MockManager)(nil).DownloadAndVerify), arg0, arg1, arg2)
}
// NewClient mocks base method
// NewClient mocks base method.
func (m *MockManager) NewClient(arg0, arg1, arg2 string, arg3 time.Time) pmapi.Client {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NewClient", arg0, arg1, arg2, arg3)
@ -663,13 +663,13 @@ func (m *MockManager) NewClient(arg0, arg1, arg2 string, arg3 time.Time) pmapi.C
return ret0
}
// NewClient indicates an expected call of NewClient
// NewClient indicates an expected call of NewClient.
func (mr *MockManagerMockRecorder) NewClient(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClient", reflect.TypeOf((*MockManager)(nil).NewClient), arg0, arg1, arg2, arg3)
}
// NewClientWithLogin mocks base method
// NewClientWithLogin mocks base method.
func (m *MockManager) NewClientWithLogin(arg0 context.Context, arg1 string, arg2 []byte) (pmapi.Client, *pmapi.Auth, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NewClientWithLogin", arg0, arg1, arg2)
@ -679,13 +679,13 @@ func (m *MockManager) NewClientWithLogin(arg0 context.Context, arg1 string, arg2
return ret0, ret1, ret2
}
// NewClientWithLogin indicates an expected call of NewClientWithLogin
// NewClientWithLogin indicates an expected call of NewClientWithLogin.
func (mr *MockManagerMockRecorder) NewClientWithLogin(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClientWithLogin", reflect.TypeOf((*MockManager)(nil).NewClientWithLogin), arg0, arg1, arg2)
}
// NewClientWithRefresh mocks base method
// NewClientWithRefresh mocks base method.
func (m *MockManager) NewClientWithRefresh(arg0 context.Context, arg1, arg2 string) (pmapi.Client, *pmapi.AuthRefresh, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "NewClientWithRefresh", arg0, arg1, arg2)
@ -695,13 +695,13 @@ func (m *MockManager) NewClientWithRefresh(arg0 context.Context, arg1, arg2 stri
return ret0, ret1, ret2
}
// NewClientWithRefresh indicates an expected call of NewClientWithRefresh
// NewClientWithRefresh indicates an expected call of NewClientWithRefresh.
func (mr *MockManagerMockRecorder) NewClientWithRefresh(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewClientWithRefresh", reflect.TypeOf((*MockManager)(nil).NewClientWithRefresh), arg0, arg1, arg2)
}
// ReportBug mocks base method
// ReportBug mocks base method.
func (m *MockManager) ReportBug(arg0 context.Context, arg1 pmapi.ReportBugReq) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReportBug", arg0, arg1)
@ -709,13 +709,13 @@ func (m *MockManager) ReportBug(arg0 context.Context, arg1 pmapi.ReportBugReq) e
return ret0
}
// ReportBug indicates an expected call of ReportBug
// ReportBug indicates an expected call of ReportBug.
func (mr *MockManagerMockRecorder) ReportBug(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportBug", reflect.TypeOf((*MockManager)(nil).ReportBug), arg0, arg1)
}
// SendSimpleMetric mocks base method
// SendSimpleMetric mocks base method.
func (m *MockManager) SendSimpleMetric(arg0 context.Context, arg1, arg2, arg3 string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SendSimpleMetric", arg0, arg1, arg2, arg3)
@ -723,55 +723,55 @@ func (m *MockManager) SendSimpleMetric(arg0 context.Context, arg1, arg2, arg3 st
return ret0
}
// SendSimpleMetric indicates an expected call of SendSimpleMetric
// SendSimpleMetric indicates an expected call of SendSimpleMetric.
func (mr *MockManagerMockRecorder) SendSimpleMetric(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendSimpleMetric", reflect.TypeOf((*MockManager)(nil).SendSimpleMetric), arg0, arg1, arg2, arg3)
}
// SetCookieJar mocks base method
// SetCookieJar mocks base method.
func (m *MockManager) SetCookieJar(arg0 http.CookieJar) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetCookieJar", arg0)
}
// SetCookieJar indicates an expected call of SetCookieJar
// SetCookieJar indicates an expected call of SetCookieJar.
func (mr *MockManagerMockRecorder) SetCookieJar(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCookieJar", reflect.TypeOf((*MockManager)(nil).SetCookieJar), arg0)
}
// SetLogging mocks base method
// SetLogging mocks base method.
func (m *MockManager) SetLogging(arg0 *logrus.Entry, arg1 bool) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetLogging", arg0, arg1)
}
// SetLogging indicates an expected call of SetLogging
// SetLogging indicates an expected call of SetLogging.
func (mr *MockManagerMockRecorder) SetLogging(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogging", reflect.TypeOf((*MockManager)(nil).SetLogging), arg0, arg1)
}
// SetRetryCount mocks base method
// SetRetryCount mocks base method.
func (m *MockManager) SetRetryCount(arg0 int) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetRetryCount", arg0)
}
// SetRetryCount indicates an expected call of SetRetryCount
// SetRetryCount indicates an expected call of SetRetryCount.
func (mr *MockManagerMockRecorder) SetRetryCount(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetRetryCount", reflect.TypeOf((*MockManager)(nil).SetRetryCount), arg0)
}
// SetTransport mocks base method
// SetTransport mocks base method.
func (m *MockManager) SetTransport(arg0 http.RoundTripper) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "SetTransport", arg0)
}
// SetTransport indicates an expected call of SetTransport
// SetTransport indicates an expected call of SetTransport.
func (mr *MockManagerMockRecorder) SetTransport(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTransport", reflect.TypeOf((*MockManager)(nil).SetTransport), arg0)

View File

@ -23,6 +23,7 @@ import (
"strconv"
"time"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/go-resty/resty/v2"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -71,6 +72,24 @@ func (m *manager) catchAPIError(_ *resty.Client, res *resty.Response) error {
return err
}
func updateTime(_ *resty.Client, res *resty.Response) error {
if date, err := time.Parse(time.RFC1123, res.Header().Get("Date")); err != nil {
log.WithError(err).Warning("Cannot parse header date")
} else {
crypto.UpdateTime(date.Unix())
}
return nil
}
func logConnReuse(_ *resty.Client, res *resty.Response) error {
if !res.Request.TraceInfo().IsConnReused {
logrus.WithField("host", res.Request.URL).Trace("Connection was NOT reused")
}
return nil
}
func catchRetryAfter(_ *resty.Client, res *resty.Response) (time.Duration, error) {
if res.StatusCode() == http.StatusTooManyRequests {
if after := res.Header().Get("Retry-After"); after != "" {

View File

@ -1,15 +1,52 @@
## v1.8.3
- 2021-05-31
### New
- Improved moving messages from other accounts to ProtonMail - implemented new parser for processing such messages
- Performance improvements
## v1.8.9
- 2021-09-01
### Fixed
- Sync issue with Microsoft Outlook (changed the order of processing requests)
- Fetching the bodies of non-multipart messages
- Fixed an issues with incorrect handling of 401 server error leading to random Bridge logouts
- Changed encoding of message/rfc822 - to better handle sending of the .msg files
- Fixed crash within RFC822 builder for invalid or empty headers
- Fixed crash within RFC822 builder for header with key length > 76 chars
## v1.8.7
- 2021-06-22
### New
- Updated crypto-libraries to gopenpgp/v2 v2.1.10
### Fixed
- Fixed IMAP/SMTP restart in Bridge to mitigate connection issues
- Fixed unknown charset error for 'combined' messages
- Implemented a long-term fix for 'invalid or missing message signature' error
## v1.8.5
- 2021-06-11
### New
- Updated golang Secure Remote Password Protocol
- Updated crypto-libraries to gopenpgp/v2 v2.1.9
- Implemented new message parser (for imports from external accounts)
### Fixed
- Bridge not to strip PGP signatures of incoming clear text messages
- Import of messages with malformed MIME header
- Improved parsing of message headers
- Fetching bodies of non-multipart messages
- Sync and performance improvements
## v1.8.3
- 2021-05-27
### Fixed
- A bug with sending encrypted emails to external contacts
## v1.8.2

View File

@ -1,3 +1,32 @@
## v1.8.7
- 2021-06-24
### New
- Updated golang Secure Remote Password Protocol
- Updated crypto-libraries to gopenpgp/v2 v2.1.10
- Implemented new message parser (for imports from external accounts)
### Fixed
- Fixed IMAP/SMTP restart in Bridge to mitigate connection issues
- Fixed unknown charset error for 'combined' messages
- Implemented a long-term fix for 'invalid or missing message signature' error
- Bridge not to strip PGP signatures of incoming clear text messages
- Import of messages with malformed MIME header
- Improved parsing of message headers
- Fetching bodies of non-multipart messages
- Sync and performance improvements
## v1.8.3
- 2021-05-27
### Fixed
- A bug with sending encrypted emails to external contacts
## v1.8.2
- 2021-05-21

View File

@ -1,7 +1,7 @@
.PHONY: check-go check-godog install-godog test test-bridge test-ie test-live test-live-bridge test-live-ie test-stage test-debug test-live-debug bench
export GO111MODULE=on
export BRIDGE_VERSION:=1.5.5+integrationtests
export BRIDGE_VERSION:=1.8.10+integrationtests
export VERBOSITY?=fatal
export TEST_DATA=testdata
export TEST_APP?=bridge
@ -14,7 +14,7 @@ check-go:
check-godog:
@which godog || $(MAKE) install-godog
install-godog: check-go
go get github.com/cucumber/godog/cmd/godog@v0.8.1
go get github.com/cucumber/godog/cmd/godog@v0.12.1
test: test-bridge test-ie
test-bridge: FEATURES ?= features/bridge

View File

@ -23,7 +23,7 @@ import (
"github.com/cucumber/godog"
)
func APIActionsFeatureContext(s *godog.Suite) {
func APIActionsFeatureContext(s *godog.ScenarioContext) {
s.Step(`^the internet connection is lost$`, theInternetConnectionIsLost)
s.Step(`^the internet connection is restored$`, theInternetConnectionIsRestored)
s.Step(`^(\d+) second[s]? pass$`, secondsPass)

View File

@ -27,16 +27,16 @@ import (
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/test/accounts"
"github.com/cucumber/godog"
"github.com/cucumber/godog/gherkin"
"github.com/stretchr/testify/assert"
)
func APIChecksFeatureContext(s *godog.Suite) {
func APIChecksFeatureContext(s *godog.ScenarioContext) {
s.Step(`^API endpoint "([^"]*)" is called$`, apiIsCalled)
s.Step(`^API endpoint "([^"]*)" is called with$`, apiIsCalledWith)
s.Step(`^API endpoint "([^"]*)" is not called$`, apiIsNotCalled)
s.Step(`^API endpoint "([^"]*)" is not called with$`, apiIsNotCalledWith)
s.Step(`^message is sent with API call$`, messageIsSentWithAPICall)
s.Step(`^packages are sent with API call$`, packagesAreSentWithAPICall)
s.Step(`^API mailbox "([^"]*)" for "([^"]*)" has (\d+) message(?:s)?$`, apiMailboxForUserHasNumberOfMessages)
s.Step(`^API mailbox "([^"]*)" for address "([^"]*)" of "([^"]*)" has (\d+) message(?:s)?$`, apiMailboxForAddressOfUserHasNumberOfMessages)
s.Step(`^API mailbox "([^"]*)" for "([^"]*)" has messages$`, apiMailboxForUserHasMessages)
@ -51,13 +51,24 @@ func apiIsCalled(endpoint string) error {
return nil
}
func apiIsCalledWith(endpoint string, data *gherkin.DocString) error {
func apiIsCalledWith(endpoint string, data *godog.DocString) error {
if !apiIsCalledWithHelper(endpoint, data.Content) {
return fmt.Errorf("%s was not called with %s", endpoint, data.Content)
}
return nil
}
func apiIsCalledWithRegex(endpoint string, data *godog.DocString) error {
match, err := apiIsCalledWithHelperRegex(endpoint, data.Content)
if err != nil {
return err
}
if !match {
return fmt.Errorf("%s was not called with %s", endpoint, data.Content)
}
return nil
}
func apiIsNotCalled(endpoint string) error {
if apiIsCalledWithHelper(endpoint, "") {
return fmt.Errorf("%s was called", endpoint)
@ -65,7 +76,7 @@ func apiIsNotCalled(endpoint string) error {
return nil
}
func apiIsNotCalledWith(endpoint string, data *gherkin.DocString) error {
func apiIsNotCalledWith(endpoint string, data *godog.DocString) error {
if apiIsCalledWithHelper(endpoint, data.Content) {
return fmt.Errorf("%s was called with %s", endpoint, data.Content)
}
@ -80,7 +91,15 @@ func apiIsCalledWithHelper(endpoint string, content string) bool {
return ctx.GetPMAPIController().WasCalled(method, path, request)
}
func messageIsSentWithAPICall(data *gherkin.DocString) error {
func apiIsCalledWithHelperRegex(endpoint string, content string) (bool, error) {
split := strings.Split(endpoint, " ")
method := split[0]
path := split[1]
request := []byte(content)
return ctx.GetPMAPIController().WasCalledRegex(method, path, request)
}
func messageIsSentWithAPICall(data *godog.DocString) error {
endpoint := "POST /mail/v4/messages"
if err := apiIsCalledWith(endpoint, data); err != nil {
return err
@ -94,6 +113,20 @@ func messageIsSentWithAPICall(data *gherkin.DocString) error {
return nil
}
func packagesAreSentWithAPICall(data *godog.DocString) error {
endpoint := "POST /mail/v4/messages/.+$"
if err := apiIsCalledWithRegex(endpoint, data); err != nil {
return err
}
for _, request := range ctx.GetPMAPIController().GetCalls("POST", "/mail/v4/messages") {
if !checkAllRequiredFieldsForSendingMessage(request) {
return fmt.Errorf("%s was not called with all required fields: %s", endpoint, request)
}
}
return nil
}
func checkAllRequiredFieldsForSendingMessage(request []byte) bool {
if matches := regexp.MustCompile(`"Subject":`).Match(request); !matches {
return false
@ -145,11 +178,11 @@ func apiMailboxForAddressOfUserHasNumberOfMessages(mailboxName, bddAddressID, bd
return nil
}
func apiMailboxForUserHasMessages(mailboxName, bddUserID string, messages *gherkin.DataTable) error {
func apiMailboxForUserHasMessages(mailboxName, bddUserID string, messages *godog.Table) error {
return apiMailboxForAddressOfUserHasMessages(mailboxName, "", bddUserID, messages)
}
func apiMailboxForAddressOfUserHasMessages(mailboxName, bddAddressID, bddUserID string, messages *gherkin.DataTable) error {
func apiMailboxForAddressOfUserHasMessages(mailboxName, bddAddressID, bddUserID string, messages *godog.Table) error {
account := ctx.GetTestAccountWithAddress(bddUserID, bddAddressID)
if account == nil {
return godog.ErrPending

View File

@ -21,7 +21,7 @@ import (
"github.com/cucumber/godog"
)
func APISetupFeatureContext(s *godog.Suite) {
func APISetupFeatureContext(s *godog.ScenarioContext) {
s.Step(`^there is no internet connection$`, thereIsNoInternetConnection)
}

View File

@ -18,9 +18,10 @@
package tests
import (
"context"
"os"
"github.com/ProtonMail/proton-bridge/test/context"
testContext "github.com/ProtonMail/proton-bridge/test/context"
"github.com/cucumber/godog"
)
@ -28,9 +29,14 @@ const (
timeFormat = "2006-01-02T15:04:05"
)
func FeatureContext(s *godog.Suite) {
s.BeforeScenario(beforeScenario)
s.AfterScenario(afterScenario)
func SuiteInitializer(s *godog.TestSuiteContext) {
s.BeforeSuite(testContext.BeforeRun)
s.AfterSuite(testContext.AfterRun)
}
func ScenarioInitializer(s *godog.ScenarioContext) {
s.Before(beforeScenario)
s.After(afterScenario)
APIActionsFeatureContext(s)
APIChecksFeatureContext(s)
@ -63,16 +69,16 @@ func FeatureContext(s *godog.Suite) {
UsersChecksFeatureContext(s)
}
var ctx *context.TestContext //nolint[gochecknoglobals]
var ctx *testContext.TestContext //nolint[gochecknoglobals]
func beforeScenario(scenario interface{}) {
// bridge or ie. With godog 0.10.x and later it can be determined from
// scenario.Uri and its file location.
func beforeScenario(scenarioCtx context.Context, _ *godog.Scenario) (context.Context, error) {
// NOTE(GODT-219) It would be possible to optimised the usage of godog with our context.
app := os.Getenv("TEST_APP")
ctx = context.New(app)
ctx = testContext.New(app)
return scenarioCtx, nil
}
func afterScenario(scenario interface{}, err error) {
func afterScenario(scenarioCtx context.Context, _ *godog.Scenario, err error) (context.Context, error) {
if err != nil {
for _, user := range ctx.GetUsers().GetUsers() {
store := user.GetStore()
@ -85,4 +91,6 @@ func afterScenario(scenario interface{}, err error) {
if err != nil {
ctx.GetPMAPIController().PrintCalls()
}
return scenarioCtx, err
}

View File

@ -35,7 +35,7 @@ func benchTestContext() (*context.TestContext, *mocks.IMAPClient) {
panic("account " + username + " does not exist")
}
_ = ctx.GetPMAPIController().AddUser(account.User(), account.Addresses(), account.Password(), account.IsTwoFAEnabled())
_ = ctx.GetPMAPIController().AddUser(account)
if err := ctx.LoginUser(account.Username(), account.Password(), account.MailboxPassword()); err != nil {
panic(err)
}

View File

@ -21,7 +21,7 @@ import (
"github.com/cucumber/godog"
)
func BridgeActionsFeatureContext(s *godog.Suite) {
func BridgeActionsFeatureContext(s *godog.ScenarioContext) {
s.Step(`^bridge starts$`, bridgeStarts)
s.Step(`^bridge syncs "([^"]*)"$`, bridgeSyncsUser)
}

View File

@ -22,7 +22,7 @@ import (
a "github.com/stretchr/testify/assert"
)
func CommonChecksFeatureContext(s *godog.Suite) {
func CommonChecksFeatureContext(s *godog.ScenarioContext) {
s.Step(`^last response is "([^"]*)"$`, lastResponseIs)
}

View File

@ -0,0 +1,99 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package calls
import (
"fmt"
"regexp"
"github.com/nsf/jsondiff"
)
type CallRecord struct {
method string
path string
request []byte
}
type Calls []CallRecord
func (c *Calls) Register(method, path string, request []byte) {
*c = append(*c, CallRecord{
method: method,
path: path,
request: request,
})
}
func (c *Calls) PrintCalls() {
fmt.Println("API calls:")
for idx, call := range *c {
fmt.Printf("%02d: [%s] %s\n", idx+1, call.method, call.path)
if call.request != nil && string(call.request) != "null" {
fmt.Printf("\t%s\n", call.request)
}
}
}
func (c *Calls) WasCalled(method, path string, expectedRequest []byte) bool {
res, err := c.WasCalledRegex("^"+regexp.QuoteMeta(method)+"$", "^"+regexp.QuoteMeta(path)+"$", expectedRequest)
if err != nil {
panic(err)
}
return res
}
func (c *Calls) WasCalledRegex(methodRegex, pathRegex string, expectedRequest []byte) (bool, error) {
for _, call := range *c {
matched, err := regexp.Match(methodRegex, []byte(call.method))
if err != nil {
return false, err
}
if !matched {
continue
}
matched, err = regexp.Match(pathRegex, []byte(call.path))
if err != nil {
return false, err
}
if !matched {
continue
}
if string(expectedRequest) == "" {
return true, nil
}
diff, _ := jsondiff.Compare(call.request, expectedRequest, &jsondiff.Options{})
isSuperset := diff == jsondiff.FullMatch || diff == jsondiff.SupersetMatch
if isSuperset {
return true, nil
}
}
return false, nil
}
func (c *Calls) GetCalls(method, path string) [][]byte {
requests := [][]byte{}
for _, call := range *c {
if call.method == method && call.path == path {
requests = append(requests, call.request)
}
}
return requests
}

View File

@ -94,8 +94,6 @@ type TestContext struct {
// New returns a new test TestContext.
func New(app string) *TestContext {
setLogrusVerbosityFromEnv()
listener := listener.New()
pmapiController, clientManager := newPMAPIController(app, listener)

40
test/context/globals.go Normal file
View File

@ -0,0 +1,40 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package context
import (
"os"
"github.com/ProtonMail/proton-bridge/test/liveapi"
)
// BeforeRun does necessary setup.
func BeforeRun() {
setLogrusVerbosityFromEnv()
if os.Getenv(EnvName) == EnvLive {
liveapi.SetupPersistentClients()
}
}
// AfterRun does necessary cleanup.
func AfterRun() {
if os.Getenv(EnvName) == EnvLive {
liveapi.CleanupPersistentClients()
}
}

View File

@ -23,6 +23,7 @@ import (
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/test/accounts"
"github.com/ProtonMail/proton-bridge/test/fakeapi"
"github.com/ProtonMail/proton-bridge/test/liveapi"
)
@ -30,7 +31,8 @@ import (
type PMAPIController interface {
TurnInternetConnectionOff()
TurnInternetConnectionOn()
AddUser(user *pmapi.User, addresses *pmapi.AddressList, password []byte, twoFAEnabled bool) error
GetAuthClient(username string) pmapi.Client
AddUser(account *accounts.TestAccount) error
AddUserLabel(username string, label *pmapi.Label) error
GetLabelIDs(username string, labelNames []string) ([]string, error)
AddUserMessage(username string, message *pmapi.Message) (string, error)
@ -38,6 +40,7 @@ type PMAPIController interface {
ReorderAddresses(user *pmapi.User, addressIDs []string) error
PrintCalls()
WasCalled(method, path string, expectedRequest []byte) bool
WasCalledRegex(methodRegex, pathRegex string, expectedRequest []byte) (bool, error)
GetCalls(method, path string) [][]byte
}

View File

@ -27,6 +27,7 @@ import (
"github.com/ProtonMail/go-srp"
"github.com/ProtonMail/proton-bridge/internal/store"
"github.com/ProtonMail/proton-bridge/internal/users"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
@ -61,6 +62,27 @@ func (ctx *TestContext) LoginUser(username string, password, mailboxPassword []b
return nil
}
// FinishLogin prevents authentication if not necessary.
func (ctx *TestContext) FinishLogin(client pmapi.Client, mailboxPassword []byte) error {
type currentAuthGetter interface {
GetCurrentAuth() *pmapi.Auth
}
c, ok := client.(currentAuthGetter)
if c == nil || !ok {
return errors.New("cannot get current auth tokens from client")
}
user, err := ctx.users.FinishLogin(client, c.GetCurrentAuth(), mailboxPassword)
if err != nil {
return errors.Wrap(err, "failed to finish login")
}
ctx.addCleanupChecked(user.Logout, "Logging out user")
return nil
}
// GetUser retrieves the bridge user matching the given query string.
func (ctx *TestContext) GetUser(username string) (*users.User, error) {
return ctx.users.GetUser(username)

View File

@ -75,5 +75,10 @@ func (api *FakePMAPI) CreateAttachment(_ context.Context, attachment *pmapi.Atta
return nil, err
}
attachment.KeyPackets = base64.StdEncoding.EncodeToString(bytes)
msg := api.getMessage(attachment.MessageID)
if msg == nil {
return nil, fmt.Errorf("no such message ID %q", attachment.MessageID)
}
msg.Attachments = append(msg.Attachments, attachment)
return attachment, nil
}

View File

@ -63,3 +63,13 @@ func (api *FakePMAPI) AuthDelete(_ context.Context) error {
return nil
}
func (api *FakePMAPI) GetCurrentAuth() *pmapi.Auth {
return &pmapi.Auth{
UserID: api.userID,
AuthRefresh: pmapi.AuthRefresh{
UID: api.uid,
RefreshToken: api.ref,
},
}
}

View File

@ -21,14 +21,17 @@ import (
"sync"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/test/context/calls"
"github.com/sirupsen/logrus"
)
// Controller implements dummy PMAPIController interface without actual
// endpoint.
type Controller struct {
// Internal states.
lock *sync.RWMutex
fakeAPIs []*FakePMAPI
calls []*fakeCall
calls calls.Calls
labelIDGenerator idGenerator
messageIDGenerator idGenerator
tokenGenerator idGenerator
@ -50,7 +53,7 @@ func NewController() (*Controller, pmapi.Manager) {
controller := &Controller{
lock: &sync.RWMutex{},
fakeAPIs: []*FakePMAPI{},
calls: []*fakeCall{},
calls: calls.Calls{},
labelIDGenerator: 100, // We cannot use system label IDs.
messageIDGenerator: 0,
tokenGenerator: 1000, // No specific reason; 1000 simply feels right.

View File

@ -19,10 +19,8 @@ package fakeapi
import (
"encoding/json"
"fmt"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/nsf/jsondiff"
)
type method string
@ -34,12 +32,6 @@ const (
DELETE method = "DELETE"
)
type fakeCall struct {
method method
path string
request []byte
}
func (ctl *Controller) checkAndRecordCall(method method, path string, req interface{}) error {
ctl.lock.Lock()
defer ctl.lock.Unlock()
@ -54,11 +46,7 @@ func (ctl *Controller) checkAndRecordCall(method method, path string, req interf
}
}
ctl.calls = append(ctl.calls, &fakeCall{
method: method,
path: path,
request: request,
})
ctl.calls.Register(string(method), path, request)
if ctl.noInternetConnection {
return pmapi.ErrNoConnection
@ -68,38 +56,17 @@ func (ctl *Controller) checkAndRecordCall(method method, path string, req interf
}
func (ctl *Controller) PrintCalls() {
fmt.Println("API calls:")
for idx, call := range ctl.calls {
fmt.Printf("%02d: [%s] %s\n", idx+1, call.method, call.path)
if call.request != nil && string(call.request) != "null" {
fmt.Printf("\t%s\n", call.request)
}
}
ctl.calls.PrintCalls()
}
func (ctl *Controller) WasCalled(method, path string, expectedRequest []byte) bool {
for _, call := range ctl.calls {
if string(call.method) != method || call.path != path {
continue
}
if string(expectedRequest) == "" {
return true
}
diff, _ := jsondiff.Compare(call.request, expectedRequest, &jsondiff.Options{})
isSuperset := diff == jsondiff.FullMatch || diff == jsondiff.SupersetMatch
if isSuperset {
return true
}
}
return false
return ctl.calls.WasCalled(method, path, expectedRequest)
}
func (ctl *Controller) WasCalledRegex(methodRegex, pathRegex string, expectedRequest []byte) (bool, error) {
return ctl.calls.WasCalledRegex(methodRegex, pathRegex, expectedRequest)
}
func (ctl *Controller) GetCalls(method, path string) [][]byte {
requests := [][]byte{}
for _, call := range ctl.calls {
if string(call.method) == method && call.path == path {
requests = append(requests, call.request)
}
}
return requests
return ctl.calls.GetCalls(method, path)
}

View File

@ -22,8 +22,10 @@ import (
"errors"
"fmt"
"strings"
"time"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/test/accounts"
)
var systemLabelNameToID = map[string]string{ //nolint[gochecknoglobals]
@ -61,13 +63,15 @@ func (ctl *Controller) ReorderAddresses(user *pmapi.User, addressIDs []string) e
return api.ReorderAddresses(context.Background(), addressIDs)
}
func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password []byte, twoFAEnabled bool) error {
ctl.usersByUsername[user.Name] = &fakeUser{
user: user,
password: password,
has2FA: twoFAEnabled,
func (ctl *Controller) AddUser(account *accounts.TestAccount) error {
ctl.usersByUsername[account.User().Name] = &fakeUser{
user: account.User(),
password: account.Password(),
has2FA: account.IsTwoFAEnabled(),
}
ctl.addressesByUsername[user.Name] = addresses
ctl.addressesByUsername[account.User().Name] = account.Addresses()
ctl.createSession(account.User().Name, true)
return nil
}
@ -181,3 +185,15 @@ func (ctl *Controller) GetMessages(username, labelID string) ([]*pmapi.Message,
}
return messages, nil
}
func (ctl *Controller) GetAuthClient(username string) pmapi.Client {
for uid, session := range ctl.sessionsByUID {
if session.username == username {
return ctl.clientManager.NewClient(uid, session.acc, session.ref, time.Now())
}
}
ctl.log.WithField("username", username).Fatal("Cannot get authenticated client.")
return nil
}

View File

@ -51,24 +51,25 @@ func (ctl *Controller) checkScope(uid string) bool {
}
func (ctl *Controller) createSessionIfAuthorized(username string, password []byte) (*fakeSession, error) {
// get user
user, ok := ctl.usersByUsername[username]
if !ok || !bytes.Equal(user.password, password) {
return nil, errWrongNameOrPassword
}
// create session
return ctl.createSession(username, !user.has2FA), nil
}
func (ctl *Controller) createSession(username string, hasFullScope bool) *fakeSession {
session := &fakeSession{
username: username,
uid: ctl.tokenGenerator.next("uid"),
acc: ctl.tokenGenerator.next("acc"),
ref: ctl.tokenGenerator.next("ref"),
hasFullScope: !user.has2FA,
hasFullScope: hasFullScope,
}
ctl.sessionsByUID[session.uid] = session
return session, nil
return session
}
func (ctl *Controller) refreshSessionIfAuthorized(uid, ref string) (*fakeSession, error) {

View File

@ -34,10 +34,8 @@ func (api *FakePMAPI) GetMessage(_ context.Context, apiID string) (*pmapi.Messag
if err := api.checkAndRecordCall(GET, "/mail/v4/messages/"+apiID, nil); err != nil {
return nil, err
}
for _, message := range api.messages {
if message.ID == apiID {
return message, nil
}
if msg := api.getMessage(apiID); msg != nil {
return msg, nil
}
return nil, fmt.Errorf("message %s not found", apiID)
}
@ -175,8 +173,8 @@ func (api *FakePMAPI) SendMessage(ctx context.Context, messageID string, sendMes
if err := api.checkAndRecordCall(POST, "/mail/v4/messages/"+messageID, sendMessageRequest); err != nil {
return nil, nil, err
}
message, err := api.GetMessage(ctx, messageID)
if err != nil {
message := api.getMessage(messageID)
if message == nil {
return nil, nil, errors.Wrap(err, "draft does not exist")
}
message.Time = time.Now().Unix()
@ -276,6 +274,15 @@ func (api *FakePMAPI) findMessage(newMsg *pmapi.Message) *pmapi.Message {
return nil
}
func (api *FakePMAPI) getMessage(msgID string) *pmapi.Message {
for _, msg := range api.messages {
if msg.ID == msgID {
return msg
}
}
return nil
}
func (api *FakePMAPI) addMessage(message *pmapi.Message) {
if api.findMessage(message) != nil {
return

View File

@ -15,7 +15,7 @@ Feature: IMAP auth
Then IMAP response is "IMAP error: NO account is logged out, use the app to login again"
Scenario: Authenticates with connected user that was loaded without internet
Given there is connected user "user"
Given there is user "user" which just logged in
And there is no internet connection
When bridge starts
And the internet connection is restored
@ -28,13 +28,13 @@ Feature: IMAP auth
Then "user" is connected
Scenario: Authenticates with freshly logged-out user
Given there is connected user "user"
Given there is user "user" which just logged in
When "user" logs out
And IMAP client authenticates "user"
Then IMAP response is "IMAP error: NO account is logged out, use the app to login again"
Scenario: Authenticates user which was re-logged in
Given there is connected user "user"
Given there is user "user" which just logged in
When "user" logs out
And IMAP client authenticates "user"
Then IMAP response is "IMAP error: NO account is logged out, use the app to login again"

View File

@ -1,30 +0,0 @@
Feature: Servers are closed when no internet
Scenario: All connection are closed and then restored multiple times
Given there is connected user "user"
And there is IMAP client "i1" logged in as "user"
And there is SMTP client "s1" logged in as "user"
When there is no internet connection
And 1 second pass
Then IMAP client "i1" is logged out
And SMTP client "s1" is logged out
Given the internet connection is restored
And 1 second pass
And there is IMAP client "i2" logged in as "user"
And there is SMTP client "s2" logged in as "user"
When IMAP client "i2" gets info of "INBOX"
When SMTP client "s2" sends "HELO example.com"
Then IMAP response to "i2" is "OK"
Then SMTP response to "s2" is "OK"
When there is no internet connection
And 1 second pass
Then IMAP client "i2" is logged out
And SMTP client "s2" is logged out
Given the internet connection is restored
And 1 second pass
And there is IMAP client "i3" logged in as "user"
And there is SMTP client "s3" logged in as "user"
When IMAP client "i3" gets info of "INBOX"
When SMTP client "s3" sends "HELO example.com"
Then IMAP response to "i3" is "OK"
Then SMTP response to "s3" is "OK"

View File

@ -120,3 +120,91 @@ Feature: SMTP sending of HTML messages with attachments
}
}
"""
Scenario: Alternative plain and HTML message with rfc822 attachment
When SMTP client sends message
"""
From: Bridge Test <[userAddress]>
To: External Bridge <pm.bridge.qa@gmail.com>
Subject: Alternative plain and HTML with rfc822 attachment
Content-Type: multipart/mixed; boundary=main-parts
This is a multipart message in MIME format
--main-parts
Content-Type: multipart/alternative; boundary=alternatives
--alternatives
Content-Type: text/plain
There is an attachment
--alternatives
Content-Type: text/html
<html><body>There <b>is</b> an attachment<body></html>
--alternatives--
--main-parts
Content-Type: message/rfc822
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment
Received: from mx1.opensuse.org (mx1.infra.opensuse.org [192.168.47.95]) by
mailman3.infra.opensuse.org (Postfix) with ESMTP id 38BE2AC3 for
<factory@lists.opensuse.org>; Sun, 11 Jul 2021 19:50:34 +0000 (UTC)
From: "Bob " <Bob@something.net>
Sender: "Bob" <Bob@gmail.com>
To: "opensuse-factory" <opensuse-factory@opensuse.org>
Cc: "Bob" <Bob@something.net>
References: <y6ZUV5yEyOVQHETZRmi1GFe-Xumzct7QcLpGoSsi1MefGaoovfrUqdkmQ5gM6uySZ7JPIJhDkPJFDqHS1fb_mQ==@protonmail.internalid>
Subject: VirtualBox problems with kernel 5.13
Date: Sun, 11 Jul 2021 21:50:25 +0200
Message-ID: <71672e5f-24a2-c79f-03cc-4c923eb1790b@lwfinger.net>
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable
X-Mailer: Microsoft Outlook 16.0
List-Unsubscribe: <mailto:factory-leave@lists.opensuse.org>
Content-Language: en-us
List-Help: <mailto:factory-request@lists.opensuse.org?subject=help>
List-Subscribe: <mailto:factory-join@lists.opensuse.org>
Thread-Index: AQFWvbNSAqFOch49YPlLU4eJWPObaQK2iKDq
I am writing this message as openSUSE's maintainer of VirtualBox.
Nearly every update of the Linux kernel to a new 5.X version breaks =
VirtualBox.
Bob
--main-parts--
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | subject |
| [userAddress] | pm.bridge.qa@gmail.com | Alternative plain and HTML with rfc822 attachment |
And message is sent with API call
"""
{
"Message": {
"Subject": "Alternative plain and HTML with rfc822 attachment",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "pm.bridge.qa@gmail.com",
"Name": "External Bridge"
}
],
"CCList": [],
"BCCList": [],
"MIMEType": "text/html"
}
}
"""

View File

@ -256,3 +256,69 @@ Feature: SMTP sending of plain messages
}
}
"""
Scenario: RCPT does not contain all CC
When SMTP client sends "MAIL FROM:<[userAddress]>"
Then SMTP response is "OK"
When SMTP client sends "RCPT TO:<bridgetest@protonmail.com>"
Then SMTP response is "OK"
When SMTP client sends "DATA"
Then SMTP response is "OK"
When SMTP client sends
"""
From: Bridge Test <[userAddress]>
To: Internal Bridge <bridgetest@protonmail.com>
CC: Internal Bridge 2 <bridgetest2@protonmail.com>
Content-Type: text/plain
Subject: RCPT-CC test
This is CC missing in RCPT test. Have a nice day!
.
"""
Then SMTP response is "OK"
And mailbox "Sent" for "user" has messages
| from | to | cc | subject |
| [userAddress] | bridgetest@protonmail.com | bridgetest2@protonmail.com | RCPT-CC test |
And message is sent with API call
"""
{
"Message": {
"Subject": "RCPT-CC test",
"Sender": {
"Name": "Bridge Test"
},
"ToList": [
{
"Address": "bridgetest@protonmail.com",
"Name": "Internal Bridge"
}
],
"CCList": [
{
"Address": "bridgetest2@protonmail.com",
"Name": "Internal Bridge 2"
}
],
"BCCList": []
}
}
"""
And packages are sent with API call
"""
{
"Packages":[
{
"Addresses":{
"bridgetest@protonmail.com":{
"Type":1
},
"bridgetest2@protonmail.com":{
"Type":1
}
},
"Type":1,
"MIMEType":"text/plain"
}
]
}
"""

View File

@ -1,6 +1,6 @@
Feature: Start bridge
Scenario: Start with connected user, database file and internet connection
Given there is connected user "user"
Given there is user "user" which just logged in
And there is database file for "user"
When bridge starts
Then "user" is connected
@ -8,7 +8,7 @@ Feature: Start bridge
And "user" has running event loop
Scenario: Start with connected user, database file and no internet connection
Given there is connected user "user"
Given there is user "user" which just logged in
And there is database file for "user"
And there is no internet connection
When bridge starts
@ -17,7 +17,7 @@ Feature: Start bridge
And "user" has running event loop
Scenario: Start with connected user, no database file and internet connection
Given there is connected user "user"
Given there is user "user" which just logged in
And there is no database file for "user"
When bridge starts
Then "user" is connected
@ -25,7 +25,7 @@ Feature: Start bridge
And "user" has running event loop
Scenario: Start with connected user, no database file and no internet connection
Given there is connected user "user"
Given there is user "user" which just logged in
And there is no database file for "user"
And there is no internet connection
When bridge starts

View File

@ -1,18 +1,18 @@
Feature: Delete user
Scenario: Deleting connected user
Given there is connected user "user"
Given there is user "user" which just logged in
When user deletes "user"
Then last response is "OK"
And "user" has database file
Scenario: Deleting connected user with cache
Given there is connected user "user"
Given there is user "user" which just logged in
When user deletes "user" with cache
Then last response is "OK"
And "user" does not have database file
Scenario: Deleting connected user without database file
Given there is connected user "user"
Given there is user "user" which just logged in
And there is no database file for "user"
When user deletes "user" with cache
Then last response is "OK"

View File

@ -1,6 +1,6 @@
Feature: Re-login
Scenario: Re-login with connected user and database file
Given there is connected user "user"
Given there is user "user" which just logged in
And there is database file for "user"
When "user" logs in
Then last response is "failed to finish login: user is already connected"
@ -9,7 +9,7 @@ Feature: Re-login
@ignore
Scenario: Re-login with connected user and no database file
Given there is connected user "user"
Given there is user "user" which just logged in
And there is no database file for "user"
When "user" logs in
Then last response is "failed to finish login: user is already connected"

View File

@ -21,7 +21,7 @@ import (
"github.com/cucumber/godog"
)
func IMAPActionsAuthFeatureContext(s *godog.Suite) {
func IMAPActionsAuthFeatureContext(s *godog.ScenarioContext) {
s.Step(`^IMAP client authenticates "([^"]*)"$`, imapClientAuthenticates)
s.Step(`^IMAP client "([^"]*)" authenticates "([^"]*)"$`, imapClientNamedAuthenticates)
s.Step(`^IMAP client authenticates "([^"]*)" with address "([^"]*)"$`, imapClientAuthenticatesWithAddress)

View File

@ -23,7 +23,7 @@ import (
"github.com/cucumber/godog"
)
func IMAPActionsMailboxFeatureContext(s *godog.Suite) {
func IMAPActionsMailboxFeatureContext(s *godog.ScenarioContext) {
s.Step(`^IMAP client creates mailbox "([^"]*)"$`, imapClientCreatesMailbox)
s.Step(`^IMAP client renames mailbox "([^"]*)" to "([^"]*)"$`, imapClientRenamesMailboxTo)
s.Step(`^IMAP client deletes mailbox "([^"]*)"$`, imapClientDeletesMailbox)

View File

@ -23,11 +23,10 @@ import (
"sync"
"github.com/cucumber/godog"
"github.com/cucumber/godog/gherkin"
"golang.org/x/net/html/charset"
)
func IMAPActionsMessagesFeatureContext(s *godog.Suite) {
func IMAPActionsMessagesFeatureContext(s *godog.ScenarioContext) {
s.Step(`^IMAP client sends command "([^"]*)"$`, imapClientSendsCommand)
s.Step(`^IMAP client fetches "([^"]*)"$`, imapClientFetches)
s.Step(`^IMAP client fetches header(?:s)? of "([^"]*)"$`, imapClientFetchesHeader)
@ -161,11 +160,11 @@ func imapClientsMoveMessageSeqOfUserFromToByAppendAndDelete(sourceIMAPClient, ta
return nil
}
func imapClientCreatesMessage(mailboxName string, message *gherkin.DocString) error {
func imapClientCreatesMessage(mailboxName string, message *godog.DocString) error {
return imapClientCreatesMessageWithEncoding(mailboxName, "utf8", message)
}
func imapClientCreatesMessageWithEncoding(mailboxName, encodingName string, message *gherkin.DocString) error {
func imapClientCreatesMessageWithEncoding(mailboxName, encodingName string, message *godog.DocString) error {
encoding, _ := charset.Lookup(encodingName)
msg := message.Content
@ -313,11 +312,11 @@ func imapClientNamedExpungeByUID(imapClient, uids string) error {
return nil
}
func imapClientSendsID(data *gherkin.DocString) error {
func imapClientSendsID(data *godog.DocString) error {
return imapClientNamedSendsID("imap", data)
}
func imapClientNamedSendsID(imapClient string, data *gherkin.DocString) error {
func imapClientNamedSendsID(imapClient string, data *godog.DocString) error {
res := ctx.GetIMAPClient(imapClient).ID(data.Content)
ctx.SetIMAPLastResponse(imapClient, res)
return nil

View File

@ -26,7 +26,7 @@ import (
"github.com/emersion/go-imap"
)
func IMAPChecksFeatureContext(s *godog.Suite) {
func IMAPChecksFeatureContext(s *godog.ScenarioContext) {
s.Step(`^IMAP response is "([^"]*)"$`, imapResponseIs)
s.Step(`^IMAP response to "([^"]*)" is "([^"]*)"$`, imapResponseNamedIs)
s.Step(`^IMAP response contains "([^"]*)"$`, imapResponseContains)

View File

@ -21,7 +21,7 @@ import (
"github.com/cucumber/godog"
)
func IMAPSetupFeatureContext(s *godog.Suite) {
func IMAPSetupFeatureContext(s *godog.ScenarioContext) {
s.Step(`^there is IMAP client logged in as "([^"]*)"$`, thereIsIMAPClientLoggedInAs)
s.Step(`^there is IMAP client "([^"]*)" logged in as "([^"]*)"$`, thereIsIMAPClientNamedLoggedInAs)
s.Step(`^there is IMAP client logged in as "([^"]*)" with address "([^"]*)"$`, thereIsIMAPClientLoggedInAsWithAddress)

View File

@ -17,62 +17,25 @@
package liveapi
import (
"fmt"
"github.com/nsf/jsondiff"
)
type fakeCall struct {
method string
path string
request []byte
}
func (ctl *Controller) recordCall(method, path string, request []byte) {
ctl.lock.Lock()
defer ctl.lock.Unlock()
ctl.calls = append(ctl.calls, &fakeCall{
method: method,
path: path,
request: request,
})
ctl.calls.Register(method, path, request)
}
func (ctl *Controller) PrintCalls() {
fmt.Println("API calls:")
for idx, call := range ctl.calls {
fmt.Printf("%02d: [%s] %s\n", idx+1, call.method, call.path)
if call.request != nil && string(call.request) != "null" {
fmt.Printf("\t%s\n", call.request)
}
}
ctl.calls.PrintCalls()
}
func (ctl *Controller) WasCalled(method, path string, expectedRequest []byte) bool {
for _, call := range ctl.calls {
if call.method != method || call.path != path {
continue
}
if string(expectedRequest) == "" {
return true
}
diff, _ := jsondiff.Compare(call.request, expectedRequest, &jsondiff.Options{})
isSuperset := diff == jsondiff.FullMatch || diff == jsondiff.SupersetMatch
if isSuperset {
return true
}
}
return false
return ctl.calls.WasCalled(method, path, expectedRequest)
}
func (ctl *Controller) WasCalledRegex(methodRegex, pathRegex string, expectedRequest []byte) (bool, error) {
return ctl.calls.WasCalledRegex(methodRegex, pathRegex, expectedRequest)
}
func (ctl *Controller) GetCalls(method, path string) [][]byte {
requests := [][]byte{}
for _, call := range ctl.calls {
if call.method == method && call.path == path {
requests = append(requests, call.request)
}
}
return requests
return ctl.calls.GetCalls(method, path)
}

View File

@ -21,45 +21,37 @@ import (
"net/http"
"sync"
"github.com/ProtonMail/proton-bridge/internal/constants"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/test/context/calls"
"github.com/sirupsen/logrus"
)
// Controller implements PMAPIController interface for specified endpoint.
type Controller struct {
log *logrus.Entry
// Internal states.
lock *sync.RWMutex
calls []*fakeCall
pmapiByUsername map[string]pmapi.Client
calls calls.Calls
messageIDsByUsername map[string][]string
clientManager pmapi.Manager
// State controlled by test.
noInternetConnection bool
}
func NewController(app string) (*Controller, pmapi.Manager) {
cm := pmapi.New(pmapi.NewConfig(getAppVersionName(app), constants.Version))
func NewController(_ string) (*Controller, pmapi.Manager) {
controller := &Controller{
log: logrus.WithField("pkg", "live-controller"),
lock: &sync.RWMutex{},
calls: []*fakeCall{},
pmapiByUsername: map[string]pmapi.Client{},
calls: calls.Calls{},
messageIDsByUsername: map[string][]string{},
clientManager: cm,
noInternetConnection: false,
}
cm.SetTransport(&fakeTransport{
persistentClients.manager.SetTransport(&fakeTransport{
ctl: controller,
transport: http.DefaultTransport,
})
return controller, cm
}
func getAppVersionName(app string) string {
if app == "ie" {
return "importExport"
}
return app
return controller, persistentClients.manager
}

View File

@ -37,9 +37,9 @@ var systemLabelNameToID = map[string]string{ //nolint[gochecknoglobals]
}
func (ctl *Controller) AddUserLabel(username string, label *pmapi.Label) error {
client, ok := ctl.pmapiByUsername[username]
if !ok {
return fmt.Errorf("user %s does not exist", username)
client, err := getPersistentClient(username)
if err != nil {
return err
}
label.Exclusive = getLabelExclusive(label.Name)
@ -68,9 +68,9 @@ func (ctl *Controller) getLabelID(username, labelName string) (string, error) {
return labelID, nil
}
client, ok := ctl.pmapiByUsername[username]
if !ok {
return "", fmt.Errorf("user %s does not exist", username)
client, err := getPersistentClient(username)
if err != nil {
return "", err
}
labels, err := client.ListLabels(context.Background())

View File

@ -19,7 +19,6 @@ package liveapi
import (
"context"
"fmt"
messageUtils "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
@ -31,9 +30,9 @@ func (ctl *Controller) AddUserMessage(username string, message *pmapi.Message) (
return "", errors.New("add user messages with attachments is not implemented for live")
}
client, ok := ctl.pmapiByUsername[username]
if !ok {
return "", fmt.Errorf("user %s does not exist", username)
client, err := getPersistentClient(username)
if err != nil {
return "", err
}
if message.Flags == 0 {
@ -75,9 +74,9 @@ func (ctl *Controller) AddUserMessage(username string, message *pmapi.Message) (
}
func (ctl *Controller) GetMessages(username, labelID string) ([]*pmapi.Message, error) {
client, ok := ctl.pmapiByUsername[username]
if !ok {
return nil, fmt.Errorf("user %s does not exist", username)
client, err := getPersistentClient(username)
if err != nil {
return nil, err
}
page := 0

View File

@ -0,0 +1,141 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
package liveapi
import (
"context"
"fmt"
"math/rand"
"os"
"github.com/ProtonMail/go-srp"
"github.com/ProtonMail/proton-bridge/internal/constants"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type clientAuthGetter interface {
pmapi.Client
GetCurrentAuth() *pmapi.Auth
}
// persistentClients keeps authenticated clients for tests.
//
// We need to reduce the number of authentication done by live tests.
// Before every *scenario* we are creating and authenticating new client.
// This is not necessary for controller purposes. We can reuse the same clients
// for all tests.
//
//nolint:gochecknoglobals // This is necessary for testing
var persistentClients = struct {
manager pmapi.Manager
byName map[string]clientAuthGetter
saltByName map[string]string
}{}
type persistentClient struct {
clientAuthGetter
username string
}
// AuthDelete is noop. All sessions will be closed in CleanupPersistentClients.
func (pc *persistentClient) AuthDelete(_ context.Context) error {
return nil
}
// AuthSalt returns cached string. Otherwise after some time there is an error:
//
// Access token does not have sufficient scope
//
// while all other routes works normally. Need to confirm with Aron that this
// is expected behaviour.
func (pc *persistentClient) AuthSalt(_ context.Context) (string, error) {
return persistentClients.saltByName[pc.username], nil
}
func SetupPersistentClients() {
app := os.Getenv("TEST_APP")
persistentClients.manager = pmapi.New(pmapi.NewConfig(getAppVersionName(app), constants.Version))
persistentClients.manager.SetLogging(logrus.WithField("pkg", "liveapi"), logrus.GetLevel() == logrus.TraceLevel)
persistentClients.byName = map[string]clientAuthGetter{}
persistentClients.saltByName = map[string]string{}
}
func getAppVersionName(app string) string {
if app == "ie" {
return "importExport"
}
return app
}
func CleanupPersistentClients() {
for username, client := range persistentClients.byName {
if err := client.AuthDelete(context.Background()); err != nil {
logrus.WithError(err).
WithField("username", username).
Error("Failed to logout persistent client")
}
}
}
func addPersistentClient(username string, password, mailboxPassword []byte) (pmapi.Client, error) {
if cl, ok := persistentClients.byName[username]; ok {
return cl, nil
}
srp.RandReader = rand.New(rand.NewSource(42)) //nolint:gosec // It is OK to use weaker random number generator here
normalClient, _, err := persistentClients.manager.NewClientWithLogin(context.Background(), username, password)
if err != nil {
return nil, errors.Wrap(err, "failed to create new persistent client")
}
client, ok := normalClient.(clientAuthGetter)
if !ok {
return nil, errors.New("cannot make clientAuthGetter")
}
salt, err := client.AuthSalt(context.Background())
if err != nil {
return nil, errors.Wrap(err, "persistent client: failed to get salt")
}
hashedMboxPass, err := pmapi.HashMailboxPassword(mailboxPassword, salt)
if err != nil {
return nil, errors.Wrap(err, "persistent client: failed to hash mailbox password")
}
if err := client.Unlock(context.Background(), hashedMboxPass); err != nil {
return nil, errors.Wrap(err, "persistent client: failed to unlock user")
}
persistentClients.byName[username] = client
persistentClients.saltByName[username] = salt
return client, nil
}
func getPersistentClient(username string) (pmapi.Client, error) {
v, ok := persistentClients.byName[username]
if !ok {
return nil, fmt.Errorf("user %s does not exist", username)
}
return &persistentClient{v, username}, nil
}

View File

@ -21,43 +21,42 @@ import (
"context"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/test/accounts"
"github.com/cucumber/godog"
"github.com/pkg/errors"
)
func (ctl *Controller) AddUser(user *pmapi.User, addresses *pmapi.AddressList, password []byte, twoFAEnabled bool) error {
if twoFAEnabled {
func (ctl *Controller) AddUser(account *accounts.TestAccount) error {
if account.IsTwoFAEnabled() {
return godog.ErrPending
}
client, _, err := ctl.clientManager.NewClientWithLogin(context.Background(), user.Name, password)
client, err := addPersistentClient(account.User().Name, account.Password(), account.MailboxPassword())
if err != nil {
return errors.Wrap(err, "failed to create new client")
return errors.Wrap(err, "failed to add persistent client")
}
salt, err := client.AuthSalt(context.Background())
if err != nil {
return errors.Wrap(err, "failed to get salt")
}
mailboxPassword, err := pmapi.HashMailboxPassword(password, salt)
if err != nil {
return errors.Wrap(err, "failed to hash mailbox password")
}
if err := client.Unlock(context.Background(), mailboxPassword); err != nil {
return errors.Wrap(err, "failed to unlock user")
}
if err := cleanup(client, addresses); err != nil {
if err := cleanup(client, account.Addresses()); err != nil {
return errors.Wrap(err, "failed to clean user")
}
ctl.pmapiByUsername[user.Name] = client
return nil
}
func (ctl *Controller) ReorderAddresses(user *pmapi.User, addressIDs []string) error {
return ctl.pmapiByUsername[user.Name].ReorderAddresses(context.Background(), addressIDs)
client, err := getPersistentClient(user.Name)
if err != nil {
return err
}
return client.ReorderAddresses(context.Background(), addressIDs)
}
func (ctl *Controller) GetAuthClient(username string) pmapi.Client {
client, err := getPersistentClient(username)
if err != nil {
ctl.log.WithError(err).
WithField("username", username).
Fatal("Cannot get authenticated client")
}
return client
}

View File

@ -33,7 +33,7 @@ var opt = godog.Options{ //nolint[gochecknoglobals]
}
func init() { //nolint[gochecknoinits]
godog.BindFlags("godog.", flag.CommandLine, &opt)
godog.BindCommandLineFlags("godog.", &opt)
// This would normally be done using ldflags but `godog` command doesn't support that.
constants.Version = os.Getenv("BRIDGE_VERSION")
@ -43,9 +43,12 @@ func TestMain(m *testing.M) {
flag.Parse()
opt.Paths = flag.Args()
status := godog.RunWithOptions("godogs", func(s *godog.Suite) {
FeatureContext(s)
}, opt)
status := godog.TestSuite{
Name: "bridge-integration-tests",
TestSuiteInitializer: SuiteInitializer,
ScenarioInitializer: ScenarioInitializer,
Options: &opt,
}.Run()
if st := m.Run(); st > status {
status = st

View File

@ -21,10 +21,9 @@ import (
"strings"
"github.com/cucumber/godog"
"github.com/cucumber/godog/gherkin"
)
func SMTPActionsAuthFeatureContext(s *godog.Suite) {
func SMTPActionsAuthFeatureContext(s *godog.ScenarioContext) {
s.Step(`^SMTP client authenticates "([^"]*)"$`, smtpClientAuthenticates)
s.Step(`^SMTP client "([^"]*)" authenticates "([^"]*)"$`, smtpClientNamedAuthenticates)
s.Step(`^SMTP client authenticates "([^"]*)" with address "([^"]*)"$`, smtpClientAuthenticatesWithAddress)
@ -37,6 +36,7 @@ func SMTPActionsAuthFeatureContext(s *godog.Suite) {
s.Step(`^SMTP client sends message with bcc "([^"]*)"$`, smtpClientSendsMessageWithBCC)
s.Step(`^SMTP client "([^"]*)" sends message with bcc "([^"]*)"$`, smtpClientNamedSendsMessageWithBCC)
s.Step(`^SMTP client sends "([^"]*)"$`, smtpClientSendsCommand)
s.Step(`^SMTP client sends$`, smtpClientSendsCommandMultiline)
s.Step(`^SMTP client "([^"]*)" sends "([^"]*)"$`, smtpClientNamedSendsCommand)
}
@ -90,19 +90,19 @@ func smtpClientLogsOut() error {
return nil
}
func smtpClientSendsMessage(message *gherkin.DocString) error {
func smtpClientSendsMessage(message *godog.DocString) error {
return smtpClientNamedSendsMessage("smtp", message)
}
func smtpClientNamedSendsMessage(clientID string, message *gherkin.DocString) error {
func smtpClientNamedSendsMessage(clientID string, message *godog.DocString) error {
return smtpClientNamedSendsMessageWithBCC(clientID, "", message)
}
func smtpClientSendsMessageWithBCC(bcc string, message *gherkin.DocString) error {
func smtpClientSendsMessageWithBCC(bcc string, message *godog.DocString) error {
return smtpClientNamedSendsMessageWithBCC("smtp", bcc, message)
}
func smtpClientNamedSendsMessageWithBCC(clientID, bcc string, message *gherkin.DocString) error {
func smtpClientNamedSendsMessageWithBCC(clientID, bcc string, message *godog.DocString) error {
res := ctx.GetSMTPClient(clientID).SendMail(strings.NewReader(message.Content), bcc)
ctx.SetSMTPLastResponse(clientID, res)
return nil
@ -111,6 +111,9 @@ func smtpClientNamedSendsMessageWithBCC(clientID, bcc string, message *gherkin.D
func smtpClientSendsCommand(command string) error {
return smtpClientNamedSendsCommand("smtp", command)
}
func smtpClientSendsCommandMultiline(command *godog.DocString) error {
return smtpClientNamedSendsCommand("smtp", command.Content)
}
func smtpClientNamedSendsCommand(clientName, command string) error {
command = strings.ReplaceAll(command, "\\r", "\r")
command = strings.ReplaceAll(command, "\\n", "\n")

View File

@ -21,7 +21,7 @@ import (
"github.com/cucumber/godog"
)
func SMTPChecksFeatureContext(s *godog.Suite) {
func SMTPChecksFeatureContext(s *godog.ScenarioContext) {
s.Step(`^SMTP response is "([^"]*)"$`, smtpResponseIs)
s.Step(`^SMTP response to "([^"]*)" is "([^"]*)"$`, smtpResponseNamedIs)
s.Step(`^SMTP client is logged out`, smtpClientIsLoggedOut)

View File

@ -21,7 +21,7 @@ import (
"github.com/cucumber/godog"
)
func SMTPSetupFeatureContext(s *godog.Suite) {
func SMTPSetupFeatureContext(s *godog.ScenarioContext) {
s.Step(`^there is SMTP client logged in as "([^"]*)"$`, thereIsSMTPClientLoggedInAs)
s.Step(`^there is SMTP client "([^"]*)" logged in as "([^"]*)"$`, thereIsSMTPClientNamedLoggedInAs)
s.Step(`^there is SMTP client logged in as "([^"]*)" with address "([^"]*)"$`, thereIsSMTPClientLoggedInAsWithAddress)

View File

@ -24,7 +24,7 @@ import (
"github.com/stretchr/testify/assert"
)
func StoreActionsFeatureContext(s *godog.Suite) {
func StoreActionsFeatureContext(s *godog.ScenarioContext) {
s.Step(`^the event loop of "([^"]*)" loops once$`, theEventLoopLoops)
s.Step(`^"([^"]*)" receives an address event$`, receivesAnAddressEvent)
}

View File

@ -27,11 +27,11 @@ import (
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/ProtonMail/proton-bridge/test/accounts"
"github.com/cucumber/godog"
"github.com/cucumber/godog/gherkin"
"github.com/cucumber/messages-go/v16"
"github.com/hashicorp/go-multierror"
)
func StoreChecksFeatureContext(s *godog.Suite) {
func StoreChecksFeatureContext(s *godog.ScenarioContext) {
s.Step(`^"([^"]*)" has mailbox "([^"]*)"$`, userHasMailbox)
s.Step(`^"([^"]*)" does not have mailbox "([^"]*)"$`, userDoesNotHaveMailbox)
s.Step(`^"([^"]*)" has the following messages$`, userHasFollowingMessages)
@ -72,7 +72,7 @@ func userDoesNotHaveMailbox(bddUserID, mailboxName string) error {
return nil
}
func userHasFollowingMessages(bddUserID string, structure *gherkin.DataTable) error {
func userHasFollowingMessages(bddUserID string, structure *godog.Table) error {
return processMailboxStructureDataTable(structure, func(bddAddressID string, mailboxNames string, numberOfMessages int) error {
for _, mailboxName := range strings.Split(mailboxNames, ",") {
if err := mailboxForAddressOfUserHasNumberOfMessages(mailboxName, bddAddressID, bddUserID, numberOfMessages); err != nil {
@ -111,7 +111,7 @@ func mailboxForAddressOfUserHasNumberOfMessages(mailboxName, bddAddressID, bddUs
return nil
}
func mailboxForUserHasMessages(mailboxName, bddUserID string, messages *gherkin.DataTable) error {
func mailboxForUserHasMessages(mailboxName, bddUserID string, messages *godog.Table) error {
return mailboxForAddressOfUserHasMessages(mailboxName, "", bddUserID, messages)
}
@ -119,7 +119,7 @@ func mailboxForUserHasNoMessages(mailboxName, bddUserID string) error {
return mailboxForUserHasNumberOfMessages(mailboxName, bddUserID, 0)
}
func mailboxForAddressOfUserHasMessages(mailboxName, bddAddressID, bddUserID string, messages *gherkin.DataTable) error {
func mailboxForAddressOfUserHasMessages(mailboxName, bddAddressID, bddUserID string, messages *godog.Table) error {
account := ctx.GetTestAccountWithAddress(bddUserID, bddAddressID)
if account == nil {
return godog.ErrPending
@ -172,7 +172,7 @@ func mailboxForAddressOfUserHasMessages(mailboxName, bddAddressID, bddUserID str
return nil
}
func pmapiMessagesContainsMessageRow(account *accounts.TestAccount, pmapiMessages []*pmapi.Message, head []*gherkin.TableCell, row *gherkin.TableRow) (bool, error) {
func pmapiMessagesContainsMessageRow(account *accounts.TestAccount, pmapiMessages []*pmapi.Message, head []*messages.PickleTableCell, row *messages.PickleTableRow) (bool, error) {
messages := make([]interface{}, len(pmapiMessages))
for i := range pmapiMessages {
messages[i] = pmapiMessages[i]
@ -180,7 +180,7 @@ func pmapiMessagesContainsMessageRow(account *accounts.TestAccount, pmapiMessage
return messagesContainsMessageRow(account, messages, head, row)
}
func storeMessagesContainsMessageRow(account *accounts.TestAccount, storeMessages []*store.Message, head []*gherkin.TableCell, row *gherkin.TableRow) (bool, error) {
func storeMessagesContainsMessageRow(account *accounts.TestAccount, storeMessages []*store.Message, head []*messages.PickleTableCell, row *messages.PickleTableRow) (bool, error) {
messages := make([]interface{}, len(storeMessages))
for i := range storeMessages {
messages[i] = storeMessages[i]
@ -188,7 +188,7 @@ func storeMessagesContainsMessageRow(account *accounts.TestAccount, storeMessage
return messagesContainsMessageRow(account, messages, head, row)
}
func messagesContainsMessageRow(account *accounts.TestAccount, allMessages []interface{}, head []*gherkin.TableCell, row *gherkin.TableRow) (bool, error) { //nolint[funlen]
func messagesContainsMessageRow(account *accounts.TestAccount, allMessages []interface{}, head []*messages.PickleTableCell, row *messages.PickleTableRow) (bool, error) { //nolint[funlen]
found := false
for _, someMessage := range allMessages {
var message *pmapi.Message

Some files were not shown because too many files have changed in this diff Show More