GODT-1657: Stable sync (still needs more tests)

This commit is contained in:
James Houlahan
2022-10-01 23:14:42 +02:00
parent 705875cff2
commit 9d69a2e565
34 changed files with 1270 additions and 1099 deletions

View File

@ -69,6 +69,9 @@ type Bridge struct {
// errors contains errors encountered during startup.
errors []error
// stopCh is used to stop ongoing goroutines when the bridge is closed.
stopCh chan struct{}
}
// New creates a new bridge.
@ -153,6 +156,8 @@ func New(
focusService: focusService,
autostarter: autostarter,
locator: locator,
stopCh: make(chan struct{}),
}
api.AddStatusObserver(func(status liteapi.Status) {
@ -232,12 +237,8 @@ func (bridge *Bridge) GetErrors() []error {
}
func (bridge *Bridge) Close(ctx context.Context) error {
// Abort any ongoing syncs.
for _, user := range bridge.users {
if err := user.AbortSync(ctx); err != nil {
return fmt.Errorf("failed to abort sync: %w", err)
}
}
// Stop ongoing operations such as connectivity checks.
close(bridge.stopCh)
// Close the IMAP server.
if err := bridge.closeIMAP(ctx); err != nil {
@ -251,7 +252,7 @@ func (bridge *Bridge) Close(ctx context.Context) error {
// Close all users.
for _, user := range bridge.users {
if err := user.Close(ctx); err != nil {
if err := user.Close(); err != nil {
logrus.WithError(err).Error("Failed to close user")
}
}
@ -335,6 +336,9 @@ func (bridge *Bridge) onStatusDown() {
case <-upCh:
return
case <-bridge.stopCh:
return
case <-time.After(backoff):
if err := bridge.api.Ping(ctx); err != nil {
logrus.WithError(err).Debug("Failed to ping API")

View File

@ -25,20 +25,17 @@ import (
"gitlab.protontech.ch/go/liteapi/server/backend"
)
const (
username = "username"
)
var password = []byte("password")
var (
username = "username"
password = []byte("password")
v2_3_0 = semver.MustParse("2.3.0")
v2_4_0 = semver.MustParse("2.4.0")
)
func init() {
user.DefaultEventPeriod = 100 * time.Millisecond
user.DefaultEventJitter = 0
user.EventPeriod = 100 * time.Millisecond
user.EventJitter = 0
backend.GenerateKey = tests.FastGenerateKey
certs.GenerateCert = tests.FastGenerateCert
}

View File

@ -74,7 +74,7 @@ func getGluonDir(encVault *vault.Vault) (string, error) {
if empty {
if err := encVault.ForUser(func(user *vault.User) error {
return user.SetSync(false)
return user.ClearSyncStatus()
}); err != nil {
return "", fmt.Errorf("failed to reset user sync status: %w", err)
}

View File

@ -5,7 +5,6 @@ import (
"fmt"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/internal/user"
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
@ -86,6 +85,10 @@ func (bridge *Bridge) LoginUser(
return "", err
}
if _, ok := bridge.users[auth.UserID]; ok {
return "", ErrUserAlreadyLoggedIn
}
if auth.TwoFA.Enabled == liteapi.TOTPEnabled {
totp, err := getTOTP()
if err != nil {
@ -110,16 +113,26 @@ func (bridge *Bridge) LoginUser(
keyPass = password
}
apiUser, apiAddrs, userKR, addrKRs, saltedKeyPass, err := client.Unlock(ctx, keyPass)
apiUser, err := client.GetUser(ctx)
if err != nil {
return "", err
}
if err := bridge.addUser(ctx, client, apiUser, apiAddrs, userKR, addrKRs, auth.UID, auth.RefreshToken, saltedKeyPass); err != nil {
salts, err := client.GetSalts(ctx)
if err != nil {
return "", err
}
return apiUser.ID, nil
saltedKeyPass, err := salts.SaltForKey(keyPass, apiUser.Keys.Primary().ID)
if err != nil {
return "", err
}
if err := bridge.addUser(ctx, client, apiUser, auth.UID, auth.RefreshToken, saltedKeyPass); err != nil {
return "", err
}
return auth.UserID, nil
}
// LogoutUser logs out the given user.
@ -158,10 +171,6 @@ func (bridge *Bridge) SetAddressMode(ctx context.Context, userID string, mode va
return fmt.Errorf("address mode is already %q", mode)
}
if err := user.AbortSync(ctx); err != nil {
return fmt.Errorf("failed to abort sync: %w", err)
}
for _, gluonID := range user.GetGluonIDs() {
if err := bridge.imapServer.RemoveUser(ctx, gluonID, true); err != nil {
return fmt.Errorf("failed to remove user from IMAP server: %w", err)
@ -181,8 +190,6 @@ func (bridge *Bridge) SetAddressMode(ctx context.Context, userID string, mode va
AddressMode: mode,
})
user.DoSync(ctx)
return nil
}
@ -220,12 +227,12 @@ func (bridge *Bridge) loadUser(ctx context.Context, user *vault.User) error {
return fmt.Errorf("failed to create API client: %w", err)
}
apiUser, apiAddrs, userKR, addrKRs, err := client.UnlockSalted(ctx, user.KeyPass())
apiUser, err := client.GetUser(ctx)
if err != nil {
return fmt.Errorf("failed to unlock user: %w", err)
return fmt.Errorf("failed to get user: %w", err)
}
if err := bridge.addUser(ctx, client, apiUser, apiAddrs, userKR, addrKRs, auth.UID, auth.RefreshToken, user.KeyPass()); err != nil {
if err := bridge.addUser(ctx, client, apiUser, auth.UID, auth.RefreshToken, user.KeyPass()); err != nil {
return fmt.Errorf("failed to add user: %w", err)
}
@ -241,27 +248,20 @@ func (bridge *Bridge) addUser(
ctx context.Context,
client *liteapi.Client,
apiUser liteapi.User,
apiAddrs []liteapi.Address,
userKR *crypto.KeyRing,
addrKRs map[string]*crypto.KeyRing,
authUID, authRef string,
saltedKeyPass []byte,
) error {
if _, ok := bridge.users[apiUser.ID]; ok {
return ErrUserAlreadyLoggedIn
}
var user *user.User
if slices.Contains(bridge.vault.GetUserIDs(), apiUser.ID) {
existingUser, err := bridge.addExistingUser(ctx, client, apiUser, apiAddrs, userKR, addrKRs, authUID, authRef, saltedKeyPass)
existingUser, err := bridge.addExistingUser(ctx, client, apiUser, authUID, authRef, saltedKeyPass)
if err != nil {
return fmt.Errorf("failed to add existing user: %w", err)
}
user = existingUser
} else {
newUser, err := bridge.addNewUser(ctx, client, apiUser, apiAddrs, userKR, addrKRs, authUID, authRef, saltedKeyPass)
newUser, err := bridge.addNewUser(ctx, client, apiUser, authUID, authRef, saltedKeyPass)
if err != nil {
return fmt.Errorf("failed to add new user: %w", err)
}
@ -269,11 +269,16 @@ func (bridge *Bridge) addUser(
user = newUser
}
// Connects the user's address(es) to gluon.
// Connect the user's address(es) to gluon.
if err := bridge.addIMAPUser(ctx, user); err != nil {
return fmt.Errorf("failed to add IMAP user: %w", err)
}
// Connect the user's address(es) to the SMTP server.
if err := bridge.smtpBackend.addUser(user); err != nil {
return fmt.Errorf("failed to add user to SMTP backend: %w", err)
}
// Handle events coming from the user before forwarding them to the bridge.
// For example, if the user's addresses change, we need to update them in gluon.
go func() {
@ -299,11 +304,6 @@ func (bridge *Bridge) addUser(
return nil
})
// TODO: Replace this with proper sync manager.
if !user.HasSync() {
user.DoSync(ctx)
}
bridge.publish(events.UserLoggedIn{
UserID: user.ID(),
})
@ -315,9 +315,6 @@ func (bridge *Bridge) addNewUser(
ctx context.Context,
client *liteapi.Client,
apiUser liteapi.User,
apiAddrs []liteapi.Address,
userKR *crypto.KeyRing,
addrKRs map[string]*crypto.KeyRing,
authUID, authRef string,
saltedKeyPass []byte,
) (*user.User, error) {
@ -326,15 +323,11 @@ func (bridge *Bridge) addNewUser(
return nil, err
}
user, err := user.New(ctx, vaultUser, client, apiUser, apiAddrs, userKR, addrKRs)
user, err := user.New(ctx, vaultUser, client, apiUser)
if err != nil {
return nil, err
}
if err := bridge.smtpBackend.addUser(user); err != nil {
return nil, err
}
bridge.users[apiUser.ID] = user
return user, nil
@ -344,9 +337,6 @@ func (bridge *Bridge) addExistingUser(
ctx context.Context,
client *liteapi.Client,
apiUser liteapi.User,
apiAddrs []liteapi.Address,
userKR *crypto.KeyRing,
addrKRs map[string]*crypto.KeyRing,
authUID, authRef string,
saltedKeyPass []byte,
) (*user.User, error) {
@ -363,15 +353,11 @@ func (bridge *Bridge) addExistingUser(
return nil, err
}
user, err := user.New(ctx, vaultUser, client, apiUser, apiAddrs, userKR, addrKRs)
user, err := user.New(ctx, vaultUser, client, apiUser)
if err != nil {
return nil, err
}
if err := bridge.smtpBackend.addUser(user); err != nil {
return nil, err
}
bridge.users[apiUser.ID] = user
return user, nil
@ -386,11 +372,6 @@ func (bridge *Bridge) logoutUser(ctx context.Context, userID string, withAPI, wi
return ErrNoSuchUser
}
// TODO: The sync should be canceled by the sync manager.
if err := user.AbortSync(ctx); err != nil {
return fmt.Errorf("failed to abort user sync: %w", err)
}
if err := bridge.smtpBackend.removeUser(user); err != nil {
return fmt.Errorf("failed to remove SMTP user: %w", err)
}
@ -407,7 +388,7 @@ func (bridge *Bridge) logoutUser(ctx context.Context, userID string, withAPI, wi
}
}
if err := user.Close(ctx); err != nil {
if err := user.Close(); err != nil {
return fmt.Errorf("failed to close user: %w", err)
}