From b37f2d138ac5fc5e820dd08b31cc5f162ec21eb2 Mon Sep 17 00:00:00 2001 From: Atanas Janeshliev Date: Wed, 9 Apr 2025 16:04:20 +0200 Subject: [PATCH] feat(BRIDGE-348): display BYOE addresses in Bridge --- go.mod | 2 +- go.sum | 6 ++ internal/bridge/bridge_test.go | 4 +- internal/bridge/sync_test.go | 10 ++-- internal/bridge/user_event_test.go | 89 ++++++++++++++++++++++++++++-- internal/bridge/user_test.go | 27 ++++++++- internal/user/user.go | 2 +- internal/user/user_test.go | 2 +- 8 files changed, 126 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 72b7afce..569c030e 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 github.com/ProtonMail/gluon v0.17.1-0.20250324123053-2abce471ad71 github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a - github.com/ProtonMail/go-proton-api v0.4.1-0.20250217140732-2e531f21de4c + github.com/ProtonMail/go-proton-api v0.4.1-0.20250410050801-92de6e7c8517 github.com/ProtonMail/gopenpgp/v2 v2.8.2-proton github.com/PuerkitoBio/goquery v1.8.1 github.com/abiosoft/ishell v2.0.0+incompatible diff --git a/go.sum b/go.sum index 5a71b7d6..380abfb1 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,12 @@ github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ek github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= github.com/ProtonMail/go-proton-api v0.4.1-0.20250217140732-2e531f21de4c h1:dxnbB+ov77BDj1LC35fKZ14hLoTpU6OTpZySwxarVx0= github.com/ProtonMail/go-proton-api v0.4.1-0.20250217140732-2e531f21de4c/go.mod h1:RYgagBFkA3zFrSt7/vviFFwjZxBo6pGzcTwFsLwsnyc= +github.com/ProtonMail/go-proton-api v0.4.1-0.20250409092940-13ddc20a05a1 h1:u3G9UB8prOnzOneOf0JFCIVnMRLiK4QgEpPQVu9Y8Q4= +github.com/ProtonMail/go-proton-api v0.4.1-0.20250409092940-13ddc20a05a1/go.mod h1:RYgagBFkA3zFrSt7/vviFFwjZxBo6pGzcTwFsLwsnyc= +github.com/ProtonMail/go-proton-api v0.4.1-0.20250409131808-0bbc8e7c32db h1:mOtbY5BB2eNr2QmbZhFn5EnsJcimTntPB6akN2r+AuE= +github.com/ProtonMail/go-proton-api v0.4.1-0.20250409131808-0bbc8e7c32db/go.mod h1:RYgagBFkA3zFrSt7/vviFFwjZxBo6pGzcTwFsLwsnyc= +github.com/ProtonMail/go-proton-api v0.4.1-0.20250410050801-92de6e7c8517 h1:70JoDgXxfil4hbDoYGF98rMd47Rld6wXWyFAw4uFOTY= +github.com/ProtonMail/go-proton-api v0.4.1-0.20250410050801-92de6e7c8517/go.mod h1:RYgagBFkA3zFrSt7/vviFFwjZxBo6pGzcTwFsLwsnyc= github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865 h1:EP1gnxLL5Z7xBSymE9nSTM27nRYINuvssAtDmG0suD8= github.com/ProtonMail/go-smtp v0.0.0-20231109081432-2b3d50599865/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI= diff --git a/internal/bridge/bridge_test.go b/internal/bridge/bridge_test.go index ff6f9b44..a72a8b49 100644 --- a/internal/bridge/bridge_test.go +++ b/internal/bridge/bridge_test.go @@ -618,7 +618,7 @@ func TestBridge_AddressWithoutKeys(t *testing.T) { require.NoError(t, err) // Create an additional address for the user; it will not have keys. - aliasAddrID, err := s.CreateAddress(userID, "alias@pm.me", []byte("password")) + aliasAddrID, err := s.CreateAddress(userID, "alias@pm.me", []byte("password"), true) require.NoError(t, err) // Create an API client so we can remove the address keys. @@ -785,7 +785,7 @@ func TestBridge_ChangeAddressOrder(t *testing.T) { require.NoError(t, err) // Create a second address for the user. - aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password) + aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password, true) require.NoError(t, err) // Create 10 messages for the user. diff --git a/internal/bridge/sync_test.go b/internal/bridge/sync_test.go index 9cee1fa5..79d05008 100644 --- a/internal/bridge/sync_test.go +++ b/internal/bridge/sync_test.go @@ -355,7 +355,7 @@ func TestBridge_CanProcessEventsDuringSync(t *testing.T) { // Create a new address newAddress := "foo@proton.ch" - addrID, err := s.CreateAddress(userID, newAddress, password) + addrID, err := s.CreateAddress(userID, newAddress, password, true) require.NoError(t, err) event := <-addressCreatedCh @@ -430,7 +430,7 @@ func TestBridge_EventReplayAfterSyncHasFinished(t *testing.T) { createNumMessages(ctx, t, c, addrID, labelID, numMsg) }) - addrID1, err := s.CreateAddress(userID, "foo@proton.ch", password) + addrID1, err := s.CreateAddress(userID, "foo@proton.ch", password, true) require.NoError(t, err) var allowSyncToProgress atomic.Bool @@ -469,7 +469,7 @@ func TestBridge_EventReplayAfterSyncHasFinished(t *testing.T) { }) // User AddrID2 event as a check point to see when the new address was created. - addrID2, err := s.CreateAddress(userID, "bar@proton.ch", password) + addrID2, err := s.CreateAddress(userID, "bar@proton.ch", password, true) require.NoError(t, err) allowSyncToProgress.Store(true) @@ -552,7 +552,7 @@ func TestBridge_MessageCreateDuringSync(t *testing.T) { }) // User AddrID2 event as a check point to see when the new address was created. - addrID, err := s.CreateAddress(userID, "bar@proton.ch", password) + addrID, err := s.CreateAddress(userID, "bar@proton.ch", password, true) require.NoError(t, err) // At most two events can be published, one for the first address, then for the second. @@ -663,7 +663,7 @@ func TestBridge_AddressOrderChangeDuringSyncInCombinedModeDoesNotTriggerBadEvent require.Equal(t, 1, len(info.Addresses)) require.Equal(t, info.Addresses[0], "user@proton.local") - addrID2, err := s.CreateAddress(userID, "foo@"+s.GetDomain(), password) + addrID2, err := s.CreateAddress(userID, "foo@"+s.GetDomain(), password, true) require.NoError(t, err) require.NoError(t, s.SetAddressOrder(userID, []string{addrID2, addrID})) diff --git a/internal/bridge/user_event_test.go b/internal/bridge/user_event_test.go index 18e99c1c..51246ba7 100644 --- a/internal/bridge/user_event_test.go +++ b/internal/bridge/user_event_test.go @@ -304,7 +304,7 @@ func TestBridge_User_AddressEvents_NoBadEvent(t *testing.T) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) { userLoginAndSync(ctx, t, bridge, "user", password) - addrID, err = s.CreateAddress(userID, "other@pm.me", password) + addrID, err = s.CreateAddress(userID, "other@pm.me", password, true) require.NoError(t, err) userContinueEventProcess(ctx, t, s, bridge) @@ -312,7 +312,7 @@ func TestBridge_User_AddressEvents_NoBadEvent(t *testing.T) { userContinueEventProcess(ctx, t, s, bridge) }) - otherID, err := s.CreateAddress(userID, "another@pm.me", password) + otherID, err := s.CreateAddress(userID, "another@pm.me", password, true) require.NoError(t, err) require.NoError(t, s.RemoveAddress(userID, otherID)) @@ -328,6 +328,87 @@ func TestBridge_User_AddressEvents_NoBadEvent(t *testing.T) { }) } +func TestBridge_User_AddressEvents_BYOEAddressAdded(t *testing.T) { + withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) { + // Create a user. + userID, addrID, err := s.CreateUser("user", password) + require.NoError(t, err) + + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) { + userLoginAndSync(ctx, t, bridge, "user", password) + + // Create an additional proton address + addrID, err = s.CreateAddress(userID, "other@pm.me", password, true) + require.NoError(t, err) + userContinueEventProcess(ctx, t, s, bridge) + require.NoError(t, s.AddAddressCreatedEvent(userID, addrID)) + userContinueEventProcess(ctx, t, s, bridge) + + userInfo, err := bridge.GetUserInfo(userID) + require.NoError(t, err) + require.Equal(t, 2, len(userInfo.Addresses)) + + // Create an external address with sending disabled. + externalID, err := s.CreateExternalAddress(userID, "another@yahoo.com", password, false) + require.NoError(t, err) + userContinueEventProcess(ctx, t, s, bridge) + require.NoError(t, s.AddAddressCreatedEvent(userID, externalID)) + userContinueEventProcess(ctx, t, s, bridge) + + // User addresses should still return 2, as we ignore the external address. + userInfo, err = bridge.GetUserInfo(userID) + require.NoError(t, err) + require.Equal(t, 2, len(userInfo.Addresses)) + + // Create an external address w. sending enabled. This is considered a BYOE address. + BYOEAddrID, err := s.CreateExternalAddress(userID, "other@yahoo.com", password, true) + require.NoError(t, err) + userContinueEventProcess(ctx, t, s, bridge) + require.NoError(t, s.AddAddressCreatedEvent(userID, BYOEAddrID)) + userContinueEventProcess(ctx, t, s, bridge) + + userInfo, err = bridge.GetUserInfo(userID) + require.NoError(t, err) + require.Equal(t, 3, len(userInfo.Addresses)) + }) + }) +} + +func TestBridge_User_AddressEvents_ExternalAddressSendChanged(t *testing.T) { + withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) { + userID, _, err := s.CreateUser("user", password) + require.NoError(t, err) + + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) { + userLoginAndSync(ctx, t, bridge, "user", password) + + // Create an additional external address. + externalID, err := s.CreateExternalAddress(userID, "other@yahoo.me", password, false) + require.NoError(t, err) + userContinueEventProcess(ctx, t, s, bridge) + require.NoError(t, s.AddAddressCreatedEvent(userID, externalID)) + userContinueEventProcess(ctx, t, s, bridge) + + // We expect only one address, the external one without sending should not be considered a valid address. + userInfo, err := bridge.GetUserInfo(userID) + require.NoError(t, err) + require.Equal(t, 1, len(userInfo.Addresses)) + + // Change it to allow sending such that it becomes a BYOE address. + err = s.ChangeAddressAllowSend(userID, externalID, true) + require.NoError(t, err) + userContinueEventProcess(ctx, t, s, bridge) + require.NoError(t, s.AddAddressUpdatedEvent(userID, externalID)) + userContinueEventProcess(ctx, t, s, bridge) + + // We should now have 2 usable addresses listed. + userInfo, err = bridge.GetUserInfo(userID) + require.NoError(t, err) + require.Equal(t, 2, len(userInfo.Addresses)) + }) + }) +} + func TestBridge_User_AddressEventUpdatedForAddressThatDoesNotExist_NoBadEvent(t *testing.T) { withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) { // Create a user. @@ -694,7 +775,7 @@ func TestBridge_User_DisableEnableAddress(t *testing.T) { require.NoError(t, err) // Create an additional address for the user. - aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password) + aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password, true) require.NoError(t, err) withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) { @@ -745,7 +826,7 @@ func TestBridge_User_CreateDisabledAddress(t *testing.T) { require.NoError(t, err) // Create an additional address for the user. - aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password) + aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password, true) require.NoError(t, err) // Immediately disable the address. diff --git a/internal/bridge/user_test.go b/internal/bridge/user_test.go index 044ef610..f5e410a2 100644 --- a/internal/bridge/user_test.go +++ b/internal/bridge/user_test.go @@ -658,7 +658,7 @@ func TestBridge_UserInfo_Alias(t *testing.T) { require.NoError(t, err) // Give the new user an alias. - require.NoError(t, getErr(s.CreateAddress(userID, "alias@pm.me", []byte("password")))) + require.NoError(t, getErr(s.CreateAddress(userID, "alias@pm.me", []byte("password"), true))) // Login the user. require.NoError(t, getErr(bridge.LoginFull(ctx, "primary", []byte("password"), nil, nil))) @@ -706,7 +706,7 @@ func TestBridge_User_GetAddresses(t *testing.T) { // Create a user. userID, _, err := s.CreateUser("user", password) require.NoError(t, err) - addrID2, err := s.CreateAddress(userID, "user@external.com", []byte("password")) + addrID2, err := s.CreateAddress(userID, "user@external.com", password, false) require.NoError(t, err) require.NoError(t, s.ChangeAddressType(userID, addrID2, proton.AddressTypeExternal)) @@ -720,6 +720,29 @@ func TestBridge_User_GetAddresses(t *testing.T) { }) } +func TestBridge_User_GetAddresses_BYOE(t *testing.T) { + withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) { + // Create a user. + userID, _, err := s.CreateUser("user", password) + require.NoError(t, err) + // Add a non-sending external address. + _, err = s.CreateExternalAddress(userID, "user@external.com", password, false) + require.NoError(t, err) + // Add a BYOE address. + _, err = s.CreateExternalAddress(userID, "user2@external.com", password, true) + require.NoError(t, err) + + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, _ *bridge.Mocks) { + userLoginAndSync(ctx, t, bridge, "user", password) + info, err := bridge.GetUserInfo(userID) + require.NoError(t, err) + require.Equal(t, 2, len(info.Addresses)) + require.Equal(t, info.Addresses[0], "user@proton.local") + require.Equal(t, info.Addresses[1], "user2@external.com") + }) + }) +} + // getErr returns the error that was passed to it. func getErr[T any](_ T, err error) error { return err diff --git a/internal/user/user.go b/internal/user/user.go index cb4ff4ed..938870cc 100644 --- a/internal/user/user.go +++ b/internal/user/user.go @@ -739,7 +739,7 @@ func (user *User) protonAddresses() []proton.Address { } addresses := xslices.Filter(maps.Values(apiAddrs), func(addr proton.Address) bool { - return addr.Status == proton.AddressStatusEnabled && addr.Type != proton.AddressTypeExternal + return addr.Status == proton.AddressStatusEnabled && (addr.IsBYOEAddress() || addr.Type != proton.AddressTypeExternal) }) slices.SortFunc(addresses, func(a, b proton.Address) bool { diff --git a/internal/user/user_test.go b/internal/user/user_test.go index ecea39ea..a5cf056d 100644 --- a/internal/user/user_test.go +++ b/internal/user/user_test.go @@ -110,7 +110,7 @@ func withAccount(tb testing.TB, s *server.Server, username, password string, ali addrIDs := []string{addrID} for _, email := range aliases { - addrID, err := s.CreateAddress(userID, email, []byte(password)) + addrID, err := s.CreateAddress(userID, email, []byte(password), true) require.NoError(tb, err) require.NoError(tb, s.ChangeAddressDisplayName(userID, addrID, email+" (Display Name)"))