diff --git a/internal/app/base/base.go b/internal/app/base/base.go index ac4adc93b..6f90834c9 100644 --- a/internal/app/base/base.go +++ b/internal/app/base/base.go @@ -159,12 +159,8 @@ func New( //nolint:funlen return nil, api.CheckOtherInstanceAndFocus(settingsObj.GetInt(settings.APIPortKey)) } - if err := migrateMacKeychainBefore220(settingsObj, keychainName); err != nil { - logrus.WithError(err).Warn("Keychain migration failed") - } - - if err := migrateStartup220(settingsObj); err != nil { - logrus.WithError(err).Warn("Failed to remove old startup paths") + if err := migrateRebranding(settingsObj, keychainName); err != nil { + logrus.WithError(err).Warn("Rebranding migration failed") } cachePath, err := locations.ProvideCachePath() @@ -244,7 +240,7 @@ func New( //nolint:funlen } autostart := &autostart.App{ - Name: appName, + Name: startupNameForRebranding(appName), DisplayName: appName, Exec: []string{exe, "--" + FlagNoWindow}, } diff --git a/internal/app/base/migration.go b/internal/app/base/migration.go index 8ca413ae3..603a34be9 100644 --- a/internal/app/base/migration.go +++ b/internal/app/base/migration.go @@ -18,16 +18,11 @@ package base import ( - "errors" "os" "path/filepath" - "runtime" - "github.com/Masterminds/semver/v3" - "github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/internal/constants" "github.com/ProtonMail/proton-bridge/internal/locations" - "github.com/ProtonMail/proton-bridge/pkg/keychain" "github.com/sirupsen/logrus" ) @@ -112,122 +107,6 @@ func migrateUpdatesFrom16x(configName string, locations *locations.Locations) er return moveIfExists(oldUpdatesPath, newUpdatesPath) } -// migrateMacKeychainBefore220 deals with write access restriction to mac -// keychain passwords which are caused by application renaming. The old -// passwords are copied under new name in order to have write access afer -// renaming. -func migrateMacKeychainBefore220(settingsObj *settings.Settings, keychainName string) error { - l := logrus.WithField("pkg", "app/base/migration") - if runtime.GOOS != "darwin" { - return nil - } - - if shouldContinue, err := isBefore220(settingsObj); !shouldContinue || err != nil { - return err - } - - l.Warn("Migrating mac keychain") - - helperConstructor, ok := keychain.Helpers["macos-keychain"] - if !ok { - return errors.New("cannot find macos-keychain helper") - } - - oldKC, err := helperConstructor("ProtonMailBridgeService") - if err != nil { - l.WithError(err).Error("Keychain constructor failed") - return err - } - - idByURL, err := oldKC.List() - if err != nil { - l.WithError(err).Error("List old keychain failed") - return err - } - - newKC, err := keychain.NewKeychain(settingsObj, keychainName) - if err != nil { - return err - } - - for url, id := range idByURL { - li := l.WithField("id", id).WithField("url", url) - userID, secret, err := oldKC.Get(url) - if err != nil { - li.WithField("userID", userID). - WithField("err", err). - Error("Faild to get old item") - continue - } - - if _, _, err := newKC.Get(userID); err == nil { - li.Warn("Skipping migration, item already exists.") - continue - } - - if err := newKC.Put(userID, secret); err != nil { - li.WithError(err).Error("Failed to migrate user") - } - - li.Info("Item migrated") - } - - return nil -} - -// migrateStartup220 removes old startup links. The creation of new links is -// handled by bridge initialisation. -func migrateStartup220(settingsObj *settings.Settings) error { - if shouldContinue, err := isBefore220(settingsObj); !shouldContinue || err != nil { - return err - } - - logrus.WithField("pkg", "app/base/migration").Warn("Migrating autostartup links") - - path, err := os.UserHomeDir() - if err != nil { - return err - } - - switch runtime.GOOS { - case "windows": - path = filepath.Join(path, `AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\ProtonMail Bridge.lnk`) - case "linux": - path = filepath.Join(path, `.config/autostart/ProtonMail Bridge.desktop`) - case "darwin": - path = filepath.Join(path, `Library/LaunchAgents/ProtonMail Bridge.plist`) - default: - return errors.New("unknown GOOS") - } - - return os.Remove(path) -} - -// isBefore220 decide if last used version was older than 2.2.0. If cannot decide it returns false with error. -func isBefore220(settingsObj *settings.Settings) (bool, error) { - lastUsedVersion := settingsObj.Get(settings.LastVersionKey) - - // Skipping migration: it is first bridge start or cache was cleared. - if lastUsedVersion == "" { - return false, nil - } - - v220 := semver.MustParse("2.2.0") - lastVer, err := semver.NewVersion(lastUsedVersion) - - // Skipping migration: Should not happen but cannot decide what to do. - if err != nil { - return false, err - } - - // Skipping migration: 2.2.0>= was already used hence old stuff was already migrated. - if !lastVer.LessThan(v220) { - return false, nil - } - - return true, nil -} - func moveIfExists(source, destination string) error { l := logrus.WithField("source", source).WithField("destination", destination) diff --git a/internal/app/base/migration_rebranding.go b/internal/app/base/migration_rebranding.go new file mode 100644 index 000000000..8bc13ff89 --- /dev/null +++ b/internal/app/base/migration_rebranding.go @@ -0,0 +1,197 @@ +// 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 base + +import ( + "errors" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/ProtonMail/proton-bridge/internal/config/settings" + "github.com/ProtonMail/proton-bridge/pkg/keychain" + "github.com/hashicorp/go-multierror" + "github.com/sirupsen/logrus" +) + +const darwin = "darwin" + +func migrateRebranding(settingsObj *settings.Settings, keychainName string) (result error) { + if err := migrateStartupBeforeRebranding(); err != nil { + result = multierror.Append(result, err) + } + + lastUsedVersion := settingsObj.Get(settings.LastVersionKey) + + // Skipping migration: it is first bridge start or cache was cleared. + if lastUsedVersion == "" { + settingsObj.SetBool(settings.RebrandingMigrationKey, true) + return + } + + // Skipping rest of migration: already done + if settingsObj.GetBool(settings.RebrandingMigrationKey) { + return + } + + switch runtime.GOOS { + case "windows", "linux": + // GODT-1260 we would need admin rights to changes desktop files + // and start menu items. + settingsObj.SetBool(settings.RebrandingMigrationKey, true) + case darwin: + if shouldContinue, err := isMacBeforeRebranding(); !shouldContinue || err != nil { + if err != nil { + result = multierror.Append(result, err) + } + break + } + + if err := migrateMacKeychainBeforeRebranding(settingsObj, keychainName); err != nil { + result = multierror.Append(result, err) + } + + settingsObj.SetBool(settings.RebrandingMigrationKey, true) + } + + return result +} + +// migrateMacKeychainBeforeRebranding deals with write access restriction to +// mac keychain passwords which are caused by application renaming. The old +// passwords are copied under new name in order to have write access afer +// renaming. +func migrateMacKeychainBeforeRebranding(settingsObj *settings.Settings, keychainName string) error { + l := logrus.WithField("pkg", "app/base/migration") + l.Warn("Migrating mac keychain") + + helperConstructor, ok := keychain.Helpers["macos-keychain"] + if !ok { + return errors.New("cannot find macos-keychain helper") + } + + oldKC, err := helperConstructor("ProtonMailBridgeService") + if err != nil { + l.WithError(err).Error("Keychain constructor failed") + return err + } + + idByURL, err := oldKC.List() + if err != nil { + l.WithError(err).Error("List old keychain failed") + return err + } + + newKC, err := keychain.NewKeychain(settingsObj, keychainName) + if err != nil { + return err + } + + for url, id := range idByURL { + li := l.WithField("id", id).WithField("url", url) + userID, secret, err := oldKC.Get(url) + if err != nil { + li.WithField("userID", userID). + WithField("err", err). + Error("Faild to get old item") + continue + } + + if _, _, err := newKC.Get(userID); err == nil { + li.Warn("Skipping migration, item already exists.") + continue + } + + if err := newKC.Put(userID, secret); err != nil { + li.WithError(err).Error("Failed to migrate user") + } + + li.Info("Item migrated") + } + + return nil +} + +// migrateStartupBeforeRebranding removes old startup links. The creation of new links is +// handled by bridge initialisation. +func migrateStartupBeforeRebranding() error { + path, err := os.UserHomeDir() + if err != nil { + return err + } + + switch runtime.GOOS { + case "windows": + path = filepath.Join(path, `AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\ProtonMail Bridge.lnk`) + case "linux": + path = filepath.Join(path, `.config/autostart/ProtonMail Bridge.desktop`) + case darwin: + path = filepath.Join(path, `Library/LaunchAgents/ProtonMail Bridge.plist`) + default: + return errors.New("unknown GOOS") + } + + if _, err := os.Stat(path); os.IsNotExist(err) { + return nil + } + + logrus.WithField("pkg", "app/base/migration").Warn("Migrating autostartup links") + return os.Remove(path) +} + +// startupNameForRebranding returns the name for autostart launcher based on +// type of rebranded instance i.e. update or manual. +// +// This only affects darwin when udpate re-writes the old startup and then +// manual installed it would not run proper exe. Therefore we return "old" name +// for updates and "new" name for manual which would be properly migrated. +// +// For orther (linux and windows) the link is always pointing to launcher which +// path didn't changed. +func startupNameForRebranding(origin string) string { + if runtime.GOOS == darwin { + if path, err := os.Executable(); err == nil && strings.Contains(path, "ProtonMail Bridge") { + return "ProtonMail Bridge" + } + } + + // No need to solve for other OS. See comment above. + return origin +} + +// isBeforeRebranding decide if last used version was older than 2.2.0. If +// cannot decide it returns false with error. +func isMacBeforeRebranding() (bool, error) { + // previous version | update | do mac migration | + // | first | false | + // cleared-cache | manual | false | + // cleared-cache | in-app | false | + // old | in-app | false | + // old in-app | in-app | false | + // old | manual | true | + // old in-app | manual | true | + // manual | in-app | false | + + // Skip if it was in-app update and not manual + if path, err := os.Executable(); err != nil || strings.Contains(path, "ProtonMail Bridge") { + return false, err + } + + return true, nil +} diff --git a/internal/bridge/autostart.go b/internal/bridge/autostart.go index f8112b94a..78224223c 100644 --- a/internal/bridge/autostart.go +++ b/internal/bridge/autostart.go @@ -18,14 +18,21 @@ // Package bridge provides core functionality of Bridge app. package bridge +import "github.com/ProtonMail/proton-bridge/internal/config/settings" + +// IsAutostartEnabled checks if link file exits. func (b *Bridge) IsAutostartEnabled() bool { return b.autostart.IsEnabled() } +// EnableAutostart creates link and sets the preferences. func (b *Bridge) EnableAutostart() error { + b.settings.SetBool(settings.AutostartKey, true) return b.autostart.Enable() } +// DisableAutostart removes link and sets the preferences. func (b *Bridge) DisableAutostart() error { + b.settings.SetBool(settings.AutostartKey, false) return b.autostart.Disable() } diff --git a/internal/config/settings/settings.go b/internal/config/settings/settings.go index 9133424d5..00e1aabb4 100644 --- a/internal/config/settings/settings.go +++ b/internal/config/settings/settings.go @@ -54,6 +54,7 @@ const ( FetchWorkers = "fetch_workers" AttachmentWorkers = "attachment_workers" ColorScheme = "color_scheme" + RebrandingMigrationKey = "rebranding_migrated" ) type Settings struct { diff --git a/internal/frontend/qml/MainWindow.qml b/internal/frontend/qml/MainWindow.qml index 8dfa488f1..761a42f56 100644 --- a/internal/frontend/qml/MainWindow.qml +++ b/internal/frontend/qml/MainWindow.qml @@ -97,10 +97,6 @@ ApplicationWindow { property bool _showSetup: false currentIndex: { - if (backend.showSplashScreen) { - return 3 - } - // show welcome when there are no users or only one non-logged-in user is present if (backend.users.count === 0) { return 1 @@ -167,14 +163,6 @@ ApplicationWindow { } } - SplashScreen { // 3 - id: splashScreen - colorScheme: root.colorScheme - backend: root.backend - - Layout.fillHeight: true - Layout.fillWidth: true - } } NotificationPopups { @@ -184,6 +172,12 @@ ApplicationWindow { backend: root.backend } + SplashScreen { + id: splashScreen + colorScheme: root.colorScheme + backend: root.backend + } + function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings() } function showSettings() { contentWrapper.showSettings() } function showHelp() { contentWrapper.showHelp() } diff --git a/internal/frontend/qml/SplashScreen.qml b/internal/frontend/qml/SplashScreen.qml index 234885885..22f938226 100644 --- a/internal/frontend/qml/SplashScreen.qml +++ b/internal/frontend/qml/SplashScreen.qml @@ -23,126 +23,86 @@ import QtQuick.Controls.impl 2.12 import Proton 4.0 -Rectangle { +Dialog { id: root - property ColorScheme colorScheme property var backend - color: root.colorScheme.background_norm + shouldShow: root.backend.showSplashScreen + modal: true + + topPadding : 0 + leftPadding : 0 + rightPadding : 0 ColumnLayout { - anchors.centerIn: root + spacing: 20 - //width: 320 + Image { + Layout.alignment: Qt.AlignHCenter - spacing: 20 + sourceSize.width: 400 + sourceSize.height: 225 - Label { - Layout.bottomMargin: 12; - Layout.alignment: Qt.AlignHCenter; + Layout.preferredWidth: 400 + Layout.preferredHeight: 225 - colorScheme: root.colorScheme; - text: "What's new in Bridge" - type: Label.Heading - horizontalAlignment: Text.AlignCenter + source: "./icons/img-splash.png" } - Repeater { - model: ListModel { - ListElement { icon: "ic-illustrative-view-html-code" ; title: qsTr("New interface") ; description: qsTr("Entirely redesigned GUI with more intuitive setup.")} - ListElement { icon: "ic-card-identity" ; title: qsTr("Status view") ; description: qsTr("Important notifications and available storage at a glance.")} - ListElement { icon: "ic-drive" ; title: qsTr("Local cache") ; description: qsTr("New and improved cache for major stability and performance enhancements.")} - } - - Item { - implicitWidth: children[0].implicitWidth - implicitHeight: children[0].implicitHeight - - RowLayout { - id: row - spacing: 25 - - Item { - Layout.topMargin: itemTitle.height/2 - Layout.alignment: Qt.AlignTop - Layout.preferredWidth: 24 - Layout.preferredHeight: 24 - - ColorImage { - anchors.top: parent.top - anchors.left: parent.left - - color: root.colorScheme.interaction_norm - source: "./icons/"+model.icon+".svg" - width: parent.width - sourceSize.width: parent.width - } - } - - ColumnLayout { - spacing: 0 - - Label { - id: itemTitle - colorScheme: root.colorScheme - text: model.title - type: Label.Body_bold - } - - Label { - Layout.preferredWidth: 320 - colorScheme: root.colorScheme - text: model.description - wrapMode: Text.WordWrap - } - } - } - } + Label { + colorScheme: root.colorScheme; + + Layout.alignment: Qt.AlignHCenter; + Layout.leftMargin: 24 + Layout.rightMargin: 24 + Layout.preferredWidth: 336 + type: Label.Title + horizontalAlignment: Text.AlignHCenter + text: qsTr("Updated Proton, unified protection") } - Item { + Label { + colorScheme: root.colorScheme + + Layout.fillWidth: true Layout.alignment: Qt.AlignHCenter; - implicitWidth: children[0].width - implicitHeight: children[0].height - - RowLayout { - spacing: 10 - - Label { - colorScheme: root.colorScheme; - text: qsTr("Full release notes") - Layout.alignment: Qt.AlignHCenter - type: Label.LabelType.Body - onLinkActivated: Qt.openUrlExternally(link) - color: root.colorScheme.interaction_norm - } - - ColorImage { - color: root.colorScheme.interaction_norm - source: "./icons/ic-external-link.svg" - width: 16 - sourceSize.width: 16 - } - - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: Qt.openUrlExternally(root.backend.releaseNotesLink) - } + Layout.preferredWidth: 336 + Layout.leftMargin: 24 + Layout.rightMargin: 24 + wrapMode: Text.WordWrap + + type: Label.Body + horizontalAlignment: Text.AlignHCenter + textFormat: Text.StyledText + text: qsTr("Introducing Proton’s refreshed look.
") + + qsTr("Many services, one mission. Welcome to an Internet where privacy is the default. ") + + link("https://proton.me/news/updated-proton",qsTr("Learn More")) + + onLinkActivated: Qt.openUrlExternally(link) } - Button { - Layout.topMargin: 12 Layout.fillWidth: true + Layout.leftMargin: 24 + Layout.rightMargin: 24 colorScheme: root.colorScheme - text: "Start using Bridge" + text: "Got it" onClicked: root.backend.showSplashScreen = false } + + Image { + Layout.alignment: Qt.AlignHCenter + + sourceSize.width: 164 + sourceSize.height: 32 + + Layout.preferredWidth: 164 + Layout.preferredHeight: 32 + + source: "./icons/img-proton-logos.svg" + } } } diff --git a/internal/frontend/qml/icons/img-proton-logos.png b/internal/frontend/qml/icons/img-proton-logos.png new file mode 100644 index 000000000..cf54402e1 Binary files /dev/null and b/internal/frontend/qml/icons/img-proton-logos.png differ diff --git a/internal/frontend/qml/icons/img-proton-logos.svg b/internal/frontend/qml/icons/img-proton-logos.svg new file mode 100644 index 000000000..3ef4d8b27 --- /dev/null +++ b/internal/frontend/qml/icons/img-proton-logos.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/frontend/qml/icons/img-splash.png b/internal/frontend/qml/icons/img-splash.png new file mode 100644 index 000000000..9bc375ada Binary files /dev/null and b/internal/frontend/qml/icons/img-splash.png differ diff --git a/internal/frontend/qml/icons/img-splash.svg b/internal/frontend/qml/icons/img-splash.svg new file mode 100644 index 000000000..238beccc3 --- /dev/null +++ b/internal/frontend/qml/icons/img-splash.svg @@ -0,0 +1,2884 @@ + + + + + + + + + + + + + + + + + diff --git a/internal/frontend/qt/frontend_init.go b/internal/frontend/qt/frontend_init.go index 78bcbfa73..49901c2e6 100644 --- a/internal/frontend/qt/frontend_init.go +++ b/internal/frontend/qt/frontend_init.go @@ -112,10 +112,9 @@ func (f *FrontendQt) setShowSplashScreen() { return } - // Current splash screen contains update on "What's new" in facelift. - // Therefore, it should be shown only if the last used version was less - // than 1.9.0. - if ver.LessThan(semver.MustParse("1.9.0")) { + // Current splash screen contains update on rebranding. Therefore, it + // should be shown only if the last used version was less than 2.2.0. + if ver.LessThan(semver.MustParse("2.2.0")) { f.qml.SetShowSplashScreen(true) } } diff --git a/internal/frontend/qt/frontend_settings.go b/internal/frontend/qt/frontend_settings.go index 38933416b..0669ee8fe 100644 --- a/internal/frontend/qt/frontend_settings.go +++ b/internal/frontend/qt/frontend_settings.go @@ -77,13 +77,15 @@ func (f *FrontendQt) changeLocalCache(enableDiskCache bool, diskCachePath *core. func (f *FrontendQt) setIsAutostartOn() { // GODT-1507 Windows: autostart needs to be created after Qt is initialized. + // GODT-1206: if preferences file says it should be on enable it here. f.firstTimeAutostart.Do(func() { - if !f.bridge.IsFirstStart() { + shouldAutostartBeOn := f.settings.GetBool(settings.AutostartKey) + if f.bridge.IsFirstStart() || shouldAutostartBeOn { + if err := f.bridge.EnableAutostart(); err != nil { + f.log.WithField("prefs", shouldAutostartBeOn).WithError(err).Error("Failed to enable first autostart") + } return } - if err := f.bridge.EnableAutostart(); err != nil { - f.log.WithError(err).Error("Failed to enable autostart") - } }) f.qml.SetIsAutostartOn(f.bridge.IsAutostartEnabled()) } diff --git a/pkg/keychain/keychain_darwin.go b/pkg/keychain/keychain_darwin.go index 2a37872d3..b9a49328b 100644 --- a/pkg/keychain/keychain_darwin.go +++ b/pkg/keychain/keychain_darwin.go @@ -15,16 +15,22 @@ // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . +//go:build darwin // +build darwin package keychain import ( "fmt" + "os" "strings" ) // hostURL uniquely identifies the app's keychain items within the system keychain. func hostURL(keychainName string) string { + // Skip when it was in-app update and not manual + if path, err := os.Executable(); err == nil && strings.Contains(path, "ProtonMail Bridge") { + return fmt.Sprintf("ProtonMail%vService", strings.Title(keychainName)) + } return fmt.Sprintf("Proton Mail %v", strings.Title(keychainName)) } diff --git a/utils/dependency_license.sh b/utils/dependency_license.sh index 1f2180352..9cc3b26fd 100755 --- a/utils/dependency_license.sh +++ b/utils/dependency_license.sh @@ -1,21 +1,21 @@ #!/bin/bash -# Copyright (c) 2022 Proton Technologies AG +# Copyright (c) 2022 Proton AG # -# This file is part of ProtonMail Bridge. +# This file is part of Proton Mail Bridge. # -# ProtonMail Bridge is free software: you can redistribute it and/or modify +# 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. # -# ProtonMail Bridge is distributed in the hope that it will be useful, +# 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 ProtonMail Bridge. If not, see . +# along with Proton Mail Bridge. If not, see . set -eo pipefail