mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-22 01:56:44 +00:00
GODT-1815: Combined/Split mode
This commit is contained in:
@ -232,6 +232,13 @@ func (bridge *Bridge) GetErrors() []error {
|
||||
}
|
||||
|
||||
func (bridge *Bridge) Close(ctx context.Context) error {
|
||||
// Abort any ongoing syncs.
|
||||
for _, user := range bridge.users {
|
||||
if err := user.AbortSync(ctx); err != nil {
|
||||
return fmt.Errorf("failed to abort sync: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Close the IMAP server.
|
||||
if err := bridge.closeIMAP(ctx); err != nil {
|
||||
logrus.WithError(err).Error("Failed to close IMAP server")
|
||||
|
||||
@ -4,19 +4,24 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/certs"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/focus"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/user"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/useragent"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
|
||||
"github.com/ProtonMail/proton-bridge/v2/tests"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gitlab.protontech.ch/go/liteapi/server"
|
||||
"gitlab.protontech.ch/go/liteapi/server/account"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -29,6 +34,13 @@ var (
|
||||
v2_4_0 = semver.MustParse("2.4.0")
|
||||
)
|
||||
|
||||
func init() {
|
||||
user.DefaultEventPeriod = 100 * time.Millisecond
|
||||
user.DefaultEventJitter = 0
|
||||
account.GenerateKey = tests.FastGenerateKey
|
||||
certs.GenerateCert = tests.FastGenerateCert
|
||||
}
|
||||
|
||||
func TestBridge_ConnStatus(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, dialer *bridge.TestDialer, locator bridge.Locator, vaultKey []byte) {
|
||||
withBridge(t, ctx, s.GetHostURL(), dialer, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
@ -156,19 +168,27 @@ func TestBridge_CheckUpdate(t *testing.T) {
|
||||
// Disable autoupdate for this test.
|
||||
require.NoError(t, bridge.SetAutoUpdate(false))
|
||||
|
||||
// Get a stream of update events.
|
||||
updateCh, done := bridge.GetEvents(events.UpdateNotAvailable{}, events.UpdateAvailable{})
|
||||
// Get a stream of update not available events.
|
||||
noUpdateCh, done := bridge.GetEvents(events.UpdateNotAvailable{})
|
||||
defer done()
|
||||
|
||||
// We are currently on the latest version.
|
||||
bridge.CheckForUpdates()
|
||||
require.Equal(t, events.UpdateNotAvailable{}, <-updateCh)
|
||||
|
||||
// we should receive an event indicating that no update is available.
|
||||
require.Equal(t, events.UpdateNotAvailable{}, <-noUpdateCh)
|
||||
|
||||
// Simulate a new version being available.
|
||||
mocks.Updater.SetLatestVersion(v2_4_0, v2_3_0)
|
||||
|
||||
// Get a stream of update available events.
|
||||
updateCh, done := bridge.GetEvents(events.UpdateAvailable{})
|
||||
defer done()
|
||||
|
||||
// Check for updates.
|
||||
bridge.CheckForUpdates()
|
||||
|
||||
// We should receive an event indicating that an update is available.
|
||||
require.Equal(t, events.UpdateAvailable{
|
||||
Version: updater.VersionInfo{
|
||||
Version: v2_4_0,
|
||||
@ -188,7 +208,7 @@ func TestBridge_AutoUpdate(t *testing.T) {
|
||||
require.NoError(t, bridge.SetAutoUpdate(true))
|
||||
|
||||
// Get a stream of update events.
|
||||
updateCh, done := bridge.GetEvents(events.UpdateNotAvailable{}, events.UpdateInstalled{})
|
||||
updateCh, done := bridge.GetEvents(events.UpdateInstalled{})
|
||||
defer done()
|
||||
|
||||
// Simulate a new version being available.
|
||||
@ -196,6 +216,8 @@ func TestBridge_AutoUpdate(t *testing.T) {
|
||||
|
||||
// Check for updates.
|
||||
bridge.CheckForUpdates()
|
||||
|
||||
// We should receive an event indicating that the update was installed.
|
||||
require.Equal(t, events.UpdateInstalled{
|
||||
Version: updater.VersionInfo{
|
||||
Version: v2_4_0,
|
||||
@ -213,8 +235,8 @@ func TestBridge_ManualUpdate(t *testing.T) {
|
||||
// Disable autoupdate for this test.
|
||||
require.NoError(t, bridge.SetAutoUpdate(false))
|
||||
|
||||
// Get a stream of update events.
|
||||
updateCh, done := bridge.GetEvents(events.UpdateNotAvailable{}, events.UpdateAvailable{})
|
||||
// Get a stream of update available events.
|
||||
updateCh, done := bridge.GetEvents(events.UpdateAvailable{})
|
||||
defer done()
|
||||
|
||||
// Simulate a new version being available, but it's too new for us.
|
||||
@ -222,6 +244,8 @@ func TestBridge_ManualUpdate(t *testing.T) {
|
||||
|
||||
// Check for updates.
|
||||
bridge.CheckForUpdates()
|
||||
|
||||
// We should receive an event indicating an update is available, but we can't install it.
|
||||
require.Equal(t, events.UpdateAvailable{
|
||||
Version: updater.VersionInfo{
|
||||
Version: v2_4_0,
|
||||
|
||||
@ -14,8 +14,9 @@ func (bridge *Bridge) ConfigureAppleMail(userID, address string) error {
|
||||
return ErrNoSuchUser
|
||||
}
|
||||
|
||||
// TODO: Handle split mode!
|
||||
if address == "" {
|
||||
address = user.Addresses()[0]
|
||||
address = user.Emails()[0]
|
||||
}
|
||||
|
||||
// If configuring apple mail for Catalina or newer, users should use SSL.
|
||||
@ -32,7 +33,7 @@ func (bridge *Bridge) ConfigureAppleMail(userID, address string) error {
|
||||
bridge.vault.GetIMAPSSL(),
|
||||
bridge.vault.GetSMTPSSL(),
|
||||
address,
|
||||
strings.Join(user.Addresses(), ","),
|
||||
strings.Join(user.Emails(), ","),
|
||||
user.BridgePass(),
|
||||
)
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||
@ -96,40 +97,39 @@ func (bridge *Bridge) GetGluonDir() string {
|
||||
|
||||
func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error {
|
||||
if newGluonDir == bridge.GetGluonDir() {
|
||||
return nil
|
||||
return fmt.Errorf("new gluon dir is the same as the old one")
|
||||
}
|
||||
|
||||
if err := bridge.closeIMAP(context.Background()); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to close IMAP: %w", err)
|
||||
}
|
||||
|
||||
if err := moveDir(bridge.GetGluonDir(), newGluonDir); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to move gluon dir: %w", err)
|
||||
}
|
||||
|
||||
if err := bridge.vault.SetGluonDir(newGluonDir); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to set new gluon dir: %w", err)
|
||||
}
|
||||
|
||||
imapServer, err := newIMAPServer(bridge.vault.GetGluonDir(), bridge.curVersion, bridge.tlsConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range bridge.users {
|
||||
imapConn, err := user.NewGluonConnector(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := imapServer.LoadUser(context.Background(), imapConn, user.GluonID(), user.GluonKey()); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("failed to create new IMAP server: %w", err)
|
||||
}
|
||||
|
||||
bridge.imapServer = imapServer
|
||||
|
||||
return bridge.serveIMAP()
|
||||
for _, user := range bridge.users {
|
||||
if err := bridge.addIMAPUser(ctx, user); err != nil {
|
||||
return fmt.Errorf("failed to add IMAP user: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := bridge.serveIMAP(); err != nil {
|
||||
return fmt.Errorf("failed to serve IMAP: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetProxyAllowed() bool {
|
||||
|
||||
@ -23,8 +23,8 @@ func (backend *smtpBackend) Login(state *smtp.ConnectionState, username string,
|
||||
defer backend.usersLock.RUnlock()
|
||||
|
||||
for _, user := range backend.users {
|
||||
if slices.Contains(user.Addresses(), username) && user.BridgePass() == password {
|
||||
return user.NewSMTPSession(username)
|
||||
if slices.Contains(user.Emails(), username) && user.BridgePass() == password {
|
||||
return user.NewSMTPSession(username), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ type UserInfo struct {
|
||||
Addresses []string
|
||||
|
||||
// AddressMode is the user's address mode.
|
||||
AddressMode AddressMode
|
||||
AddressMode vault.AddressMode
|
||||
|
||||
// BridgePass is the user's bridge password.
|
||||
BridgePass string
|
||||
@ -41,13 +41,6 @@ type UserInfo struct {
|
||||
MaxSpace int
|
||||
}
|
||||
|
||||
type AddressMode int
|
||||
|
||||
const (
|
||||
SplitMode AddressMode = iota
|
||||
CombinedMode
|
||||
)
|
||||
|
||||
// GetUserIDs returns the IDs of all known users (authorized or not).
|
||||
func (bridge *Bridge) GetUserIDs() []string {
|
||||
return bridge.vault.GetUserIDs()
|
||||
@ -62,7 +55,7 @@ func (bridge *Bridge) GetUserInfo(userID string) (UserInfo, error) {
|
||||
|
||||
user, ok := bridge.users[userID]
|
||||
if !ok {
|
||||
return getUserInfo(vaultUser.UserID(), vaultUser.Username()), nil
|
||||
return getUserInfo(vaultUser.UserID(), vaultUser.Username(), vaultUser.AddressMode()), nil
|
||||
}
|
||||
|
||||
return getConnUserInfo(user), nil
|
||||
@ -153,12 +146,43 @@ func (bridge *Bridge) DeleteUser(ctx context.Context, userID string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bridge *Bridge) GetAddressMode(userID string) (AddressMode, error) {
|
||||
panic("TODO")
|
||||
}
|
||||
// SetAddressMode sets the address mode for the given user.
|
||||
func (bridge *Bridge) SetAddressMode(ctx context.Context, userID string, mode vault.AddressMode) error {
|
||||
user, ok := bridge.users[userID]
|
||||
if !ok {
|
||||
return ErrNoSuchUser
|
||||
}
|
||||
|
||||
func (bridge *Bridge) SetAddressMode(userID string, mode AddressMode) error {
|
||||
panic("TODO")
|
||||
if user.GetAddressMode() == mode {
|
||||
return fmt.Errorf("address mode is already %q", mode)
|
||||
}
|
||||
|
||||
if err := user.AbortSync(ctx); err != nil {
|
||||
return fmt.Errorf("failed to abort sync: %w", err)
|
||||
}
|
||||
|
||||
for _, gluonID := range user.GetGluonIDs() {
|
||||
if err := bridge.imapServer.RemoveUser(ctx, gluonID, true); err != nil {
|
||||
return fmt.Errorf("failed to remove user from IMAP server: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := user.SetAddressMode(ctx, mode); err != nil {
|
||||
return fmt.Errorf("failed to set address mode: %w", err)
|
||||
}
|
||||
|
||||
if err := bridge.addIMAPUser(ctx, user); err != nil {
|
||||
return fmt.Errorf("failed to add IMAP user: %w", err)
|
||||
}
|
||||
|
||||
bridge.publish(events.AddressModeChanged{
|
||||
UserID: userID,
|
||||
AddressMode: mode,
|
||||
})
|
||||
|
||||
user.DoSync(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadUsers loads authorized users from the vault.
|
||||
@ -177,7 +201,7 @@ func (bridge *Bridge) loadUsers(ctx context.Context) error {
|
||||
logrus.WithError(err).Error("Failed to load connected user")
|
||||
|
||||
if _, ok := err.(*resty.ResponseError); ok {
|
||||
if err := user.Clear(); err != nil {
|
||||
if err := bridge.vault.ClearUser(userID); err != nil {
|
||||
logrus.WithError(err).Error("Failed to clear user")
|
||||
}
|
||||
}
|
||||
@ -231,33 +255,41 @@ func (bridge *Bridge) addUser(
|
||||
if slices.Contains(bridge.vault.GetUserIDs(), apiUser.ID) {
|
||||
existingUser, err := bridge.addExistingUser(ctx, client, apiUser, apiAddrs, userKR, addrKRs, authUID, authRef, saltedKeyPass)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to add existing user: %w", err)
|
||||
}
|
||||
|
||||
user = existingUser
|
||||
} else {
|
||||
newUser, err := bridge.addNewUser(ctx, client, apiUser, apiAddrs, userKR, addrKRs, authUID, authRef, saltedKeyPass)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to add new user: %w", err)
|
||||
}
|
||||
|
||||
user = newUser
|
||||
}
|
||||
|
||||
go func() {
|
||||
for event := range user.GetNotifyCh() {
|
||||
switch event := event.(type) {
|
||||
case events.UserDeauth:
|
||||
if err := bridge.logoutUser(context.Background(), event.UserID, false, false); err != nil {
|
||||
logrus.WithError(err).Error("Failed to logout user")
|
||||
}
|
||||
}
|
||||
// Connects the user's address(es) to gluon.
|
||||
if err := bridge.addIMAPUser(ctx, user); err != nil {
|
||||
return fmt.Errorf("failed to add IMAP user: %w", err)
|
||||
}
|
||||
|
||||
bridge.publish(event)
|
||||
// Handle events coming from the user before forwarding them to the bridge.
|
||||
// For example, if the user's addresses change, we need to update them in gluon.
|
||||
go func() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
for event := range user.GetEventCh() {
|
||||
if err := bridge.handleUserEvent(ctx, user, event); err != nil {
|
||||
logrus.WithError(err).Error("Failed to handle user event")
|
||||
} else {
|
||||
bridge.publish(event)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Gluon will set the IMAP ID in the context, if known, before making requests on behalf of this user.
|
||||
// As such, if we find this ID in the context, we should use it to update our user agent.
|
||||
client.AddPreRequestHook(func(ctx context.Context, req *resty.Request) error {
|
||||
if imapID, ok := imap.GetIMAPIDFromContext(ctx); ok {
|
||||
bridge.identifier.SetClient(imapID.Name, imapID.Version)
|
||||
@ -266,6 +298,11 @@ func (bridge *Bridge) addUser(
|
||||
return nil
|
||||
})
|
||||
|
||||
// TODO: Replace this with proper sync manager.
|
||||
if !user.HasSync() {
|
||||
user.DoSync(ctx)
|
||||
}
|
||||
|
||||
bridge.publish(events.UserLoggedIn{
|
||||
UserID: user.ID(),
|
||||
})
|
||||
@ -293,25 +330,6 @@ func (bridge *Bridge) addNewUser(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gluonKey, err := crypto.RandomToken(32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imapConn, err := user.NewGluonConnector(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gluonID, err := bridge.imapServer.AddUser(ctx, imapConn, gluonKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := vaultUser.SetGluonAuth(gluonID, gluonKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := bridge.smtpBackend.addUser(user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -349,15 +367,6 @@ func (bridge *Bridge) addExistingUser(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
imapConn, err := user.NewGluonConnector(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := bridge.imapServer.LoadUser(ctx, imapConn, user.GluonID(), user.GluonKey()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := bridge.smtpBackend.addUser(user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -376,31 +385,39 @@ func (bridge *Bridge) logoutUser(ctx context.Context, userID string, withAPI, wi
|
||||
return ErrNoSuchUser
|
||||
}
|
||||
|
||||
vaultUser, err := bridge.vault.GetUser(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := bridge.imapServer.RemoveUser(ctx, vaultUser.GluonID(), withFiles); err != nil {
|
||||
return err
|
||||
// TODO: The sync should be canceled by the sync manager.
|
||||
if err := user.AbortSync(ctx); err != nil {
|
||||
return fmt.Errorf("failed to abort user sync: %w", err)
|
||||
}
|
||||
|
||||
if err := bridge.smtpBackend.removeUser(user); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to remove SMTP user: %w", err)
|
||||
}
|
||||
|
||||
for _, gluonID := range user.GetGluonIDs() {
|
||||
if err := bridge.imapServer.RemoveUser(ctx, gluonID, withFiles); err != nil {
|
||||
return fmt.Errorf("failed to remove IMAP user: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if withAPI {
|
||||
if err := user.Logout(ctx); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to logout user: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := user.Close(ctx); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to close user: %w", err)
|
||||
}
|
||||
|
||||
if err := vaultUser.Clear(); err != nil {
|
||||
return err
|
||||
if err := bridge.vault.ClearUser(userID); err != nil {
|
||||
return fmt.Errorf("failed to clear user: %w", err)
|
||||
}
|
||||
|
||||
if withFiles {
|
||||
if err := bridge.vault.DeleteUser(userID); err != nil {
|
||||
return fmt.Errorf("failed to delete user: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
delete(bridge.users, userID)
|
||||
@ -412,12 +429,39 @@ func (bridge *Bridge) logoutUser(ctx context.Context, userID string, withAPI, wi
|
||||
return nil
|
||||
}
|
||||
|
||||
// addIMAPUser connects the given user to gluon.
|
||||
func (bridge *Bridge) addIMAPUser(ctx context.Context, user *user.User) error {
|
||||
imapConn, err := user.NewIMAPConnectors()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create IMAP connectors: %w", err)
|
||||
}
|
||||
|
||||
for addrID, imapConn := range imapConn {
|
||||
if gluonID, ok := user.GetGluonID(addrID); ok {
|
||||
if err := bridge.imapServer.LoadUser(ctx, imapConn, gluonID, user.GluonKey()); err != nil {
|
||||
return fmt.Errorf("failed to load IMAP user: %w", err)
|
||||
}
|
||||
} else {
|
||||
gluonID, err := bridge.imapServer.AddUser(ctx, imapConn, user.GluonKey())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add IMAP user: %w", err)
|
||||
}
|
||||
|
||||
if err := user.SetGluonID(addrID, gluonID); err != nil {
|
||||
return fmt.Errorf("failed to set IMAP user ID: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getUserInfo returns information about a disconnected user.
|
||||
func getUserInfo(userID, username string) UserInfo {
|
||||
func getUserInfo(userID, username string, addressMode vault.AddressMode) UserInfo {
|
||||
return UserInfo{
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
AddressMode: CombinedMode,
|
||||
AddressMode: addressMode,
|
||||
}
|
||||
}
|
||||
|
||||
@ -427,8 +471,8 @@ func getConnUserInfo(user *user.User) UserInfo {
|
||||
Connected: true,
|
||||
UserID: user.ID(),
|
||||
Username: user.Name(),
|
||||
Addresses: user.Addresses(),
|
||||
AddressMode: CombinedMode,
|
||||
Addresses: user.Emails(),
|
||||
AddressMode: user.GetAddressMode(),
|
||||
BridgePass: user.BridgePass(),
|
||||
UsedSpace: user.UsedSpace(),
|
||||
MaxSpace: user.MaxSpace(),
|
||||
118
internal/bridge/user_events.go
Normal file
118
internal/bridge/user_events.go
Normal file
@ -0,0 +1,118 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/user"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
|
||||
)
|
||||
|
||||
func (bridge *Bridge) handleUserEvent(ctx context.Context, user *user.User, event events.Event) error {
|
||||
switch event := event.(type) {
|
||||
case events.UserAddressCreated:
|
||||
if err := bridge.handleUserAddressCreated(ctx, user, event); err != nil {
|
||||
return fmt.Errorf("failed to handle user address created event: %w", err)
|
||||
}
|
||||
|
||||
case events.UserAddressUpdated:
|
||||
if err := bridge.handleUserAddressUpdated(ctx, user, event); err != nil {
|
||||
return fmt.Errorf("failed to handle user address updated event: %w", err)
|
||||
}
|
||||
|
||||
case events.UserAddressDeleted:
|
||||
if err := bridge.handleUserAddressDeleted(ctx, user, event); err != nil {
|
||||
return fmt.Errorf("failed to handle user address deleted event: %w", err)
|
||||
}
|
||||
|
||||
case events.UserDeauth:
|
||||
if err := bridge.logoutUser(context.Background(), event.UserID, false, false); err != nil {
|
||||
return fmt.Errorf("failed to logout user: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bridge *Bridge) handleUserAddressCreated(ctx context.Context, user *user.User, event events.UserAddressCreated) error {
|
||||
switch user.GetAddressMode() {
|
||||
case vault.CombinedMode:
|
||||
for addrID, gluonID := range user.GetGluonIDs() {
|
||||
if err := bridge.imapServer.RemoveUser(ctx, gluonID, false); err != nil {
|
||||
return fmt.Errorf("failed to remove user from IMAP server: %w", err)
|
||||
}
|
||||
|
||||
imapConn, err := user.NewIMAPConnector(addrID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create IMAP connector: %w", err)
|
||||
}
|
||||
|
||||
if err := bridge.imapServer.LoadUser(ctx, imapConn, gluonID, user.GluonKey()); err != nil {
|
||||
return fmt.Errorf("failed to add user to IMAP server: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
case vault.SplitMode:
|
||||
imapConn, err := user.NewIMAPConnector(event.AddressID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create IMAP connector: %w", err)
|
||||
}
|
||||
|
||||
gluonID, err := bridge.imapServer.AddUser(ctx, imapConn, user.GluonKey())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add user to IMAP server: %w", err)
|
||||
}
|
||||
|
||||
if err := user.SetGluonID(event.AddressID, gluonID); err != nil {
|
||||
return fmt.Errorf("failed to set gluon ID: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Handle addresses that have been disabled!
|
||||
func (bridge *Bridge) handleUserAddressUpdated(ctx context.Context, user *user.User, event events.UserAddressUpdated) error {
|
||||
switch user.GetAddressMode() {
|
||||
case vault.CombinedMode:
|
||||
return fmt.Errorf("not implemented")
|
||||
|
||||
case vault.SplitMode:
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bridge *Bridge) handleUserAddressDeleted(ctx context.Context, user *user.User, event events.UserAddressDeleted) error {
|
||||
switch user.GetAddressMode() {
|
||||
case vault.CombinedMode:
|
||||
for addrID, gluonID := range user.GetGluonIDs() {
|
||||
if err := bridge.imapServer.RemoveUser(ctx, gluonID, false); err != nil {
|
||||
return fmt.Errorf("failed to remove user from IMAP server: %w", err)
|
||||
}
|
||||
|
||||
imapConn, err := user.NewIMAPConnector(addrID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create IMAP connector: %w", err)
|
||||
}
|
||||
|
||||
if err := bridge.imapServer.LoadUser(ctx, imapConn, gluonID, user.GluonKey()); err != nil {
|
||||
return fmt.Errorf("failed to add user to IMAP server: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
case vault.SplitMode:
|
||||
gluonID, ok := user.GetGluonID(event.AddressID)
|
||||
if !ok {
|
||||
return fmt.Errorf("gluon ID not found for address %s", event.AddressID)
|
||||
}
|
||||
|
||||
if err := bridge.imapServer.RemoveUser(ctx, gluonID, true); err != nil {
|
||||
return fmt.Errorf("failed to remove user from IMAP server: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gitlab.protontech.ch/go/liteapi/server"
|
||||
)
|
||||
@ -283,3 +284,30 @@ func TestBridge_BridgePass(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestBridge_AddressMode(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, dialer *bridge.TestDialer, locator bridge.Locator, storeKey []byte) {
|
||||
withBridge(t, ctx, s.GetHostURL(), dialer, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// Login the user.
|
||||
userID, err := bridge.LoginUser(ctx, username, password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get the user's info.
|
||||
info, err := bridge.GetUserInfo(userID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The user is in combined mode by default.
|
||||
require.Equal(t, vault.CombinedMode, info.AddressMode)
|
||||
|
||||
// Put the user in split mode.
|
||||
require.NoError(t, bridge.SetAddressMode(ctx, userID, vault.SplitMode))
|
||||
|
||||
// Get the user's info.
|
||||
info, err = bridge.GetUserInfo(userID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The user is in split mode.
|
||||
require.Equal(t, vault.SplitMode, info.AddressMode)
|
||||
})
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user