From 7ed8d76d845a84a5e4d18548141b71555c57b9b4 Mon Sep 17 00:00:00 2001 From: James Houlahan Date: Mon, 21 Nov 2022 01:08:06 +0100 Subject: [PATCH] GODT-1976: Migrate app settings from prefs.json --- internal/app/app.go | 4 + internal/app/migration.go | 122 +++++++++++++++++++++++++++++++ internal/app/migration_test.go | 82 +++++++++++++++++++++ internal/app/testdata/prefs.json | 31 ++++++++ internal/vault/settings.go | 21 ++++++ 5 files changed, 260 insertions(+) create mode 100644 internal/app/migration_test.go create mode 100644 internal/app/testdata/prefs.json diff --git a/internal/app/app.go b/internal/app/app.go index 75bf15d4..5558fceb 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -194,6 +194,10 @@ func run(c *cli.Context) error { //nolint:funlen return withSingleInstance(locations, version, func() error { // Unlock the encrypted vault. return WithVault(locations, func(vault *vault.Vault, insecure, corrupt bool) error { + if err := migrateOldSettings(vault); err != nil { + logrus.WithError(err).Error("Failed to migrate old settings") + } + // Load the cookies from the vault. return withCookieJar(vault, func(cookieJar http.CookieJar) error { // Create a new bridge instance. diff --git a/internal/app/migration.go b/internal/app/migration.go index 097f3907..68514925 100644 --- a/internal/app/migration.go +++ b/internal/app/migration.go @@ -18,18 +18,140 @@ package app import ( + "encoding/json" + "fmt" "os" "path/filepath" "strconv" "strings" "time" + "github.com/Masterminds/semver/v3" + "github.com/ProtonMail/proton-bridge/v2/internal/updater" + "github.com/ProtonMail/proton-bridge/v2/internal/vault" "github.com/allan-simon/go-singleinstance" "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) +// nolint:gosec +func migrateOldSettings(vault *vault.Vault) error { + configDir, err := os.UserConfigDir() + if err != nil { + return fmt.Errorf("failed to get user config dir: %w", err) + } + + b, err := os.ReadFile(filepath.Join(configDir, "protonmail", "bridge", "prefs.json")) + if err != nil { + return fmt.Errorf("failed to read old prefs file: %w", err) + } + + return migratePrefsToVault(vault, b) +} + +// nolint:funlen +func migratePrefsToVault(vault *vault.Vault, b []byte) error { + var prefs struct { + IMAPPort int `json:"user_port_imap,,string"` + SMTPPort int `json:"user_port_smtp,,string"` + SMTPSSL bool `json:"user_ssl_smtp,,string"` + + AutoUpdate bool `json:"autoupdate,,string"` + UpdateChannel updater.Channel `json:"update_channel"` + UpdateRollout float64 `json:"rollout,,string"` + + FirstStart bool `json:"first_time_start,,string"` + FirstStartGUI bool `json:"first_time_start_gui,,string"` + ColorScheme string `json:"color_scheme"` + LastVersion *semver.Version `json:"last_used_version"` + Autostart bool `json:"autostart,,string"` + + AllowProxy bool `json:"allow_proxy,,string"` + FetchWorkers int `json:"fetch_workers,,string"` + AttachmentWorkers int `json:"attachment_workers,,string"` + ShowAllMail bool `json:"is_all_mail_visible,,string"` + + Cookies string `json:"cookies"` + } + + if err := json.Unmarshal(b, &prefs); err != nil { + return fmt.Errorf("failed to unmarshal old prefs file: %w", err) + } + + var errs error + + if err := vault.SetIMAPPort(prefs.IMAPPort); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate IMAP port: %w", err)) + } + + if err := vault.SetSMTPPort(prefs.SMTPPort); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate SMTP port: %w", err)) + } + + if err := vault.SetSMTPSSL(prefs.SMTPSSL); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate SMTP SSL: %w", err)) + } + + if err := vault.SetAutoUpdate(prefs.AutoUpdate); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate auto update: %w", err)) + } + + if err := vault.SetUpdateChannel(prefs.UpdateChannel); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate update channel: %w", err)) + } + + if err := vault.SetUpdateRollout(prefs.UpdateRollout); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate rollout: %w", err)) + } + + if err := vault.SetFirstStart(prefs.FirstStart); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate first start: %w", err)) + } + + if err := vault.SetFirstStartGUI(prefs.FirstStartGUI); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate first start GUI: %w", err)) + } + + if err := vault.SetColorScheme(prefs.ColorScheme); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate color scheme: %w", err)) + } + + if err := vault.SetLastVersion(prefs.LastVersion); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate last version: %w", err)) + } + + if err := vault.SetAutostart(prefs.Autostart); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate autostart: %w", err)) + } + + if err := vault.SetProxyAllowed(prefs.AllowProxy); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate allow proxy: %w", err)) + } + + if err := vault.SetShowAllMail(prefs.ShowAllMail); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate show all mail: %w", err)) + } + + if err := vault.SetSyncWorkers(prefs.FetchWorkers); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate sync workers: %w", err)) + } + + if err := vault.SetSyncBuffer(prefs.FetchWorkers); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate sync buffer: %w", err)) + } + + if err := vault.SetSyncAttPool(prefs.AttachmentWorkers); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate sync attachment pool: %w", err)) + } + + if err := vault.SetCookies([]byte(prefs.Cookies)); err != nil { + errs = multierror.Append(errs, fmt.Errorf("failed to migrate cookies: %w", err)) + } + + return errs +} + func migrateOldVersions() (allErrors error) { cacheDir, cacheError := os.UserCacheDir() if cacheError != nil { diff --git a/internal/app/migration_test.go b/internal/app/migration_test.go new file mode 100644 index 00000000..74690636 --- /dev/null +++ b/internal/app/migration_test.go @@ -0,0 +1,82 @@ +// Copyright (c) 2022 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 . + +package app + +import ( + "net/http/cookiejar" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/ProtonMail/proton-bridge/v2/internal/cookies" + "github.com/ProtonMail/proton-bridge/v2/internal/updater" + "github.com/ProtonMail/proton-bridge/v2/internal/vault" + "github.com/stretchr/testify/require" +) + +func TestMigrateOldVaultFromJSON(t *testing.T) { + // Create a new vault. + vault, corrupt, err := vault.New(t.TempDir(), t.TempDir(), []byte("my secret key")) + require.NoError(t, err) + require.False(t, corrupt) + + // load the old prefs file. + b, err := os.ReadFile(filepath.Join("testdata", "prefs.json")) + require.NoError(t, err) + + // Migrate the old prefs file to the new vault. + require.NoError(t, migratePrefsToVault(vault, b)) + + // Check that the IMAP and SMTP prefs are migrated. + require.Equal(t, 2143, vault.GetIMAPPort()) + require.Equal(t, 2025, vault.GetSMTPPort()) + require.True(t, vault.GetSMTPSSL()) + + // Check that the update channel is migrated. + require.True(t, vault.GetAutoUpdate()) + require.Equal(t, updater.EarlyChannel, vault.GetUpdateChannel()) + require.Equal(t, 0.4849529004202015, vault.GetUpdateRollout()) + + // Check that the app settings have been migrated. + require.False(t, vault.GetFirstStart()) + require.True(t, vault.GetFirstStartGUI()) + require.Equal(t, "blablabla", vault.GetColorScheme()) + require.Equal(t, "2.3.0+git", vault.GetLastVersion().String()) + require.True(t, vault.GetAutostart()) + + // Check that the other app settings have been migrated. + require.Equal(t, 16, vault.SyncWorkers()) + require.Equal(t, 16, vault.SyncBuffer()) + require.Equal(t, 16, vault.SyncAttPool()) + require.False(t, vault.GetProxyAllowed()) + require.False(t, vault.GetShowAllMail()) + + // Check that the cookies have been migrated. + jar, err := cookiejar.New(nil) + require.NoError(t, err) + + cookies, err := cookies.NewCookieJar(jar, vault) + require.NoError(t, err) + + url, err := url.Parse("https://api.protonmail.ch") + require.NoError(t, err) + + // There should be a cookie for the API. + require.NotEmpty(t, cookies.Cookies(url)) +} diff --git a/internal/app/testdata/prefs.json b/internal/app/testdata/prefs.json new file mode 100644 index 00000000..b6a16d46 --- /dev/null +++ b/internal/app/testdata/prefs.json @@ -0,0 +1,31 @@ +{ + "allow_proxy": "false", + "attachment_workers": "16", + "autostart": "true", + "autoupdate": "true", + "cache_compression": "true", + "cache_concurrent_read": "16", + "cache_concurrent_write": "16", + "cache_enabled": "true", + "cache_location": "/home/user/.config/protonmail/bridge/cache/c11/messages", + "cache_min_free_abs": "250000000", + "cache_min_free_rat": "", + "color_scheme": "blablabla", + "cookies": "{\"https://api.protonmail.ch\":[{\"Name\":\"Session-Id\",\"Value\":\"blablablablablablablablabla\",\"Path\":\"/\",\"Domain\":\"protonmail.ch\",\"Expires\":\"2023-02-19T00:20:40.269424437+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":true,\"SameSite\":0,\"Raw\":\"Session-Id=blablablablablablablablabla; Domain=protonmail.ch; Path=/; HttpOnly; Secure; Max-Age=7776000\",\"Unparsed\":null},{\"Name\":\"Tag\",\"Value\":\"default\",\"Path\":\"/\",\"Domain\":\"\",\"Expires\":\"2023-02-19T00:20:40.269428627+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":false,\"SameSite\":0,\"Raw\":\"Tag=default; Path=/; Secure; Max-Age=7776000\",\"Unparsed\":null}],\"https://protonmail.com\":[{\"Name\":\"Session-Id\",\"Value\":\"blablablablablablablablabla\",\"Path\":\"/\",\"Domain\":\"protonmail.com\",\"Expires\":\"2023-02-19T00:20:18.315084712+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":true,\"SameSite\":0,\"Raw\":\"Session-Id=Y3q2Mh-ClvqL6LWeYdfyPgAAABI; Domain=protonmail.com; Path=/; HttpOnly; Secure; Max-Age=7776000\",\"Unparsed\":null},{\"Name\":\"Tag\",\"Value\":\"redirect\",\"Path\":\"/\",\"Domain\":\"\",\"Expires\":\"2023-02-19T00:20:18.315087646+01:00\",\"RawExpires\":\"\",\"MaxAge\":7776000,\"Secure\":true,\"HttpOnly\":false,\"SameSite\":0,\"Raw\":\"Tag=redirect; Path=/; Secure; Max-Age=7776000\",\"Unparsed\":null}]}", + "fetch_workers": "16", + "first_time_start": "false", + "first_time_start_gui": "true", + "imap_workers": "16", + "is_all_mail_visible": "false", + "last_heartbeat": "325", + "last_used_version": "2.3.0+git", + "preferred_keychain": "secret-service", + "rebranding_migrated": "true", + "report_outgoing_email_without_encryption": "false", + "rollout": "0.4849529004202015", + "user_port_api": "1042", + "update_channel": "early", + "user_port_imap": "2143", + "user_port_smtp": "2025", + "user_ssl_smtp": "true" +} \ No newline at end of file diff --git a/internal/vault/settings.go b/internal/vault/settings.go index fd61d9c3..0d8cf5da 100644 --- a/internal/vault/settings.go +++ b/internal/vault/settings.go @@ -207,12 +207,33 @@ func (vault *Vault) SyncWorkers() int { return vault.get().Settings.SyncWorkers } +// SetSyncWorkers sets the number of workers to use for syncing. +func (vault *Vault) SetSyncWorkers(workers int) error { + return vault.mod(func(data *Data) { + data.Settings.SyncWorkers = workers + }) +} + // SyncBuffer returns the number of buffer workers to use for syncing. func (vault *Vault) SyncBuffer() int { return vault.get().Settings.SyncBuffer } +// SetSyncBuffer sets the number of buffer workers to use for syncing. +func (vault *Vault) SetSyncBuffer(buffer int) error { + return vault.mod(func(data *Data) { + data.Settings.SyncBuffer = buffer + }) +} + // SyncAttPool returns the size of the attachment pool. func (vault *Vault) SyncAttPool() int { return vault.get().Settings.SyncAttPool } + +// SetSyncAttPool sets the size of the attachment pool. +func (vault *Vault) SetSyncAttPool(pool int) error { + return vault.mod(func(data *Data) { + data.Settings.SyncAttPool = pool + }) +}