GODT-1815: Combined/Split mode

This commit is contained in:
James Houlahan
2022-09-28 11:29:33 +02:00
parent 9670e29d9f
commit e9672e6bba
55 changed files with 1909 additions and 705 deletions

View File

@ -13,7 +13,9 @@ type API interface {
GetHostURL() string
AddCallWatcher(func(server.Call), ...string)
AddUser(username, password, address string) (userID, addrID string, err error)
AddUser(username, password, address string) (string, string, error)
AddAddress(userID, address, password string) (string, error)
RemoveAddress(userID, addrID string) error
RevokeUser(userID string) error
GetLabels(userID string) ([]liteapi.Label, error)

View File

@ -30,8 +30,8 @@ import (
)
func init() {
user.DefaultEventPeriod = time.Second
user.DefaultEventJitter = time.Second
user.DefaultEventPeriod = 100 * time.Millisecond
user.DefaultEventJitter = 0
}
type scenario struct {
@ -76,6 +76,16 @@ func TestFeatures(testingT *testing.T) {
ctx.Step(`^the user agent is "([^"]*)"$`, s.theUserAgentIs)
ctx.Step(`^the value of the "([^"]*)" header in the request to "([^"]*)" is "([^"]*)"$`, s.theValueOfTheHeaderInTheRequestToIs)
// ==== SETUP ====
ctx.Step(`^there exists an account with username "([^"]*)" and password "([^"]*)"$`, s.thereExistsAnAccountWithUsernameAndPassword)
ctx.Step(`^the account "([^"]*)" has additional address "([^"]*)"$`, s.theAccountHasAdditionalAddress)
ctx.Step(`^the account "([^"]*)" no longer has additional address "([^"]*)"$`, s.theAccountNoLongerHasAdditionalAddress)
ctx.Step(`^the account "([^"]*)" has (\d+) custom folders$`, s.theAccountHasCustomFolders)
ctx.Step(`^the account "([^"]*)" has (\d+) custom labels$`, s.theAccountHasCustomLabels)
ctx.Step(`^the account "([^"]*)" has the following custom mailboxes:$`, s.theAccountHasTheFollowingCustomMailboxes)
ctx.Step(`^the address "([^"]*)" of account "([^"]*)" has the following messages in "([^"]*)":$`, s.theAddressOfAccountHasTheFollowingMessagesInMailbox)
ctx.Step(`^the address "([^"]*)" of account "([^"]*)" has (\d+) messages in "([^"]*)"$`, s.theAddressOfAccountHasMessagesInMailbox)
// ==== BRIDGE ====
ctx.Step(`^bridge starts$`, s.bridgeStarts)
ctx.Step(`^bridge restarts$`, s.bridgeRestarts)
@ -85,12 +95,15 @@ func TestFeatures(testingT *testing.T) {
ctx.Step(`^the user has disabled automatic updates$`, s.theUserHasDisabledAutomaticUpdates)
ctx.Step(`^the user changes the IMAP port to (\d+)$`, s.theUserChangesTheIMAPPortTo)
ctx.Step(`^the user changes the SMTP port to (\d+)$`, s.theUserChangesTheSMTPPortTo)
ctx.Step(`^the user sets the address mode of "([^"]*)" to "([^"]*)"$`, s.theUserSetsTheAddressModeOfTo)
ctx.Step(`^the user changes the gluon path$`, s.theUserChangesTheGluonPath)
ctx.Step(`^the user deletes the gluon files$`, s.theUserDeletesTheGluonFiles)
ctx.Step(`^the user reports a bug$`, s.theUserReportsABug)
ctx.Step(`^bridge sends a connection up event$`, s.bridgeSendsAConnectionUpEvent)
ctx.Step(`^bridge sends a connection down event$`, s.bridgeSendsAConnectionDownEvent)
ctx.Step(`^bridge sends a deauth event for user "([^"]*)"$`, s.bridgeSendsADeauthEventForUser)
ctx.Step(`^bridge sends an address created event for user "([^"]*)"$`, s.bridgeSendsAnAddressCreatedEventForUser)
ctx.Step(`^bridge sends an address deleted event for user "([^"]*)"$`, s.bridgeSendsAnAddressDeletedEventForUser)
ctx.Step(`^bridge sends sync started and finished events for user "([^"]*)"$`, s.bridgeSendsSyncStartedAndFinishedEventsForUser)
ctx.Step(`^bridge sends an update available event for version "([^"]*)"$`, s.bridgeSendsAnUpdateAvailableEventForVersion)
ctx.Step(`^bridge sends a manual update event for version "([^"]*)"$`, s.bridgeSendsAManualUpdateEventForVersion)
@ -99,12 +112,6 @@ func TestFeatures(testingT *testing.T) {
ctx.Step(`^bridge sends a forced update event$`, s.bridgeSendsAForcedUpdateEvent)
// ==== USER ====
ctx.Step(`^there exists an account with username "([^"]*)" and password "([^"]*)"$`, s.thereExistsAnAccountWithUsernameAndPassword)
ctx.Step(`^the account "([^"]*)" has (\d+) custom folders$`, s.theAccountHasCustomFolders)
ctx.Step(`^the account "([^"]*)" has (\d+) custom labels$`, s.theAccountHasCustomLabels)
ctx.Step(`^the account "([^"]*)" has the following custom mailboxes:$`, s.theAccountHasTheFollowingCustomMailboxes)
ctx.Step(`^the account "([^"]*)" has the following messages in "([^"]*)":$`, s.theAccountHasTheFollowingMessagesInMailbox)
ctx.Step(`^the account "([^"]*)" has (\d+) messages in "([^"]*)"$`, s.theAccountHasMessagesInMailbox)
ctx.Step(`^the user logs in with username "([^"]*)" and password "([^"]*)"$`, s.userLogsInWithUsernameAndPassword)
ctx.Step(`^user "([^"]*)" logs out$`, s.userLogsOut)
ctx.Step(`^user "([^"]*)" is deleted$`, s.userIsDeleted)
@ -119,8 +126,10 @@ func TestFeatures(testingT *testing.T) {
ctx.Step(`^user "([^"]*)" connects IMAP client "([^"]*)"$`, s.userConnectsIMAPClient)
ctx.Step(`^user "([^"]*)" connects IMAP client "([^"]*)" on port (\d+)$`, s.userConnectsIMAPClientOnPort)
ctx.Step(`^user "([^"]*)" connects and authenticates IMAP client "([^"]*)"$`, s.userConnectsAndAuthenticatesIMAPClient)
ctx.Step(`^user "([^"]*)" connects and authenticates IMAP client "([^"]*)" with address "([^"]*)"$`, s.userConnectsAndAuthenticatesIMAPClientWithAddress)
ctx.Step(`^IMAP client "([^"]*)" can authenticate$`, s.imapClientCanAuthenticate)
ctx.Step(`^IMAP client "([^"]*)" cannot authenticate$`, s.imapClientCannotAuthenticate)
ctx.Step(`^IMAP client "([^"]*)" cannot authenticate with address "([^"]*)"$`, s.imapClientCannotAuthenticateWithAddress)
ctx.Step(`^IMAP client "([^"]*)" cannot authenticate with incorrect username$`, s.imapClientCannotAuthenticateWithIncorrectUsername)
ctx.Step(`^IMAP client "([^"]*)" cannot authenticate with incorrect password$`, s.imapClientCannotAuthenticateWithIncorrectPassword)
ctx.Step(`^IMAP client "([^"]*)" announces its ID with name "([^"]*)" and version "([^"]*)"$`, s.imapClientAnnouncesItsIDWithNameAndVersion)
@ -151,6 +160,7 @@ func TestFeatures(testingT *testing.T) {
ctx.Step(`^user "([^"]*)" connects SMTP client "([^"]*)"$`, s.userConnectsSMTPClient)
ctx.Step(`^user "([^"]*)" connects SMTP client "([^"]*)" on port (\d+)$`, s.userConnectsSMTPClientOnPort)
ctx.Step(`^user "([^"]*)" connects and authenticates SMTP client "([^"]*)"$`, s.userConnectsAndAuthenticatesSMTPClient)
ctx.Step(`^user "([^"]*)" connects and authenticates SMTP client "([^"]*)" with address "([^"]*)"$`, s.userConnectsAndAuthenticatesSMTPClientWithAddress)
ctx.Step(`^SMTP client "([^"]*)" can authenticate$`, s.smtpClientCanAuthenticate)
ctx.Step(`^SMTP client "([^"]*)" cannot authenticate$`, s.smtpClientCannotAuthenticate)
ctx.Step(`^SMTP client "([^"]*)" cannot authenticate with incorrect username$`, s.smtpClientCannotAuthenticateWithIncorrectUsername)

View File

@ -9,6 +9,7 @@ import (
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
)
func (s *scenario) bridgeStarts() error {
@ -46,6 +47,19 @@ func (s *scenario) theUserChangesTheSMTPPortTo(port int) error {
return s.t.bridge.SetSMTPPort(port)
}
func (s *scenario) theUserSetsTheAddressModeOfTo(user, mode string) error {
switch mode {
case "split":
return s.t.bridge.SetAddressMode(context.Background(), s.t.getUserID(user), vault.SplitMode)
case "combined":
return s.t.bridge.SetAddressMode(context.Background(), s.t.getUserID(user), vault.CombinedMode)
default:
return fmt.Errorf("unknown address mode %q", mode)
}
}
func (s *scenario) theUserChangesTheGluonPath() error {
gluonDir, err := os.MkdirTemp(s.t.dir, "gluon")
if err != nil {
@ -113,7 +127,7 @@ func (s *scenario) bridgeSendsAConnectionDownEvent() error {
}
func (s *scenario) bridgeSendsADeauthEventForUser(username string) error {
return try(s.t.userDeauthCh, 5*time.Second, func(event events.UserDeauth) error {
return try(s.t.deauthCh, 5*time.Second, func(event events.UserDeauth) error {
if wantUserID := s.t.getUserID(username); wantUserID != event.UserID {
return fmt.Errorf("expected deauth event for user with ID %s, got %s", wantUserID, event.UserID)
}
@ -122,6 +136,26 @@ func (s *scenario) bridgeSendsADeauthEventForUser(username string) error {
})
}
func (s *scenario) bridgeSendsAnAddressCreatedEventForUser(username string) error {
return try(s.t.addrCreatedCh, 5*time.Second, func(event events.UserAddressCreated) error {
if wantUserID := s.t.getUserID(username); wantUserID != event.UserID {
return fmt.Errorf("expected user address created event for user with ID %s, got %s", wantUserID, event.UserID)
}
return nil
})
}
func (s *scenario) bridgeSendsAnAddressDeletedEventForUser(username string) error {
return try(s.t.addrDeletedCh, 5*time.Second, func(event events.UserAddressDeleted) error {
if wantUserID := s.t.getUserID(username); wantUserID != event.UserID {
return fmt.Errorf("expected user address deleted event for user with ID %s, got %s", wantUserID, event.UserID)
}
return nil
})
}
func (s *scenario) bridgeSendsSyncStartedAndFinishedEventsForUser(username string) error {
if err := get(s.t.syncStartedCh, func(event events.SyncStarted) error {
if wantUserID := s.t.getUserID(username); wantUserID != event.UserID {

View File

@ -54,10 +54,12 @@ func (t *testCtx) startBridge() error {
t.bridge = bridge
// Connect the event channels.
t.userLoginCh = chToType[events.Event, events.UserLoggedIn](bridge.GetEvents(events.UserLoggedIn{}))
t.userLogoutCh = chToType[events.Event, events.UserLoggedOut](bridge.GetEvents(events.UserLoggedOut{}))
t.userDeletedCh = chToType[events.Event, events.UserDeleted](bridge.GetEvents(events.UserDeleted{}))
t.userDeauthCh = chToType[events.Event, events.UserDeauth](bridge.GetEvents(events.UserDeauth{}))
t.loginCh = chToType[events.Event, events.UserLoggedIn](bridge.GetEvents(events.UserLoggedIn{}))
t.logoutCh = chToType[events.Event, events.UserLoggedOut](bridge.GetEvents(events.UserLoggedOut{}))
t.deletedCh = chToType[events.Event, events.UserDeleted](bridge.GetEvents(events.UserDeleted{}))
t.deauthCh = chToType[events.Event, events.UserDeauth](bridge.GetEvents(events.UserDeauth{}))
t.addrCreatedCh = chToType[events.Event, events.UserAddressCreated](bridge.GetEvents(events.UserAddressCreated{}))
t.addrDeletedCh = chToType[events.Event, events.UserAddressDeleted](bridge.GetEvents(events.UserAddressDeleted{}))
t.syncStartedCh = chToType[events.Event, events.SyncStarted](bridge.GetEvents(events.SyncStarted{}))
t.syncFinishedCh = chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
t.forcedUpdateCh = chToType[events.Event, events.UpdateForced](bridge.GetEvents(events.UpdateForced{}))

View File

@ -14,6 +14,7 @@ import (
"github.com/emersion/go-imap/client"
"gitlab.protontech.ch/go/liteapi"
"gitlab.protontech.ch/go/liteapi/server"
"golang.org/x/exp/maps"
)
var defaultVersion = semver.MustParse("1.0.0")
@ -32,10 +33,12 @@ type testCtx struct {
bridge *bridge.Bridge
// These channels hold events of various types coming from bridge.
userLoginCh <-chan events.UserLoggedIn
userLogoutCh <-chan events.UserLoggedOut
userDeletedCh <-chan events.UserDeleted
userDeauthCh <-chan events.UserDeauth
loginCh <-chan events.UserLoggedIn
logoutCh <-chan events.UserLoggedOut
deletedCh <-chan events.UserDeleted
deauthCh <-chan events.UserDeauth
addrCreatedCh <-chan events.UserAddressCreated
addrDeletedCh <-chan events.UserAddressDeleted
syncStartedCh <-chan events.SyncStarted
syncFinishedCh <-chan events.SyncFinished
forcedUpdateCh <-chan events.UpdateForced
@ -43,10 +46,10 @@ type testCtx struct {
updateCh <-chan events.Event
// These maps hold expected userIDByName, their primary addresses and bridge passwords.
userIDByName map[string]string
userAddrByID map[string]string
userPassByID map[string]string
addrIDByID map[string]string
userIDByName map[string]string
userAddrByEmail map[string]map[string]string
userPassByID map[string]string
userBridgePassByID map[string]string
// These are the IMAP and SMTP clients used to connect to bridge.
imapClients map[string]*imapClient
@ -83,10 +86,10 @@ func newTestCtx(tb testing.TB) *testCtx {
mocks: bridge.NewMocks(tb, dialer, defaultVersion, defaultVersion),
version: defaultVersion,
userIDByName: make(map[string]string),
userAddrByID: make(map[string]string),
userPassByID: make(map[string]string),
addrIDByID: make(map[string]string),
userIDByName: make(map[string]string),
userAddrByEmail: make(map[string]map[string]string),
userPassByID: make(map[string]string),
userBridgePassByID: make(map[string]string),
imapClients: make(map[string]*imapClient),
smtpClients: make(map[string]*smtpClient),
@ -112,12 +115,28 @@ func (t *testCtx) setUserID(username, userID string) {
t.userIDByName[username] = userID
}
func (t *testCtx) getUserAddr(userID string) string {
return t.userAddrByID[userID]
func (t *testCtx) getUserAddrID(userID, email string) string {
return t.userAddrByEmail[userID][email]
}
func (t *testCtx) setUserAddr(userID, addr string) {
t.userAddrByID[userID] = addr
func (t *testCtx) getUserAddrs(userID string) []string {
return maps.Keys(t.userAddrByEmail[userID])
}
func (t *testCtx) setUserAddr(userID, addrID, email string) {
if _, ok := t.userAddrByEmail[userID]; !ok {
t.userAddrByEmail[userID] = make(map[string]string)
}
t.userAddrByEmail[userID][email] = addrID
}
func (t *testCtx) unsetUserAddr(userID, wantAddrID string) {
for email, addrID := range t.userAddrByEmail[userID] {
if addrID == wantAddrID {
delete(t.userAddrByEmail[userID], email)
}
}
}
func (t *testCtx) getUserPass(userID string) string {
@ -128,12 +147,12 @@ func (t *testCtx) setUserPass(userID, pass string) {
t.userPassByID[userID] = pass
}
func (t *testCtx) getAddrID(userID string) string {
return t.addrIDByID[userID]
func (t *testCtx) getUserBridgePass(userID string) string {
return t.userBridgePassByID[userID]
}
func (t *testCtx) setAddrID(userID, addrID string) {
t.addrIDByID[userID] = addrID
func (t *testCtx) setUserBridgePass(userID, pass string) {
t.userBridgePassByID[userID] = pass
}
func (t *testCtx) getMBoxID(userID string, name string) string {

48
tests/fast.go Normal file
View File

@ -0,0 +1,48 @@
package tests
import (
"crypto/x509"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/v2/internal/certs"
)
var (
preCompPGPKey *crypto.Key
preCompCertPEM []byte
preCompKeyPEM []byte
)
func FastGenerateKey(name, email string, passphrase []byte, keyType string, bits int) (string, error) {
encKey, err := preCompPGPKey.Lock(passphrase)
if err != nil {
return "", err
}
return encKey.Armor()
}
func FastGenerateCert(template *x509.Certificate) ([]byte, []byte, error) {
return preCompCertPEM, preCompKeyPEM, nil
}
func init() {
key, err := crypto.GenerateKey("name", "email", "rsa", 1024)
if err != nil {
panic(err)
}
template, err := certs.NewTLSTemplate()
if err != nil {
panic(err)
}
certPEM, keyPEM, err := certs.GenerateCert(template)
if err != nil {
panic(err)
}
preCompPGPKey = key
preCompCertPEM = certPEM
preCompKeyPEM = keyPEM
}

View File

@ -4,7 +4,7 @@ Feature: IMAP get mailbox info
And the account "user@pm.me" has the following custom mailboxes:
| name | type |
| one | folder |
And the account "user@pm.me" has the following messages in "one":
And the address "user@pm.me" of account "user@pm.me" has the following messages in "one":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |

View File

@ -5,7 +5,7 @@ Feature: IMAP copy messages
| name | type |
| mbox | folder |
| label | label |
And the account "user@pm.me" has the following messages in "Inbox":
And the address "user@pm.me" of account "user@pm.me" has the following messages in "Inbox":
| sender | recipient | subject | unread |
| john.doe@mail.com | user@pm.me | foo | false |
| jane.doe@mail.com | name@pm.me | bar | true |

View File

@ -5,7 +5,7 @@ Feature: IMAP remove messages from mailbox
| name | type |
| mbox | folder |
| label | label |
And the account "user@pm.me" has 10 messages in "mbox"
And the address "user@pm.me" of account "user@pm.me" has 10 messages in "mbox"
And bridge starts
And the user logs in with username "user@pm.me" and password "password"
And user "user@pm.me" finishes syncing

View File

@ -0,0 +1,180 @@
Feature: Address mode
Background:
Given there exists an account with username "user@pm.me" and password "password"
And the account "user@pm.me" has additional address "alias@pm.me"
And the account "user@pm.me" has the following custom mailboxes:
| name | type |
| one | folder |
| two | folder |
And the address "user@pm.me" of account "user@pm.me" has the following messages in "one":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
And the address "alias@pm.me" of account "user@pm.me" has the following messages in "two":
| sender | recipient | subject | unread |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
And bridge starts
And the user logs in with username "user@pm.me" and password "password"
And user "user@pm.me" finishes syncing
Scenario: The user is in combined mode
When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me"
Then IMAP client "1" sees the following messages in "Folders/one":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
And IMAP client "1" sees the following messages in "Folders/two":
| sender | recipient | subject | unread |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
And IMAP client "1" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me"
Then IMAP client "2" sees the following messages in "Folders/one":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
And IMAP client "2" sees the following messages in "Folders/two":
| sender | recipient | subject | unread |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
And IMAP client "2" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
Scenario: The user is in split mode
Given the user sets the address mode of "user@pm.me" to "split"
And user "user@pm.me" finishes syncing
When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me"
Then IMAP client "1" sees the following messages in "Folders/one":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
And IMAP client "1" sees 0 messages in "Folders/two"
And IMAP client "1" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me"
Then IMAP client "2" sees 0 messages in "Folders/one"
And IMAP client "2" sees the following messages in "Folders/two":
| sender | recipient | subject | unread |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
And IMAP client "2" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
Scenario: The user switches from combined to split mode and back
Given the user sets the address mode of "user@pm.me" to "split"
And user "user@pm.me" finishes syncing
And the user sets the address mode of "user@pm.me" to "combined"
And user "user@pm.me" finishes syncing
When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me"
Then IMAP client "1" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me"
Then IMAP client "2" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
Scenario: The user adds an address while in combined mode
When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me"
Then IMAP client "1" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me"
Then IMAP client "2" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
Given the account "user@pm.me" has additional address "other@pm.me"
And bridge sends an address created event for user "user@pm.me"
When user "user@pm.me" connects and authenticates IMAP client "3" with address "other@pm.me"
Then IMAP client "3" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
Scenario: The user adds an address while in split mode
Given the user sets the address mode of "user@pm.me" to "split"
And user "user@pm.me" finishes syncing
When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me"
And IMAP client "1" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me"
And IMAP client "2" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
Given the account "user@pm.me" has additional address "other@pm.me"
And bridge sends an address created event for user "user@pm.me"
When user "user@pm.me" connects and authenticates IMAP client "3" with address "other@pm.me"
Then IMAP client "3" eventually sees 0 messages in "All Mail"
Scenario: The user deletes an address while in combined mode
When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me"
Then IMAP client "1" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me"
Then IMAP client "2" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
Given the account "user@pm.me" no longer has additional address "alias@pm.me"
And bridge sends an address deleted event for user "user@pm.me"
When user "user@pm.me" connects IMAP client "3"
Then IMAP client "3" cannot authenticate with address "alias@pm.me"
Scenario: The user deletes an address while in split mode
Given the user sets the address mode of "user@pm.me" to "split"
And user "user@pm.me" finishes syncing
When user "user@pm.me" connects and authenticates IMAP client "1" with address "user@pm.me"
And IMAP client "1" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
When user "user@pm.me" connects and authenticates IMAP client "2" with address "alias@pm.me"
And IMAP client "2" sees the following messages in "All Mail":
| sender | recipient | subject | unread |
| c@pm.me | c@pm.me | three | true |
| d@pm.me | d@pm.me | four | false |
Given the account "user@pm.me" no longer has additional address "alias@pm.me"
And bridge sends an address deleted event for user "user@pm.me"
When user "user@pm.me" connects IMAP client "3"
Then IMAP client "3" cannot authenticate with address "alias@pm.me"
Scenario: The user makes an alias the primary address while in combined mode
Scenario: The user makes an alias the primary address while in split mode

View File

@ -6,11 +6,11 @@ Feature: Bridge can fully sync an account
| one | folder |
| two | folder |
| three | label |
And the account "user@pm.me" has the following messages in "one":
And the address "user@pm.me" of account "user@pm.me" has the following messages in "one":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |
And the account "user@pm.me" has the following messages in "two":
And the address "user@pm.me" of account "user@pm.me" has the following messages in "two":
| sender | recipient | subject | unread |
| a@pm.me | a@pm.me | one | true |
| b@pm.me | b@pm.me | two | false |

View File

@ -26,25 +26,39 @@ func (s *scenario) userConnectsIMAPClientOnPort(username, clientID string, port
}
func (s *scenario) userConnectsAndAuthenticatesIMAPClient(username, clientID string) error {
return s.userConnectsAndAuthenticatesIMAPClientWithAddress(username, clientID, s.t.getUserAddrs(s.t.getUserID(username))[0])
}
func (s *scenario) userConnectsAndAuthenticatesIMAPClientWithAddress(username, clientID, address string) error {
if err := s.t.newIMAPClient(s.t.getUserID(username), clientID); err != nil {
return err
}
userID, client := s.t.getIMAPClient(clientID)
return client.Login(s.t.getUserAddr(userID), s.t.getUserPass(userID))
return client.Login(address, s.t.getUserBridgePass(userID))
}
func (s *scenario) imapClientCanAuthenticate(clientID string) error {
userID, client := s.t.getIMAPClient(clientID)
return client.Login(s.t.getUserAddr(userID), s.t.getUserPass(userID))
return client.Login(s.t.getUserAddrs(userID)[0], s.t.getUserBridgePass(userID))
}
func (s *scenario) imapClientCannotAuthenticate(clientID string) error {
userID, client := s.t.getIMAPClient(clientID)
if err := client.Login(s.t.getUserAddr(userID), s.t.getUserPass(userID)); err == nil {
if err := client.Login(s.t.getUserAddrs(userID)[0], s.t.getUserBridgePass(userID)); err == nil {
return fmt.Errorf("expected error, got nil")
}
return nil
}
func (s *scenario) imapClientCannotAuthenticateWithAddress(clientID, address string) error {
userID, client := s.t.getIMAPClient(clientID)
if err := client.Login(address, s.t.getUserBridgePass(userID)); err == nil {
return fmt.Errorf("expected error, got nil")
}
@ -54,7 +68,7 @@ func (s *scenario) imapClientCannotAuthenticate(clientID string) error {
func (s *scenario) imapClientCannotAuthenticateWithIncorrectUsername(clientID string) error {
userID, client := s.t.getIMAPClient(clientID)
if err := client.Login(s.t.getUserAddr(userID)+"bad", s.t.getUserPass(userID)); err == nil {
if err := client.Login(s.t.getUserAddrs(userID)[0]+"bad", s.t.getUserBridgePass(userID)); err == nil {
return fmt.Errorf("expected error, got nil")
}
@ -64,7 +78,7 @@ func (s *scenario) imapClientCannotAuthenticateWithIncorrectUsername(clientID st
func (s *scenario) imapClientCannotAuthenticateWithIncorrectPassword(clientID string) error {
userID, client := s.t.getIMAPClient(clientID)
if err := client.Login(s.t.getUserAddr(userID), s.t.getUserPass(userID)+"bad"); err == nil {
if err := client.Login(s.t.getUserAddrs(userID)[0], s.t.getUserBridgePass(userID)+"bad"); err == nil {
return fmt.Errorf("expected error, got nil")
}

View File

@ -1,39 +1,14 @@
package tests
import (
"crypto/x509"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/v2/internal/certs"
"gitlab.protontech.ch/go/liteapi/server/account"
)
func init() {
key, err := crypto.GenerateKey("name", "email", "rsa", 1024)
if err != nil {
panic(err)
}
// Use the fast key generation for tests.
account.GenerateKey = FastGenerateKey
account.GenerateKey = func(name, email string, passphrase []byte, keyType string, bits int) (string, error) {
encKey, err := key.Lock(passphrase)
if err != nil {
return "", err
}
return encKey.Armor()
}
template, err := certs.NewTLSTemplate()
if err != nil {
panic(err)
}
certPEM, keyPEM, err := certs.GenerateCert(template)
if err != nil {
panic(err)
}
certs.GenerateCert = func(template *x509.Certificate) ([]byte, []byte, error) {
return certPEM, keyPEM, nil
}
// Use the fast cert generation for tests.
certs.GenerateCert = FastGenerateCert
}

View File

@ -16,13 +16,17 @@ func (s *scenario) userConnectsSMTPClientOnPort(username, clientID string, port
}
func (s *scenario) userConnectsAndAuthenticatesSMTPClient(username, clientID string) error {
return s.userConnectsAndAuthenticatesSMTPClientWithAddress(username, clientID, s.t.getUserAddrs(s.t.getUserID(username))[0])
}
func (s *scenario) userConnectsAndAuthenticatesSMTPClientWithAddress(username, clientID, address string) error {
if err := s.t.newSMTPClient(s.t.getUserID(username), clientID); err != nil {
return err
}
userID, client := s.t.getSMTPClient(clientID)
s.t.pushError(client.Auth(smtp.PlainAuth("", s.t.getUserAddr(userID), s.t.getUserPass(userID), constants.Host)))
s.t.pushError(client.Auth(smtp.PlainAuth("", address, s.t.getUserBridgePass(userID), constants.Host)))
return nil
}
@ -30,7 +34,7 @@ func (s *scenario) userConnectsAndAuthenticatesSMTPClient(username, clientID str
func (s *scenario) smtpClientCanAuthenticate(clientID string) error {
userID, client := s.t.getSMTPClient(clientID)
if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddr(userID), s.t.getUserPass(userID), constants.Host)); err != nil {
if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddrs(userID)[0], s.t.getUserBridgePass(userID), constants.Host)); err != nil {
return fmt.Errorf("expected no error, got %v", err)
}
@ -40,7 +44,7 @@ func (s *scenario) smtpClientCanAuthenticate(clientID string) error {
func (s *scenario) smtpClientCannotAuthenticate(clientID string) error {
userID, client := s.t.getSMTPClient(clientID)
if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddr(userID), s.t.getUserPass(userID), constants.Host)); err == nil {
if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddrs(userID)[0], s.t.getUserBridgePass(userID), constants.Host)); err == nil {
return fmt.Errorf("expected error, got nil")
}
@ -50,7 +54,7 @@ func (s *scenario) smtpClientCannotAuthenticate(clientID string) error {
func (s *scenario) smtpClientCannotAuthenticateWithIncorrectUsername(clientID string) error {
userID, client := s.t.getSMTPClient(clientID)
if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddr(userID)+"bad", s.t.getUserPass(userID), constants.Host)); err == nil {
if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddrs(userID)[0]+"bad", s.t.getUserBridgePass(userID), constants.Host)); err == nil {
return fmt.Errorf("expected error, got nil")
}
@ -60,7 +64,7 @@ func (s *scenario) smtpClientCannotAuthenticateWithIncorrectUsername(clientID st
func (s *scenario) smtpClientCannotAuthenticateWithIncorrectPassword(clientID string) error {
userID, client := s.t.getSMTPClient(clientID)
if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddr(userID), s.t.getUserPass(userID)+"bad", constants.Host)); err == nil {
if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddrs(userID)[0], s.t.getUserBridgePass(userID)+"bad", constants.Host)); err == nil {
return fmt.Errorf("expected error, got nil")
}

View File

@ -16,6 +16,7 @@ import (
)
func (s *scenario) thereExistsAnAccountWithUsernameAndPassword(username, password string) error {
// Create the user.
userID, addrID, err := s.t.api.AddUser(username, password, username)
if err != nil {
return err
@ -24,11 +25,37 @@ func (s *scenario) thereExistsAnAccountWithUsernameAndPassword(username, passwor
// Set the ID of this user.
s.t.setUserID(username, userID)
// Set the address ID of this user.
s.t.setAddrID(userID, addrID)
// Set the password of this user.
s.t.setUserPass(userID, password)
// Set the address of this user (right now just the same as the username, but let's stay flexible).
s.t.setUserAddr(userID, username)
s.t.setUserAddr(userID, addrID, username)
return nil
}
func (s *scenario) theAccountHasAdditionalAddress(username, address string) error {
userID := s.t.getUserID(username)
addrID, err := s.t.api.AddAddress(userID, address, s.t.getUserPass(userID))
if err != nil {
return err
}
s.t.setUserAddr(userID, addrID, address)
return nil
}
func (s *scenario) theAccountNoLongerHasAdditionalAddress(username, address string) error {
userID := s.t.getUserID(username)
addrID := s.t.getUserAddrID(userID, address)
if err := s.t.api.RemoveAddress(userID, addrID); err != nil {
return err
}
s.t.unsetUserAddr(userID, addrID)
return nil
}
@ -84,9 +111,9 @@ func (s *scenario) theAccountHasTheFollowingCustomMailboxes(username string, tab
return nil
}
func (s *scenario) theAccountHasTheFollowingMessagesInMailbox(username, mailbox string, table *godog.Table) error {
func (s *scenario) theAddressOfAccountHasTheFollowingMessagesInMailbox(address, username, mailbox string, table *godog.Table) error {
userID := s.t.getUserID(username)
addrID := s.t.getAddrID(userID)
addrID := s.t.getUserAddrID(userID, address)
mboxID := s.t.getMBoxID(userID, mailbox)
for _, wantMessage := range parseMessages(table) {
@ -109,9 +136,9 @@ func (s *scenario) theAccountHasTheFollowingMessagesInMailbox(username, mailbox
return nil
}
func (s *scenario) theAccountHasMessagesInMailbox(username string, count int, mailbox string) error {
func (s *scenario) theAddressOfAccountHasMessagesInMailbox(address, username string, count int, mailbox string) error {
userID := s.t.getUserID(username)
addrID := s.t.getAddrID(userID)
addrID := s.t.getUserAddrID(userID, address)
mboxID := s.t.getMBoxID(userID, mailbox)
for idx := 0; idx < count; idx++ {
@ -148,7 +175,7 @@ func (s *scenario) userLogsInWithUsernameAndPassword(username, password string)
return err
}
s.t.setUserPass(userID, info.BridgePass)
s.t.setUserBridgePass(userID, info.BridgePass)
}
return nil