chore: merge Umshiang to master
This commit is contained in:
commit
a3b8fabb26
|
@ -3,6 +3,13 @@
|
|||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
|
||||
## Umshiang Bridge 3.5.2
|
||||
|
||||
### Fixed
|
||||
* GODT-3003: Ensure IMAP State is reset after vault corruption.
|
||||
* GODT-3001: Only create system labels during system label sync.
|
||||
|
||||
|
||||
## Umshiang Bridge 3.5.1
|
||||
|
||||
### Fixed
|
||||
|
|
3
Makefile
3
Makefile
|
@ -11,7 +11,7 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))
|
|||
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
||||
|
||||
# Keep version hardcoded so app build works also without Git repository.
|
||||
BRIDGE_APP_VERSION?=3.5.1+git
|
||||
BRIDGE_APP_VERSION?=3.5.2+git
|
||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||
APP_FULL_NAME:=Proton Mail Bridge
|
||||
APP_VENDOR:=Proton AG
|
||||
|
@ -304,6 +304,7 @@ ApplyStageInput,BuildStageInput,BuildStageOutput,DownloadStageInput,DownloadStag
|
|||
StateProvider,Regulator,UpdateApplier,MessageBuilder,APIClient,Reporter,DownloadRateModifier \
|
||||
> tmp
|
||||
mv tmp internal/services/syncservice/mocks_test.go
|
||||
mockgen --package mocks github.com/ProtonMail/gluon/connector IMAPStateWrite > internal/services/imapservice/mocks/mocks.go
|
||||
|
||||
lint: gofiles lint-golang lint-license lint-dependencies lint-changelog lint-bug-report
|
||||
|
||||
|
|
2
go.mod
2
go.mod
|
@ -5,7 +5,7 @@ go 1.20
|
|||
require (
|
||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||
github.com/Masterminds/semver/v3 v3.2.0
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20230829112217-5d5c25c504b5
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20231009084701-3af0474b0b3c
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20230831064234-0e3a549b3f36
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.7.1-proton
|
||||
|
|
4
go.sum
4
go.sum
|
@ -23,8 +23,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.17.1-0.20230829112217-5d5c25c504b5 h1:C/8P5NHAKi2yCKez+OZ5rSR8SsL7k8si4pK4SE2QtV8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20230829112217-5d5c25c504b5/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20231009084701-3af0474b0b3c h1:gUDu4pOswgbou0QczfreNiXQFrmvVlpSh8Q+vft/JvI=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20231009084701-3af0474b0b3c/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo=
|
||||
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-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
||||
|
|
|
@ -585,7 +585,7 @@ func TestBridge_MissingGluonStore(t *testing.T) {
|
|||
require.NoError(t, os.RemoveAll(gluonDir))
|
||||
|
||||
// Bridge starts but can't find the gluon store dir; there should be no error.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
withBridgeWaitForServers(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
// ...
|
||||
})
|
||||
})
|
||||
|
|
|
@ -37,6 +37,7 @@ import (
|
|||
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice"
|
||||
"github.com/bradenaw/juniper/iterator"
|
||||
"github.com/bradenaw/juniper/stream"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
|
@ -579,6 +580,67 @@ func TestBridge_MessageCreateDuringSync(t *testing.T) {
|
|||
}, server.WithTLS(false))
|
||||
}
|
||||
|
||||
func TestBridge_CorruptedVaultClearsPreviousIMAPSyncState(t *testing.T) {
|
||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||
userID, addrID, err := s.CreateUser("imap", password)
|
||||
require.NoError(t, err)
|
||||
|
||||
labelID, err := s.CreateLabel(userID, "folder", "", proton.LabelTypeFolder)
|
||||
require.NoError(t, err)
|
||||
|
||||
withClient(ctx, t, s, "imap", password, func(ctx context.Context, c *proton.Client) {
|
||||
createNumMessages(ctx, t, c, addrID, labelID, 100)
|
||||
})
|
||||
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
syncCh, done := chToType[events.Event, events.SyncFinished](bridge.GetEvents(events.SyncFinished{}))
|
||||
defer done()
|
||||
|
||||
var err error
|
||||
|
||||
userID, err = bridge.LoginFull(context.Background(), "imap", password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wait for sync to finish
|
||||
require.Equal(t, userID, (<-syncCh).UserID)
|
||||
})
|
||||
|
||||
settingsPath, err := locator.ProvideSettingsPath()
|
||||
require.NoError(t, err)
|
||||
|
||||
syncConfigPath, err := locator.ProvideIMAPSyncConfigPath()
|
||||
require.NoError(t, err)
|
||||
|
||||
syncStatePath := imapservice.GetSyncConfigPath(syncConfigPath, userID)
|
||||
// Check sync state is complete
|
||||
{
|
||||
state, err := imapservice.NewSyncState(syncStatePath)
|
||||
require.NoError(t, err)
|
||||
syncStatus, err := state.GetSyncStatus(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.True(t, syncStatus.IsComplete())
|
||||
}
|
||||
|
||||
// corrupt the vault
|
||||
require.NoError(t, os.WriteFile(filepath.Join(settingsPath, "vault.enc"), []byte("Trash!"), 0o600))
|
||||
|
||||
// Bridge starts but can't find the gluon database dir; there should be no error.
|
||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||
_, err := bridge.LoginFull(context.Background(), "imap", password, nil, nil)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
// Check sync state is reset.
|
||||
{
|
||||
state, err := imapservice.NewSyncState(syncStatePath)
|
||||
require.NoError(t, err)
|
||||
syncStatus, err := state.GetSyncStatus(context.Background())
|
||||
require.NoError(t, err)
|
||||
require.False(t, syncStatus.IsComplete())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func withClient(ctx context.Context, t *testing.T, s *server.Server, username string, password []byte, fn func(context.Context, *proton.Client)) { //nolint:unparam
|
||||
m := proton.New(
|
||||
proton.WithHostURL(s.GetHostURL()),
|
||||
|
|
|
@ -63,6 +63,7 @@ type Connector struct {
|
|||
log *logrus.Entry
|
||||
|
||||
sharedCache *SharedCache
|
||||
syncState *SyncState
|
||||
}
|
||||
|
||||
func NewConnector(
|
||||
|
@ -75,6 +76,7 @@ func NewConnector(
|
|||
panicHandler async.PanicHandler,
|
||||
telemetry Telemetry,
|
||||
showAllMail bool,
|
||||
syncState *SyncState,
|
||||
) *Connector {
|
||||
userID := identityState.UserID()
|
||||
|
||||
|
@ -106,6 +108,7 @@ func NewConnector(
|
|||
}),
|
||||
|
||||
sharedCache: NewSharedCached(),
|
||||
syncState: syncState,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,9 +117,35 @@ func (s *Connector) StateClose() {
|
|||
s.updateCh.CloseAndDiscardQueued()
|
||||
}
|
||||
|
||||
func (s *Connector) Init(_ context.Context, cache connector.IMAPState) error {
|
||||
func (s *Connector) Init(ctx context.Context, cache connector.IMAPState) error {
|
||||
s.sharedCache.Set(cache)
|
||||
return nil
|
||||
|
||||
return cache.Write(ctx, func(ctx context.Context, write connector.IMAPStateWrite) error {
|
||||
rd := s.labels.Read()
|
||||
defer rd.Close()
|
||||
|
||||
mboxes, err := write.GetMailboxesWithoutAttrib(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Attempt to fix bug when a vault got corrupted, but the sync state did not get reset leading to
|
||||
// all labels being written to the root level. If we detect this happened, reset the sync state.
|
||||
{
|
||||
applied, err := fixGODT3003Labels(ctx, s.log, mboxes, rd, write)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if applied {
|
||||
s.log.Debug("Patched folders/labels after GODT-3003 incident, resetting sync state.")
|
||||
if err := s.syncState.ClearSyncStatus(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Connector) Authorize(ctx context.Context, username string, password []byte) bool {
|
||||
|
@ -745,3 +774,41 @@ func (s *Connector) createDraft(ctx context.Context, literal []byte, addrKR *cry
|
|||
func (s *Connector) publishUpdate(_ context.Context, update imap.Update) {
|
||||
s.updateCh.Enqueue(update)
|
||||
}
|
||||
|
||||
func fixGODT3003Labels(
|
||||
ctx context.Context,
|
||||
log *logrus.Entry,
|
||||
mboxes []imap.MailboxNoAttrib,
|
||||
rd labelsRead,
|
||||
write connector.IMAPStateWrite,
|
||||
) (bool, error) {
|
||||
var applied bool
|
||||
for _, mbox := range mboxes {
|
||||
lbl, ok := rd.GetLabel(string(mbox.ID))
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if lbl.Type == proton.LabelTypeFolder {
|
||||
if mbox.Name[0] != folderPrefix {
|
||||
log.WithField("labelID", mbox.ID.ShortID()).Debug("Found folder without prefix, patching")
|
||||
if err := write.PatchMailboxHierarchyWithoutTransforms(ctx, mbox.ID, xslices.Insert(mbox.Name, 0, folderPrefix)); err != nil {
|
||||
return false, fmt.Errorf("failed to update mailbox name: %w", err)
|
||||
}
|
||||
|
||||
applied = true
|
||||
}
|
||||
} else if lbl.Type == proton.LabelTypeLabel {
|
||||
if mbox.Name[0] != labelPrefix {
|
||||
log.WithField("labelID", mbox.ID.ShortID()).Debug("Found label without prefix, patching")
|
||||
if err := write.PatchMailboxHierarchyWithoutTransforms(ctx, mbox.ID, xslices.Insert(mbox.Name, 0, labelPrefix)); err != nil {
|
||||
return false, fmt.Errorf("failed to update mailbox name: %w", err)
|
||||
}
|
||||
|
||||
applied = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return applied, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
// Copyright (c) 2023 Proton AG
|
||||
//
|
||||
// This file is part of Proton Mail Bridge.
|
||||
//
|
||||
// Proton Mail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Mail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package imapservice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/gluon/imap"
|
||||
"github.com/ProtonMail/go-proton-api"
|
||||
"github.com/ProtonMail/proton-bridge/v3/internal/services/imapservice/mocks"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFixGODT3003Labels(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
log := logrus.WithField("test", "test")
|
||||
|
||||
sharedLabels := newRWLabels()
|
||||
wr := sharedLabels.Write()
|
||||
wr.SetLabel("foo", proton.Label{
|
||||
ID: "foo",
|
||||
ParentID: "bar",
|
||||
Name: "Foo",
|
||||
Path: []string{"bar", "Foo"},
|
||||
Color: "",
|
||||
Type: proton.LabelTypeFolder,
|
||||
})
|
||||
|
||||
wr.SetLabel("0", proton.Label{
|
||||
ID: "0",
|
||||
ParentID: "",
|
||||
Name: "Inbox",
|
||||
Path: []string{"Inbox"},
|
||||
Color: "",
|
||||
Type: proton.LabelTypeSystem,
|
||||
})
|
||||
|
||||
wr.SetLabel("bar", proton.Label{
|
||||
ID: "bar",
|
||||
ParentID: "",
|
||||
Name: "boo",
|
||||
Path: []string{"bar"},
|
||||
Color: "",
|
||||
Type: proton.LabelTypeFolder,
|
||||
})
|
||||
|
||||
wr.SetLabel("my_label", proton.Label{
|
||||
ID: "my_label",
|
||||
ParentID: "",
|
||||
Name: "MyLabel",
|
||||
Path: []string{"MyLabel"},
|
||||
Color: "",
|
||||
Type: proton.LabelTypeLabel,
|
||||
})
|
||||
|
||||
wr.SetLabel("my_label2", proton.Label{
|
||||
ID: "my_label2",
|
||||
ParentID: "",
|
||||
Name: "MyLabel2",
|
||||
Path: []string{labelPrefix, "MyLabel2"},
|
||||
Color: "",
|
||||
Type: proton.LabelTypeLabel,
|
||||
})
|
||||
wr.Close()
|
||||
|
||||
mboxs := []imap.MailboxNoAttrib{
|
||||
{
|
||||
ID: "0",
|
||||
Name: []string{"Inbox"},
|
||||
},
|
||||
{
|
||||
ID: "bar",
|
||||
Name: []string{"bar"},
|
||||
},
|
||||
{
|
||||
ID: "foo",
|
||||
Name: []string{"bar", "Foo"},
|
||||
},
|
||||
{
|
||||
ID: "my_label",
|
||||
Name: []string{"MyLabel"},
|
||||
},
|
||||
{
|
||||
ID: "my_label2",
|
||||
Name: []string{labelPrefix, "MyLabel2"},
|
||||
},
|
||||
}
|
||||
|
||||
rd := sharedLabels.Read()
|
||||
defer rd.Close()
|
||||
|
||||
imapState := mocks.NewMockIMAPStateWrite(mockCtrl)
|
||||
|
||||
imapState.EXPECT().PatchMailboxHierarchyWithoutTransforms(gomock.Any(), gomock.Eq(imap.MailboxID("bar")), gomock.Eq([]string{folderPrefix, "bar"}))
|
||||
imapState.EXPECT().PatchMailboxHierarchyWithoutTransforms(gomock.Any(), gomock.Eq(imap.MailboxID("foo")), gomock.Eq([]string{folderPrefix, "bar", "Foo"}))
|
||||
imapState.EXPECT().PatchMailboxHierarchyWithoutTransforms(gomock.Any(), gomock.Eq(imap.MailboxID("my_label")), gomock.Eq([]string{labelPrefix, "MyLabel"}))
|
||||
|
||||
applied, err := fixGODT3003Labels(context.Background(), log, mboxs, rd, imapState)
|
||||
require.NoError(t, err)
|
||||
require.True(t, applied)
|
||||
}
|
||||
|
||||
func TestFixGODT3003Labels_Noop(t *testing.T) {
|
||||
mockCtrl := gomock.NewController(t)
|
||||
|
||||
log := logrus.WithField("test", "test")
|
||||
|
||||
sharedLabels := newRWLabels()
|
||||
wr := sharedLabels.Write()
|
||||
wr.SetLabel("foo", proton.Label{
|
||||
ID: "foo",
|
||||
ParentID: "bar",
|
||||
Name: "Foo",
|
||||
Path: []string{folderPrefix, "bar", "Foo"},
|
||||
Color: "",
|
||||
Type: proton.LabelTypeFolder,
|
||||
})
|
||||
|
||||
wr.SetLabel("0", proton.Label{
|
||||
ID: "0",
|
||||
ParentID: "",
|
||||
Name: "Inbox",
|
||||
Path: []string{"Inbox"},
|
||||
Color: "",
|
||||
Type: proton.LabelTypeSystem,
|
||||
})
|
||||
|
||||
wr.SetLabel("bar", proton.Label{
|
||||
ID: "bar",
|
||||
ParentID: "",
|
||||
Name: "bar",
|
||||
Path: []string{folderPrefix, "bar"},
|
||||
Color: "",
|
||||
Type: proton.LabelTypeFolder,
|
||||
})
|
||||
|
||||
wr.SetLabel("my_label", proton.Label{
|
||||
ID: "my_label",
|
||||
ParentID: "",
|
||||
Name: "MyLabel",
|
||||
Path: []string{labelPrefix, "MyLabel"},
|
||||
Color: "",
|
||||
Type: proton.LabelTypeLabel,
|
||||
})
|
||||
|
||||
wr.SetLabel("my_label2", proton.Label{
|
||||
ID: "my_label2",
|
||||
ParentID: "",
|
||||
Name: "MyLabel2",
|
||||
Path: []string{labelPrefix, "MyLabel2"},
|
||||
Color: "",
|
||||
Type: proton.LabelTypeLabel,
|
||||
})
|
||||
wr.Close()
|
||||
|
||||
mboxs := []imap.MailboxNoAttrib{
|
||||
{
|
||||
ID: "0",
|
||||
Name: []string{"Inbox"},
|
||||
},
|
||||
{
|
||||
ID: "bar",
|
||||
Name: []string{folderPrefix, "bar"},
|
||||
},
|
||||
{
|
||||
ID: "foo",
|
||||
Name: []string{folderPrefix, "bar", "Foo"},
|
||||
},
|
||||
{
|
||||
ID: "my_label",
|
||||
Name: []string{labelPrefix, "MyLabel"},
|
||||
},
|
||||
{
|
||||
ID: "my_label2",
|
||||
Name: []string{labelPrefix, "MyLabel2"},
|
||||
},
|
||||
}
|
||||
|
||||
rd := sharedLabels.Read()
|
||||
defer rd.Close()
|
||||
|
||||
imapState := mocks.NewMockIMAPStateWrite(mockCtrl)
|
||||
applied, err := fixGODT3003Labels(context.Background(), log, mboxs, rd, imapState)
|
||||
require.NoError(t, err)
|
||||
require.False(t, applied)
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/ProtonMail/gluon/connector (interfaces: IMAPStateWrite)
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
|
||||
imap "github.com/ProtonMail/gluon/imap"
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
// MockIMAPStateWrite is a mock of IMAPStateWrite interface.
|
||||
type MockIMAPStateWrite struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockIMAPStateWriteMockRecorder
|
||||
}
|
||||
|
||||
// MockIMAPStateWriteMockRecorder is the mock recorder for MockIMAPStateWrite.
|
||||
type MockIMAPStateWriteMockRecorder struct {
|
||||
mock *MockIMAPStateWrite
|
||||
}
|
||||
|
||||
// NewMockIMAPStateWrite creates a new mock instance.
|
||||
func NewMockIMAPStateWrite(ctrl *gomock.Controller) *MockIMAPStateWrite {
|
||||
mock := &MockIMAPStateWrite{ctrl: ctrl}
|
||||
mock.recorder = &MockIMAPStateWriteMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockIMAPStateWrite) EXPECT() *MockIMAPStateWriteMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// CreateMailbox mocks base method.
|
||||
func (m *MockIMAPStateWrite) CreateMailbox(arg0 context.Context, arg1 imap.Mailbox) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateMailbox", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateMailbox indicates an expected call of CreateMailbox.
|
||||
func (mr *MockIMAPStateWriteMockRecorder) CreateMailbox(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateMailbox", reflect.TypeOf((*MockIMAPStateWrite)(nil).CreateMailbox), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetMailboxCount mocks base method.
|
||||
func (m *MockIMAPStateWrite) GetMailboxCount(arg0 context.Context) (int, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetMailboxCount", arg0)
|
||||
ret0, _ := ret[0].(int)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetMailboxCount indicates an expected call of GetMailboxCount.
|
||||
func (mr *MockIMAPStateWriteMockRecorder) GetMailboxCount(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMailboxCount", reflect.TypeOf((*MockIMAPStateWrite)(nil).GetMailboxCount), arg0)
|
||||
}
|
||||
|
||||
// GetMailboxesWithoutAttrib mocks base method.
|
||||
func (m *MockIMAPStateWrite) GetMailboxesWithoutAttrib(arg0 context.Context) ([]imap.MailboxNoAttrib, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetMailboxesWithoutAttrib", arg0)
|
||||
ret0, _ := ret[0].([]imap.MailboxNoAttrib)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetMailboxesWithoutAttrib indicates an expected call of GetMailboxesWithoutAttrib.
|
||||
func (mr *MockIMAPStateWriteMockRecorder) GetMailboxesWithoutAttrib(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMailboxesWithoutAttrib", reflect.TypeOf((*MockIMAPStateWrite)(nil).GetMailboxesWithoutAttrib), arg0)
|
||||
}
|
||||
|
||||
// GetSettings mocks base method.
|
||||
func (m *MockIMAPStateWrite) GetSettings(arg0 context.Context) (string, bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetSettings", arg0)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(bool)
|
||||
ret2, _ := ret[2].(error)
|
||||
return ret0, ret1, ret2
|
||||
}
|
||||
|
||||
// GetSettings indicates an expected call of GetSettings.
|
||||
func (mr *MockIMAPStateWriteMockRecorder) GetSettings(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSettings", reflect.TypeOf((*MockIMAPStateWrite)(nil).GetSettings), arg0)
|
||||
}
|
||||
|
||||
// PatchMailboxHierarchyWithoutTransforms mocks base method.
|
||||
func (m *MockIMAPStateWrite) PatchMailboxHierarchyWithoutTransforms(arg0 context.Context, arg1 imap.MailboxID, arg2 []string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "PatchMailboxHierarchyWithoutTransforms", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// PatchMailboxHierarchyWithoutTransforms indicates an expected call of PatchMailboxHierarchyWithoutTransforms.
|
||||
func (mr *MockIMAPStateWriteMockRecorder) PatchMailboxHierarchyWithoutTransforms(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PatchMailboxHierarchyWithoutTransforms", reflect.TypeOf((*MockIMAPStateWrite)(nil).PatchMailboxHierarchyWithoutTransforms), arg0, arg1, arg2)
|
||||
}
|
||||
|
||||
// StoreSettings mocks base method.
|
||||
func (m *MockIMAPStateWrite) StoreSettings(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "StoreSettings", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// StoreSettings indicates an expected call of StoreSettings.
|
||||
func (mr *MockIMAPStateWriteMockRecorder) StoreSettings(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StoreSettings", reflect.TypeOf((*MockIMAPStateWrite)(nil).StoreSettings), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateMessageFlags mocks base method.
|
||||
func (m *MockIMAPStateWrite) UpdateMessageFlags(arg0 context.Context, arg1 imap.MessageID, arg2 imap.FlagSet) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateMessageFlags", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateMessageFlags indicates an expected call of UpdateMessageFlags.
|
||||
func (mr *MockIMAPStateWriteMockRecorder) UpdateMessageFlags(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateMessageFlags", reflect.TypeOf((*MockIMAPStateWrite)(nil).UpdateMessageFlags), arg0, arg1, arg2)
|
||||
}
|
|
@ -158,7 +158,7 @@ func NewService(
|
|||
syncUpdateApplier: syncUpdateApplier,
|
||||
syncMessageBuilder: syncMessageBuilder,
|
||||
syncReporter: syncReporter,
|
||||
syncConfigPath: getSyncConfigPath(syncConfigDir, identityState.User.ID),
|
||||
syncConfigPath: GetSyncConfigPath(syncConfigDir, identityState.User.ID),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -498,6 +498,7 @@ func (s *Service) buildConnectors() (map[string]*Connector, error) {
|
|||
s.panicHandler,
|
||||
s.telemetry,
|
||||
s.showAllMail,
|
||||
s.syncStateProvider,
|
||||
)
|
||||
|
||||
return connectors, nil
|
||||
|
@ -514,6 +515,7 @@ func (s *Service) buildConnectors() (map[string]*Connector, error) {
|
|||
s.panicHandler,
|
||||
s.telemetry,
|
||||
s.showAllMail,
|
||||
s.syncStateProvider,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -644,6 +646,6 @@ type setAddressModeReq struct {
|
|||
|
||||
type getSyncFailedMessagesReq struct{}
|
||||
|
||||
func getSyncConfigPath(path string, userID string) string {
|
||||
func GetSyncConfigPath(path string, userID string) string {
|
||||
return filepath.Join(path, fmt.Sprintf("sync-%v", userID))
|
||||
}
|
||||
|
|
|
@ -128,6 +128,7 @@ func addNewAddressSplitMode(ctx context.Context, s *Service, addrID string) erro
|
|||
s.panicHandler,
|
||||
s.telemetry,
|
||||
s.showAllMail,
|
||||
s.syncStateProvider,
|
||||
)
|
||||
|
||||
if err := s.serverManager.AddIMAPUser(ctx, connector, connector.addrID, s.gluonIDProvider, s.syncStateProvider); err != nil {
|
||||
|
|
|
@ -220,7 +220,7 @@ func (s *SyncState) loadUnsafe() error {
|
|||
}
|
||||
|
||||
func DeleteSyncState(configDir, userID string) error {
|
||||
path := getSyncConfigPath(configDir, userID)
|
||||
path := GetSyncConfigPath(configDir, userID)
|
||||
|
||||
if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
|
@ -234,7 +234,7 @@ func MigrateVaultSettings(
|
|||
hasLabels, hasMessages bool,
|
||||
failedMessageIDs []string,
|
||||
) (bool, error) {
|
||||
filePath := getSyncConfigPath(configDir, userID)
|
||||
filePath := GetSyncConfigPath(configDir, userID)
|
||||
|
||||
_, err := os.ReadFile(filePath) //nolint:gosec
|
||||
if err == nil {
|
||||
|
|
|
@ -29,7 +29,7 @@ import (
|
|||
|
||||
func TestMigrateSyncSettings_AlreadyExists(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
testFile := getSyncConfigPath(tmpDir, "test")
|
||||
testFile := GetSyncConfigPath(tmpDir, "test")
|
||||
|
||||
expected, err := generateTestState(testFile)
|
||||
require.NoError(t, err)
|
||||
|
@ -53,7 +53,7 @@ func TestMigrateSyncSettings_DoesNotExist(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.True(t, migrated)
|
||||
|
||||
state, err := NewSyncState(getSyncConfigPath(tmpDir, "test"))
|
||||
state, err := NewSyncState(GetSyncConfigPath(tmpDir, "test"))
|
||||
require.NoError(t, err)
|
||||
status, err := state.GetSyncStatus(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -119,6 +119,10 @@ func (s *SyncUpdateApplier) SyncSystemLabelsOnly(ctx context.Context, labels map
|
|||
continue
|
||||
}
|
||||
|
||||
if label.Type != proton.LabelTypeSystem {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, c := range connectors {
|
||||
update := newSystemMailboxCreatedUpdate(imap.MailboxID(label.ID), label.Name)
|
||||
updates = append(updates, update)
|
||||
|
|
|
@ -390,6 +390,11 @@ func (sm *Service) handleAddIMAPUserImpl(ctx context.Context,
|
|||
} else {
|
||||
log.Info("Creating new IMAP user")
|
||||
|
||||
// GODT-3003: Ensure previous IMAP sync state is cleared if we run into code path after vault corruption.
|
||||
if err := syncStateProvider.ClearSyncStatus(ctx); err != nil {
|
||||
return fmt.Errorf("failed to reset sync status: %w", err)
|
||||
}
|
||||
|
||||
gluonID, err := sm.imapServer.AddUser(ctx, connector, idProvider.GluonKey())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add IMAP user: %w", err)
|
||||
|
|
Loading…
Reference in New Issue