Compare commits

..

30 Commits

Author SHA1 Message Date
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
73 changed files with 1904 additions and 1154 deletions

View File

@ -2,6 +2,42 @@
Changelog [format](http://keepachangelog.com/en/1.0.0/)
## [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 +50,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.9+git
IE_APP_VERSION?=1.3.3+git
APP_VERSION:=${BRIDGE_APP_VERSION}
SRC_ICO:=logo.ico

5
go.mod
View File

@ -19,7 +19,7 @@ require (
github.com/ProtonMail/go-rfc5322 v0.8.0
github.com/ProtonMail/go-srp v0.0.0-20210514134713-bd9454f3fa01
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
github.com/ProtonMail/gopenpgp/v2 v2.1.9
github.com/ProtonMail/gopenpgp/v2 v2.1.10
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
@ -59,6 +59,8 @@ require (
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/stretchr/testify v1.6.1
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
@ -71,5 +73,6 @@ require (
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
)

18
go.sum
View File

@ -20,6 +20,8 @@ github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac h1:2xU3QncAiS/W
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=
@ -28,8 +30,8 @@ github.com/ProtonMail/go-srp v0.0.0-20210514134713-bd9454f3fa01 h1:sRxNvPGnJFh6y
github.com/ProtonMail/go-srp v0.0.0-20210514134713-bd9454f3fa01/go.mod h1:jOXzdvWTILIJzl83yzi/EZcnnhpI+A/5EyflaeVfi/0=
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.1.10 h1:WPwpzVQFvzzFIzHCsJ1RzuVkN4JsMIydl/0vc05yU0E=
github.com/ProtonMail/gopenpgp/v2 v2.1.10/go.mod h1:CHIXesUdnPxIxtJTg2P/cxoA0cvUwIBpZIS8SsY82QA=
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=
@ -81,15 +83,11 @@ 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=
@ -176,9 +174,6 @@ 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/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=
@ -260,6 +255,10 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/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=
@ -356,6 +355,7 @@ golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3
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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
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=

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 {
panicHandler panicHandler
server *imapserver.Server
userAgent *useragent.UserAgent
eventListener listener.Listener
debugClient bool
debugServer bool
port int
isRunning atomic.Value
// Server takes care of IMAP listening serving. It implements serverutil.Server.
type Server struct {
panicHandler panicHandler
userAgent *useragent.UserAgent
debugClient bool
debugServer bool
port int
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
panicHandler panicHandler
backend goSMTP.Backend
debug bool
useSSL bool
port int
tls *tls.Config
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,
debug: debug,
useSSL: useSSL,
port: port,
tls: tls,
}
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

@ -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

@ -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

@ -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

@ -59,6 +59,7 @@ 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(updateTime)
m.rc.OnAfterResponse(m.catchAPIError)
m.rc.OnAfterResponse(m.handleRequestSuccess)
m.rc.OnError(m.handleRequestFailure)

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,16 @@ 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 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.9+integrationtests
export VERBOSITY?=fatal
export TEST_DATA=testdata
export TEST_APP?=bridge

View File

@ -29,6 +29,9 @@ const (
)
func FeatureContext(s *godog.Suite) {
s.BeforeSuite(context.BeforeRun)
s.AfterSuite(context.AfterRun)
s.BeforeScenario(beforeScenario)
s.AfterScenario(afterScenario)

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

@ -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)

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

@ -24,6 +24,8 @@ import (
"github.com/sirupsen/logrus"
)
// Controller implements dummy PMAPIController interface without actual
// endpoint.
type Controller struct {
// Internal states.
lock *sync.RWMutex

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

@ -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,45 +21,36 @@ import (
"net/http"
"sync"
"github.com/ProtonMail/proton-bridge/internal/constants"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"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
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{},
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

@ -28,6 +28,7 @@ import (
func UsersSetupFeatureContext(s *godog.Suite) {
s.Step(`^there is user "([^"]*)"$`, thereIsUser)
s.Step(`^there is connected user "([^"]*)"$`, thereIsConnectedUser)
s.Step(`^there is user "([^"]*)" which just logged in$`, thereIsUserWhichJustLoggedIn)
s.Step(`^there is disconnected user "([^"]*)"$`, thereIsDisconnectedUser)
s.Step(`^there is database file for "([^"]*)"$`, thereIsDatabaseFileForUser)
s.Step(`^there is no database file for "([^"]*)"$`, thereIsNoDatabaseFileForUser)
@ -39,7 +40,7 @@ func thereIsUser(bddUserID string) error {
if account == nil {
return godog.ErrPending
}
err := ctx.GetPMAPIController().AddUser(account.User(), account.Addresses(), account.Password(), account.IsTwoFAEnabled())
err := ctx.GetPMAPIController().AddUser(account)
return internalError(err, "adding user %s", account.Username())
}
@ -48,7 +49,21 @@ func thereIsConnectedUser(bddUserID string) error {
if account == nil {
return godog.ErrPending
}
err := ctx.GetPMAPIController().AddUser(account.User(), account.Addresses(), account.Password(), account.IsTwoFAEnabled())
username := account.Username()
ctl := ctx.GetPMAPIController()
err := ctl.AddUser(account)
if err != nil {
return internalError(err, "adding user %s", username)
}
return ctx.FinishLogin(ctx.GetPMAPIController().GetAuthClient(username), account.MailboxPassword())
}
func thereIsUserWhichJustLoggedIn(bddUserID string) error {
account := ctx.GetTestAccount(bddUserID)
if account == nil {
return godog.ErrPending
}
err := ctx.GetPMAPIController().AddUser(account)
if err != nil {
return internalError(err, "adding user %s", account.Username())
}
@ -60,7 +75,7 @@ func thereIsDisconnectedUser(bddUserID string) error {
if account == nil {
return godog.ErrPending
}
err := ctx.GetPMAPIController().AddUser(account.User(), account.Addresses(), account.Password(), account.IsTwoFAEnabled())
err := ctx.GetPMAPIController().AddUser(account)
if err != nil {
return internalError(err, "adding user %s", account.Username())
}