feat(GODT-2610): re-use previous password when removing and adding back account.

This commit is contained in:
Xavier Michelon
2023-05-04 19:57:17 +02:00
parent 01aa19edff
commit eee2c73a61
11 changed files with 161 additions and 6 deletions

View File

@ -0,0 +1,46 @@
// 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 vault
import (
"crypto/sha256"
"fmt"
)
// set archives the password for an email address, overwriting any existing archived value.
func (p *PasswordArchive) set(emailAddress string, password []byte) {
if p.Archive == nil {
p.Archive = make(map[string][]byte)
}
p.Archive[emailHashString(emailAddress)] = password
}
// get retrieves the archived password for an email address, or nil if not found.
func (p *PasswordArchive) get(emailAddress string) []byte {
if p.Archive == nil {
return nil
}
return p.Archive[emailHashString(emailAddress)]
}
// emailHashString returns a hash string for an email address as a hexadecimal string.
func emailHashString(emailAddress string) string {
return fmt.Sprintf("%x", sha256.Sum256([]byte(emailAddress)))
}

View File

@ -238,3 +238,30 @@ func TestVault_Settings_LastUserAgent(t *testing.T) {
// Check the default first start value.
require.Equal(t, vault.DefaultUserAgent, s.GetLastUserAgent())
}
func Test_Settings_PasswordArchive(t *testing.T) {
// Create a new test vault.
s := newVault(t)
// The store should have no users.
require.Empty(t, s.GetUserIDs())
// Create a new user.
user, err := s.AddUser("userID1", "username1", "username1@pm.me", "authUID1", "authRef1", []byte("keyPass1"))
require.NoError(t, err)
bridgePass := user.BridgePass()
// Remove the user.
require.NoError(t, user.Close())
require.NoError(t, s.DeleteUser("userID1"))
// Add a different user. Another password is generated.
user, err = s.AddUser("userID2", "username2", "username2@pm.me", "authUID2", "authRef2", []byte("keyPass2"))
require.NoError(t, err)
require.NotEqual(t, user.BridgePass(), bridgePass)
// Add the first user again. The password is restored.
user, err = s.AddUser("userID1", "username1", "username1@pm.me", "authUID1", "authRef1", []byte("keyPass1"))
require.NoError(t, err)
require.Equal(t, user.BridgePass(), bridgePass)
}

View File

@ -0,0 +1,25 @@
// 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 vault
// PasswordArchive maps a list email address hashes to passwords.
// The type is not defined as a map alias to prevent having to handle nil default values when vault was created by an older version of the application.
type PasswordArchive struct {
// we store the SHA-256 sum as string for readability and JSON marshalling of map[[32]byte][]byte will not be allowed, thus breaking vault-editor.
Archive map[string][]byte
}

View File

@ -53,6 +53,8 @@ type Settings struct {
LastHeartbeatSent time.Time
PasswordArchive PasswordArchive
// **WARNING**: These entry can't be removed until they vault has proper migration support.
SyncWorkers int
SyncAttPool int
@ -105,5 +107,7 @@ func newDefaultSettings(gluonDir string) Settings {
LastUserAgent: DefaultUserAgent,
LastHeartbeatSent: time.Time{},
PasswordArchive: PasswordArchive{},
}
}

View File

@ -73,7 +73,7 @@ func (status SyncStatus) IsComplete() bool {
return status.HasLabels && status.HasMessages
}
func newDefaultUser(userID, username, primaryEmail, authUID, authRef string, keyPass []byte) UserData {
func newDefaultUser(userID, username, primaryEmail, authUID, authRef string, keyPass, bridgePass []byte) UserData {
return UserData{
UserID: userID,
Username: username,
@ -82,7 +82,7 @@ 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),
BridgePass: bridgePass,
AddressMode: CombinedMode,
AuthUID: authUID,

View File

@ -133,7 +133,8 @@ func (vault *Vault) ForUser(parallelism int, fn func(*User) error) error {
}
// AddUser creates a new user in the vault with the given ID, username and password.
// A bridge password and gluon key are generated using the package's token generator.
// A gluon key is generated using the package's token generator. If a password is found in the password archive for this user,
// it is restored, otherwise a new bridge password is generated using the package's token generator.
func (vault *Vault) AddUser(userID, username, primaryEmail, authUID, authRef string, keyPass []byte) (*User, error) {
logrus.WithField("userID", userID).Info("Adding vault user")
@ -145,7 +146,12 @@ func (vault *Vault) AddUser(userID, username, primaryEmail, authUID, authRef str
}); idx >= 0 {
exists = true
} else {
data.Users = append(data.Users, newDefaultUser(userID, username, primaryEmail, authUID, authRef, keyPass))
bridgePass := data.Settings.PasswordArchive.get(primaryEmail)
if len(bridgePass) == 0 {
bridgePass = newRandomToken(16)
}
data.Users = append(data.Users, newDefaultUser(userID, username, primaryEmail, authUID, authRef, keyPass, bridgePass))
}
}); err != nil {
return nil, err
@ -177,7 +183,7 @@ func (vault *Vault) DeleteUser(userID string) error {
if idx < 0 {
return
}
data.Settings.PasswordArchive.set(data.Users[idx].PrimaryEmail, data.Users[idx].BridgePass)
data.Users = append(data.Users[:idx], data.Users[idx+1:]...)
})
}