diff --git a/go.mod b/go.mod index e01a6075..69d486c0 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 github.com/Masterminds/semver/v3 v3.1.1 - github.com/ProtonMail/gluon v0.14.2-0.20230130104154-2c64e59b8f54 + github.com/ProtonMail/gluon v0.14.2-0.20230201115538-18e0b89693fc github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a github.com/ProtonMail/go-proton-api v0.3.1-0.20230130144605-4b05f1e5c427 github.com/ProtonMail/go-rfc5322 v0.11.0 diff --git a/go.sum b/go.sum index 3e164c8b..49a271b5 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo= github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk= github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g= -github.com/ProtonMail/gluon v0.14.2-0.20230130104154-2c64e59b8f54 h1:uUg8CDiYTMlbvGijzoN0fb72vwDJD7hMjgNTbmAHxRc= -github.com/ProtonMail/gluon v0.14.2-0.20230130104154-2c64e59b8f54/go.mod h1:HYHr7hG7LPWI1S50M8NfHRb1kYi5B+Yu4/N/H+y+JUY= +github.com/ProtonMail/gluon v0.14.2-0.20230201115538-18e0b89693fc h1:q7sX422Eu9H97v2sLRPmPFi8yBJtNwQRzVN9DSmBHvc= +github.com/ProtonMail/gluon v0.14.2-0.20230201115538-18e0b89693fc/go.mod h1:HYHr7hG7LPWI1S50M8NfHRb1kYi5B+Yu4/N/H+y+JUY= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= diff --git a/internal/app/bridge.go b/internal/app/bridge.go index 668aca59..38ce3c82 100644 --- a/internal/app/bridge.go +++ b/internal/app/bridge.go @@ -23,6 +23,7 @@ import ( "runtime" "github.com/Masterminds/semver/v3" + "github.com/ProtonMail/gluon/imap" "github.com/ProtonMail/go-autostart" "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/ProtonMail/proton-bridge/v3/internal/bridge" @@ -110,6 +111,7 @@ func withBridge( //nolint:funlen // Crash and report stuff crashHandler, reporter, + imap.DefaultEpochUIDValidityGenerator(), // The logging stuff. c.String(flagLogIMAP) == "client" || c.String(flagLogIMAP) == "all", diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index 1a6de766..43cd7087 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -30,6 +30,7 @@ import ( "github.com/Masterminds/semver/v3" "github.com/ProtonMail/gluon" imapEvents "github.com/ProtonMail/gluon/events" + "github.com/ProtonMail/gluon/imap" "github.com/ProtonMail/gluon/reporter" "github.com/ProtonMail/gluon/watcher" "github.com/ProtonMail/go-proton-api" @@ -122,6 +123,8 @@ type Bridge struct { // goUpdate triggers a check/install of updates. goUpdate func() + + uidValidityGenerator imap.UIDValidityGenerator } // New creates a new bridge. @@ -140,6 +143,7 @@ func New( //nolint:funlen proxyCtl ProxyController, // the DoH controller crashHandler async.PanicHandler, reporter reporter.Reporter, + uidValidityGenerator imap.UIDValidityGenerator, logIMAPClient, logIMAPServer bool, // whether to log IMAP client/server activity logSMTP bool, // whether to log SMTP activity @@ -169,6 +173,7 @@ func New( //nolint:funlen api, identifier, proxyCtl, + uidValidityGenerator, logIMAPClient, logIMAPServer, logSMTP, ) if err != nil { @@ -214,6 +219,7 @@ func newBridge( api *proton.Manager, identifier Identifier, proxyCtl ProxyController, + uidValidityGenerator imap.UIDValidityGenerator, logIMAPClient, logIMAPServer, logSMTP bool, ) (*Bridge, error) { @@ -252,6 +258,7 @@ func newBridge( logIMAPServer, imapEventCh, tasks, + uidValidityGenerator, ) if err != nil { return nil, fmt.Errorf("failed to create IMAP server: %w", err) @@ -298,6 +305,8 @@ func newBridge( lastVersion: lastVersion, tasks: tasks, + + uidValidityGenerator: uidValidityGenerator, } bridge.smtpServer = newSMTPServer(bridge, tlsConfig, logSMTP) diff --git a/internal/bridge/bridge_test.go b/internal/bridge/bridge_test.go index 298a7d66..c9514709 100644 --- a/internal/bridge/bridge_test.go +++ b/internal/bridge/bridge_test.go @@ -29,6 +29,7 @@ import ( "time" "github.com/Masterminds/semver/v3" + "github.com/ProtonMail/gluon/imap" "github.com/ProtonMail/go-proton-api" "github.com/ProtonMail/go-proton-api/server" "github.com/ProtonMail/go-proton-api/server/backend" @@ -594,6 +595,9 @@ func withMocks(t *testing.T, tests func(*bridge.Mocks)) { tests(mocks) } +// Needs to be global to survive bridge shutdown/startup in unit tests as they happen to fast. +var testUIDValidityGenerator = imap.DefaultEpochUIDValidityGenerator() + // withBridge creates a new bridge which points to the given API URL and uses the given keychain, and closes it when done. func withBridgeNoMocks( ctx context.Context, @@ -639,6 +643,7 @@ func withBridgeNoMocks( mocks.ProxyCtl, mocks.CrashHandler, mocks.Reporter, + testUIDValidityGenerator, // The logging stuff. os.Getenv("BRIDGE_LOG_IMAP_CLIENT") == "1", diff --git a/internal/bridge/imap.go b/internal/bridge/imap.go index e8deded8..a9f411e0 100644 --- a/internal/bridge/imap.go +++ b/internal/bridge/imap.go @@ -28,6 +28,7 @@ import ( "github.com/Masterminds/semver/v3" "github.com/ProtonMail/gluon" imapEvents "github.com/ProtonMail/gluon/events" + "github.com/ProtonMail/gluon/imap" "github.com/ProtonMail/gluon/reporter" "github.com/ProtonMail/gluon/store" "github.com/ProtonMail/proton-bridge/v3/internal/async" @@ -254,6 +255,7 @@ func newIMAPServer( logClient, logServer bool, eventCh chan<- imapEvents.Event, tasks *async.Group, + uidValidityGenerator imap.UIDValidityGenerator, ) (*gluon.Server, error) { gluonCacheDir = ApplyGluonCachePathSuffix(gluonCacheDir) gluonConfigDir = ApplyGluonConfigPathSuffix(gluonConfigDir) @@ -297,6 +299,7 @@ func newIMAPServer( gluon.WithLogger(imapClientLog, imapServerLog), getGluonVersionInfo(version), gluon.WithReporter(reporter), + gluon.WithUIDValidityGenerator(uidValidityGenerator), ) if err != nil { return nil, err diff --git a/internal/bridge/refresh_test.go b/internal/bridge/refresh_test.go index eb1f4159..ad4192f6 100644 --- a/internal/bridge/refresh_test.go +++ b/internal/bridge/refresh_test.go @@ -57,6 +57,7 @@ func TestBridge_Refresh(t *testing.T) { require.Equal(t, userID, (<-syncCh).UserID) }) + var uidValidities = make(map[string]uint32, len(names)) // If we then connect an IMAP client, it should see all the labels with UID validity of 1. withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) { mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).AnyTimes() @@ -73,7 +74,7 @@ func TestBridge_Refresh(t *testing.T) { for _, name := range names { status, err := client.Select("Folders/"+name, false) require.NoError(t, err) - require.Equal(t, uint32(1000), status.UidValidity) + uidValidities[name] = status.UidValidity } }) @@ -106,7 +107,7 @@ func TestBridge_Refresh(t *testing.T) { for _, name := range names { status, err := client.Select("Folders/"+name, false) require.NoError(t, err) - require.Equal(t, uint32(1001), status.UidValidity) + require.Greater(t, status.UidValidity, uidValidities[name]) } }) }) diff --git a/internal/bridge/settings.go b/internal/bridge/settings.go index 6d276561..1e0b1a0f 100644 --- a/internal/bridge/settings.go +++ b/internal/bridge/settings.go @@ -163,6 +163,7 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error bridge.logIMAPServer, bridge.imapEventCh, bridge.tasks, + bridge.uidValidityGenerator, ) if err != nil { panic(fmt.Errorf("failed to create new IMAP server: %w", err)) diff --git a/internal/user/imap.go b/internal/user/imap.go index 9de879e2..9251440b 100644 --- a/internal/user/imap.go +++ b/internal/user/imap.go @@ -477,16 +477,6 @@ func (conn *imapConnector) GetUpdates() <-chan imap.Update { }, conn.updateChLock) } -// GetUIDValidity returns the default UID validity for this user. -func (conn *imapConnector) GetUIDValidity() imap.UID { - return conn.vault.GetUIDValidity(conn.addrID) -} - -// SetUIDValidity sets the default UID validity for this user. -func (conn *imapConnector) SetUIDValidity(validity imap.UID) error { - return conn.vault.SetUIDValidity(conn.addrID, validity) -} - // IsMailboxVisible returns whether this mailbox should be visible over IMAP. func (conn *imapConnector) IsMailboxVisible(_ context.Context, mailboxID imap.MailboxID) bool { return atomic.LoadUint32(&conn.showAllMail) != 0 || mailboxID != proton.AllMailLabel diff --git a/internal/vault/types_user.go b/internal/vault/types_user.go index 0fe118da..af1958e3 100644 --- a/internal/vault/types_user.go +++ b/internal/vault/types_user.go @@ -17,8 +17,6 @@ package vault -import "github.com/ProtonMail/gluon/imap" - // UserData holds information about a single bridge user. // The user may or may not be logged in. type UserData struct { @@ -28,7 +26,6 @@ type UserData struct { GluonKey []byte GluonIDs map[string]string - UIDValidity map[string]imap.UID BridgePass []byte // raw token represented as byte slice (needs to be encoded) AddressMode AddressMode @@ -79,7 +76,6 @@ func newDefaultUser(userID, username, primaryEmail, authUID, authRef string, key GluonKey: newRandomToken(32), GluonIDs: make(map[string]string), - UIDValidity: make(map[string]imap.UID), BridgePass: newRandomToken(16), AddressMode: CombinedMode, diff --git a/internal/vault/user.go b/internal/vault/user.go index 1f3bf8b6..f97358dc 100644 --- a/internal/vault/user.go +++ b/internal/vault/user.go @@ -20,7 +20,6 @@ package vault import ( "fmt" - "github.com/ProtonMail/gluon/imap" "github.com/bradenaw/juniper/xslices" "golang.org/x/exp/slices" ) @@ -81,24 +80,6 @@ func (user *User) RemoveGluonID(addrID, gluonID string) error { return err } -func (user *User) GetUIDValidity(addrID string) imap.UID { - if validity, ok := user.vault.getUser(user.userID).UIDValidity[addrID]; ok { - return validity - } - - if err := user.SetUIDValidity(addrID, 1000); err != nil { - panic(err) - } - - return user.GetUIDValidity(addrID) -} - -func (user *User) SetUIDValidity(addrID string, validity imap.UID) error { - return user.vault.modUser(user.userID, func(data *UserData) { - data.UIDValidity[addrID] = validity - }) -} - // AddressMode returns the user's address mode. func (user *User) AddressMode() AddressMode { return user.vault.getUser(user.userID).AddressMode @@ -208,10 +189,6 @@ func (user *User) ClearSyncStatus() error { data.SyncStatus = SyncStatus{} data.EventID = "" - - for addrID := range data.UIDValidity { - data.UIDValidity[addrID]++ - } }) } diff --git a/tests/ctx_bridge_test.go b/tests/ctx_bridge_test.go index fb43cd60..3d7a75bd 100644 --- a/tests/ctx_bridge_test.go +++ b/tests/ctx_bridge_test.go @@ -29,6 +29,7 @@ import ( "runtime" "time" + "github.com/ProtonMail/gluon/imap" "github.com/ProtonMail/gluon/queue" "github.com/ProtonMail/proton-bridge/v3/internal/bridge" "github.com/ProtonMail/proton-bridge/v3/internal/constants" @@ -160,6 +161,7 @@ func (t *testCtx) initBridge() (<-chan events.Event, error) { t.mocks.ProxyCtl, t.mocks.CrashHandler, t.reporter, + imap.DefaultEpochUIDValidityGenerator(), // Logging stuff logIMAP,