forked from Silverfish/proton-bridge
GODT-1815: Combined/Split mode
This commit is contained in:
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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{}))
|
||||
|
||||
@ -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
48
tests/fast.go
Normal 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
|
||||
}
|
||||
@ -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 |
|
||||
|
||||
@ -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 |
|
||||
|
||||
@ -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
|
||||
|
||||
180
tests/features/user/addressmode.feature
Normal file
180
tests/features/user/addressmode.feature
Normal 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
|
||||
@ -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 |
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user