forked from Silverfish/proton-bridge
GODT-1522: Rebuild macOS keychain notification
This commit is contained in:
@ -601,6 +601,14 @@ Window {
|
|||||||
root.notifyHasNoKeychain()
|
root.notifyHasNoKeychain()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Rebuild keychain"
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
onClicked: {
|
||||||
|
root.notifyRebuildKeychain()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -825,6 +833,7 @@ Window {
|
|||||||
}
|
}
|
||||||
signal changeKeychainFinished()
|
signal changeKeychainFinished()
|
||||||
signal notifyHasNoKeychain()
|
signal notifyHasNoKeychain()
|
||||||
|
signal notifyRebuildKeychain()
|
||||||
|
|
||||||
signal noActiveKeyForRecipient(string email)
|
signal noActiveKeyForRecipient(string email)
|
||||||
signal showMainWindow()
|
signal showMainWindow()
|
||||||
|
|||||||
@ -115,4 +115,9 @@ Item {
|
|||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
notification: root.notifications.noKeychain
|
notification: root.notifications.noKeychain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotificationDialog {
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
notification: root.notifications.rebuildKeychain
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,7 +73,8 @@ QtObject {
|
|||||||
root.enableLocalCache,
|
root.enableLocalCache,
|
||||||
root.resetBridge,
|
root.resetBridge,
|
||||||
root.deleteAccount,
|
root.deleteAccount,
|
||||||
root.noKeychain
|
root.noKeychain,
|
||||||
|
root.rebuildKeychain
|
||||||
]
|
]
|
||||||
|
|
||||||
// Connection
|
// Connection
|
||||||
@ -901,4 +902,36 @@ QtObject {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property Notification rebuildKeychain: Notification {
|
||||||
|
title: qsTr("Your macOS keychain might be corrupted")
|
||||||
|
description: qsTr("Bridge is not able to access your macOS keychain. Please consult the instructions on our support page.")
|
||||||
|
brief: title
|
||||||
|
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||||
|
type: Notification.NotificationType.Danger
|
||||||
|
group: Notifications.Group.Dialogs | Notifications.Group.Configuration
|
||||||
|
|
||||||
|
property var supportLink: "https://protonmail.com/support/knowledge-base/macos-keychain-corrupted"
|
||||||
|
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.backend
|
||||||
|
|
||||||
|
onNotifyRebuildKeychain: {
|
||||||
|
console.log("notifications")
|
||||||
|
root.rebuildKeychain.active = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
action: [
|
||||||
|
Action {
|
||||||
|
text: qsTr("Open the support page")
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
Qt.openUrlExternally(root.rebuildKeychain.supportLink)
|
||||||
|
root.backend.quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *FrontendQt) watchEvents() {
|
func (f *FrontendQt) watchEvents() {
|
||||||
@ -64,7 +65,11 @@ func (f *FrontendQt) watchEvents() {
|
|||||||
if strings.Contains(errorDetails, "SMTP failed") {
|
if strings.Contains(errorDetails, "SMTP failed") {
|
||||||
f.qml.PortIssueSMTP()
|
f.qml.PortIssueSMTP()
|
||||||
}
|
}
|
||||||
case <-credentialsErrorCh:
|
case reason := <-credentialsErrorCh:
|
||||||
|
if reason == keychain.ErrMacKeychainRebuild.Error() {
|
||||||
|
f.qml.NotifyRebuildKeychain()
|
||||||
|
continue
|
||||||
|
}
|
||||||
f.qml.NotifyHasNoKeychain()
|
f.qml.NotifyHasNoKeychain()
|
||||||
case email := <-noActiveKeyForRecipientCh:
|
case email := <-noActiveKeyForRecipientCh:
|
||||||
f.qml.NoActiveKeyForRecipient(email)
|
f.qml.NoActiveKeyForRecipient(email)
|
||||||
|
|||||||
@ -142,6 +142,7 @@ type QMLBackend struct {
|
|||||||
_ func(keychain string) `slot:"changeKeychain"`
|
_ func(keychain string) `slot:"changeKeychain"`
|
||||||
_ func() `signal:"changeKeychainFinished"`
|
_ func() `signal:"changeKeychainFinished"`
|
||||||
_ func() `signal:"notifyHasNoKeychain"`
|
_ func() `signal:"notifyHasNoKeychain"`
|
||||||
|
_ func() `signal:"notifyRebuildKeychain"`
|
||||||
|
|
||||||
_ func(email string) `signal:noActiveKeyForRecipient`
|
_ func(email string) `signal:noActiveKeyForRecipient`
|
||||||
_ func() `signal:showMainWindow`
|
_ func() `signal:showMainWindow`
|
||||||
|
|||||||
@ -69,6 +69,7 @@ func newUser(
|
|||||||
|
|
||||||
creds, err := credStorer.Get(userID)
|
creds, err := credStorer.Get(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
notifyKeychainRepair(eventListener, err)
|
||||||
return nil, nil, errors.Wrap(err, "failed to load user credentials")
|
return nil, nil, errors.Wrap(err, "failed to load user credentials")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,6 +163,7 @@ func (u *User) handleAuthRefresh(auth *pmapi.AuthRefresh) {
|
|||||||
|
|
||||||
creds, err := u.credStorer.UpdateToken(u.userID, auth.UID, auth.RefreshToken)
|
creds, err := u.credStorer.UpdateToken(u.userID, auth.UID, auth.RefreshToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
notifyKeychainRepair(u.listener, err)
|
||||||
u.log.WithError(err).Error("Failed to update refresh token in credentials store")
|
u.log.WithError(err).Error("Failed to update refresh token in credentials store")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -408,6 +410,7 @@ func (u *User) UpdateUser(ctx context.Context) error {
|
|||||||
|
|
||||||
creds, err := u.credStorer.UpdateEmails(u.userID, u.client.Addresses().ActiveEmails())
|
creds, err := u.credStorer.UpdateEmails(u.userID, u.client.Addresses().ActiveEmails())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
notifyKeychainRepair(u.listener, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,6 +448,7 @@ func (u *User) SwitchAddressMode() error {
|
|||||||
|
|
||||||
creds, err := u.credStorer.SwitchAddressMode(u.userID)
|
creds, err := u.credStorer.SwitchAddressMode(u.userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
notifyKeychainRepair(u.listener, err)
|
||||||
return errors.Wrap(err, "could not switch credentials store address mode")
|
return errors.Wrap(err, "could not switch credentials store address mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,9 +494,11 @@ func (u *User) Logout() error {
|
|||||||
|
|
||||||
creds, err := u.credStorer.Logout(u.userID)
|
creds, err := u.credStorer.Logout(u.userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
notifyKeychainRepair(u.listener, err)
|
||||||
u.log.WithError(err).Warn("Could not log user out from credentials store")
|
u.log.WithError(err).Warn("Could not log user out from credentials store")
|
||||||
|
|
||||||
if err := u.credStorer.Delete(u.userID); err != nil {
|
if err := u.credStorer.Delete(u.userID); err != nil {
|
||||||
|
notifyKeychainRepair(u.listener, err)
|
||||||
u.log.WithError(err).Error("Could not delete user from credentials store")
|
u.log.WithError(err).Error("Could not delete user from credentials store")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
@ -130,6 +131,7 @@ func (u *Users) loadUsersFromCredentialsStore() error {
|
|||||||
|
|
||||||
userIDs, err := u.credStorer.List()
|
userIDs, err := u.credStorer.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
notifyKeychainRepair(u.events, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,6 +190,7 @@ func (u *Users) loadConnectedUser(ctx context.Context, user *User, creds *creden
|
|||||||
|
|
||||||
// Update the user's credentials with the latest auth used to connect this user.
|
// Update the user's credentials with the latest auth used to connect this user.
|
||||||
if creds, err = u.credStorer.UpdateToken(creds.UserID, auth.UID, auth.RefreshToken); err != nil {
|
if creds, err = u.credStorer.UpdateToken(creds.UserID, auth.UID, auth.RefreshToken); err != nil {
|
||||||
|
notifyKeychainRepair(u.events, err)
|
||||||
return errors.Wrap(err, "could not create get user's refresh token")
|
return errors.Wrap(err, "could not create get user's refresh token")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,12 +229,14 @@ func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password []by
|
|||||||
|
|
||||||
// Update the user's credentials with the latest auth used to connect this user.
|
// Update the user's credentials with the latest auth used to connect this user.
|
||||||
if _, err := u.credStorer.UpdateToken(auth.UserID, auth.UID, auth.RefreshToken); err != nil {
|
if _, err := u.credStorer.UpdateToken(auth.UserID, auth.UID, auth.RefreshToken); err != nil {
|
||||||
|
notifyKeychainRepair(u.events, err)
|
||||||
return nil, errors.Wrap(err, "failed to load user credentials")
|
return nil, errors.Wrap(err, "failed to load user credentials")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the password in case the user changed it.
|
// Update the password in case the user changed it.
|
||||||
creds, err := u.credStorer.UpdatePassword(apiUser.ID, passphrase)
|
creds, err := u.credStorer.UpdatePassword(apiUser.ID, passphrase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
notifyKeychainRepair(u.events, err)
|
||||||
return nil, errors.Wrap(err, "failed to update password of user in credentials store")
|
return nil, errors.Wrap(err, "failed to update password of user in credentials store")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,6 +265,7 @@ func (u *Users) addNewUser(client pmapi.Client, apiUser *pmapi.User, auth *pmapi
|
|||||||
defer u.lock.Unlock()
|
defer u.lock.Unlock()
|
||||||
|
|
||||||
if _, err := u.credStorer.Add(apiUser.ID, apiUser.Name, auth.UID, auth.RefreshToken, passphrase, client.Addresses().ActiveEmails()); err != nil {
|
if _, err := u.credStorer.Add(apiUser.ID, apiUser.Name, auth.UID, auth.RefreshToken, passphrase, client.Addresses().ActiveEmails()); err != nil {
|
||||||
|
notifyKeychainRepair(u.events, err)
|
||||||
return errors.Wrap(err, "failed to add user credentials to credentials store")
|
return errors.Wrap(err, "failed to add user credentials to credentials store")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -384,6 +390,7 @@ func (u *Users) DeleteUser(userID string, clearStore bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := u.credStorer.Delete(userID); err != nil {
|
if err := u.credStorer.Delete(userID); err != nil {
|
||||||
|
notifyKeychainRepair(u.events, err)
|
||||||
log.WithError(err).Error("Cannot remove user")
|
log.WithError(err).Error("Cannot remove user")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -443,3 +450,9 @@ func (u *Users) crashBandicoot(username string) {
|
|||||||
panic("Your wish is my command… I crash!")
|
panic("Your wish is my command… I crash!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func notifyKeychainRepair(l listener.Listener, err error) {
|
||||||
|
if err == keychain.ErrMacKeychainRebuild {
|
||||||
|
l.Emit(events.CredentialsErrorEvent, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -40,6 +40,16 @@ func init() { // nolint[noinit]
|
|||||||
defaultHelper = MacOSKeychain
|
defaultHelper = MacOSKeychain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseError(original error) error {
|
||||||
|
if original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.Contains(original.Error(), "25293") {
|
||||||
|
return ErrMacKeychainRebuild
|
||||||
|
}
|
||||||
|
return original
|
||||||
|
}
|
||||||
|
|
||||||
func newMacOSHelper(url string) (credentials.Helper, error) {
|
func newMacOSHelper(url string) (credentials.Helper, error) {
|
||||||
return &macOSHelper{url: url}, nil
|
return &macOSHelper{url: url}, nil
|
||||||
}
|
}
|
||||||
@ -76,7 +86,7 @@ func (h *macOSHelper) Add(creds *credentials.Credentials) error {
|
|||||||
|
|
||||||
query := newQuery(hostURL, userID)
|
query := newQuery(hostURL, userID)
|
||||||
query.SetData([]byte(creds.Secret))
|
query.SetData([]byte(creds.Secret))
|
||||||
return keychain.AddItem(query)
|
return parseError(keychain.AddItem(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *macOSHelper) Delete(secretURL string) error {
|
func (h *macOSHelper) Delete(secretURL string) error {
|
||||||
@ -87,7 +97,7 @@ func (h *macOSHelper) Delete(secretURL string) error {
|
|||||||
|
|
||||||
query := newQuery(hostURL, userID)
|
query := newQuery(hostURL, userID)
|
||||||
|
|
||||||
return keychain.DeleteItem(query)
|
return parseError(keychain.DeleteItem(query))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *macOSHelper) Get(secretURL string) (string, string, error) {
|
func (h *macOSHelper) Get(secretURL string) (string, string, error) {
|
||||||
@ -102,7 +112,7 @@ func (h *macOSHelper) Get(secretURL string) (string, string, error) {
|
|||||||
|
|
||||||
results, err := keychain.QueryItem(query)
|
results, err := keychain.QueryItem(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", parseError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(results) == 0 {
|
if len(results) == 0 {
|
||||||
@ -121,7 +131,7 @@ func (h *macOSHelper) List() (map[string]string, error) {
|
|||||||
|
|
||||||
userIDs, err := keychain.GetGenericPasswordAccounts(h.url)
|
userIDs, err := keychain.GetGenericPasswordAccounts(h.url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, parseError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, userID := range userIDs {
|
for _, userID := range userIDs {
|
||||||
|
|||||||
@ -37,6 +37,9 @@ var (
|
|||||||
// ErrNoKeychain indicates that no suitable keychain implementation could be loaded.
|
// ErrNoKeychain indicates that no suitable keychain implementation could be loaded.
|
||||||
ErrNoKeychain = errors.New("no keychain") // nolint[noglobals]
|
ErrNoKeychain = errors.New("no keychain") // nolint[noglobals]
|
||||||
|
|
||||||
|
// ErrMacKeychainRebuild is returned on macOS with blocked or corrupted keychain.
|
||||||
|
ErrMacKeychainRebuild = errors.New("keychain error -25293")
|
||||||
|
|
||||||
// Helpers holds all discovered keychain helpers. It is populated in init().
|
// Helpers holds all discovered keychain helpers. It is populated in init().
|
||||||
Helpers map[string]helperConstructor // nolint[noglobals]
|
Helpers map[string]helperConstructor // nolint[noglobals]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user