mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-15 14:56:42 +00:00
GODT-1657: Stable sync (still needs more tests)
This commit is contained in:
@ -1,8 +1,6 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
)
|
||||
|
||||
@ -18,12 +16,3 @@ func newRandomToken(size int) []byte {
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
func newRandomString(size int) []byte {
|
||||
token, err := RandomToken(size)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return []byte(hex.EncodeToString(token))
|
||||
}
|
||||
|
||||
@ -46,33 +46,6 @@ type Settings struct {
|
||||
FirstStartGUI bool
|
||||
}
|
||||
|
||||
type AddressMode int
|
||||
|
||||
const (
|
||||
CombinedMode AddressMode = iota
|
||||
SplitMode
|
||||
)
|
||||
|
||||
// UserData holds information about a single bridge user.
|
||||
// The user may or may not be logged in.
|
||||
type UserData struct {
|
||||
UserID string
|
||||
Username string
|
||||
|
||||
GluonKey []byte
|
||||
GluonIDs map[string]string
|
||||
UIDValidity map[string]imap.UID
|
||||
BridgePass []byte
|
||||
AddressMode AddressMode
|
||||
|
||||
AuthUID string
|
||||
AuthRef string
|
||||
KeyPass []byte
|
||||
|
||||
EventID string
|
||||
HasSync bool
|
||||
}
|
||||
|
||||
func newDefaultSettings(gluonDir string) Settings {
|
||||
return Settings{
|
||||
GluonDir: gluonDir,
|
||||
@ -96,3 +69,53 @@ func newDefaultSettings(gluonDir string) Settings {
|
||||
FirstStartGUI: true,
|
||||
}
|
||||
}
|
||||
|
||||
// UserData holds information about a single bridge user.
|
||||
// The user may or may not be logged in.
|
||||
type UserData struct {
|
||||
UserID string
|
||||
Username string
|
||||
|
||||
GluonKey []byte
|
||||
GluonIDs map[string]string
|
||||
UIDValidity map[string]imap.UID
|
||||
BridgePass []byte
|
||||
AddressMode AddressMode
|
||||
|
||||
AuthUID string
|
||||
AuthRef string
|
||||
KeyPass []byte
|
||||
|
||||
SyncStatus SyncStatus
|
||||
EventID string
|
||||
}
|
||||
|
||||
type AddressMode int
|
||||
|
||||
const (
|
||||
CombinedMode AddressMode = iota
|
||||
SplitMode
|
||||
)
|
||||
|
||||
type SyncStatus struct {
|
||||
HasLabels bool
|
||||
HasMessages bool
|
||||
LastMessageID string
|
||||
}
|
||||
|
||||
func newDefaultUser(userID, username, authUID, authRef string, keyPass []byte) UserData {
|
||||
return UserData{
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
|
||||
GluonKey: newRandomToken(32),
|
||||
GluonIDs: make(map[string]string),
|
||||
UIDValidity: make(map[string]imap.UID),
|
||||
BridgePass: newRandomToken(16),
|
||||
AddressMode: CombinedMode,
|
||||
|
||||
AuthUID: authUID,
|
||||
AuthRef: authRef,
|
||||
KeyPass: keyPass,
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,11 @@ func (user *User) Username() string {
|
||||
return user.vault.getUser(user.userID).Username
|
||||
}
|
||||
|
||||
// GluonKey returns the key needed to decrypt the user's gluon database.
|
||||
func (user *User) GluonKey() []byte {
|
||||
return user.vault.getUser(user.userID).GluonKey
|
||||
}
|
||||
|
||||
func (user *User) GetGluonIDs() map[string]string {
|
||||
return user.vault.getUser(user.userID).GluonIDs
|
||||
}
|
||||
@ -42,44 +47,33 @@ func (user *User) SetUIDValidity(addrID string, validity imap.UID) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (user *User) GluonKey() []byte {
|
||||
return user.vault.getUser(user.userID).GluonKey
|
||||
}
|
||||
|
||||
// AddressMode returns the user's address mode.
|
||||
func (user *User) AddressMode() AddressMode {
|
||||
return user.vault.getUser(user.userID).AddressMode
|
||||
}
|
||||
|
||||
// SetAddressMode sets the address mode for the given user.
|
||||
func (user *User) SetAddressMode(mode AddressMode) error {
|
||||
return user.vault.modUser(user.userID, func(data *UserData) {
|
||||
data.AddressMode = mode
|
||||
})
|
||||
}
|
||||
|
||||
// BridgePass returns the user's bridge password (unencoded).
|
||||
func (user *User) BridgePass() []byte {
|
||||
return user.vault.getUser(user.userID).BridgePass
|
||||
}
|
||||
|
||||
// AuthUID returns the user's auth UID.
|
||||
func (user *User) AuthUID() string {
|
||||
return user.vault.getUser(user.userID).AuthUID
|
||||
}
|
||||
|
||||
// AuthRef returns the user's auth refresh token.
|
||||
func (user *User) AuthRef() string {
|
||||
return user.vault.getUser(user.userID).AuthRef
|
||||
}
|
||||
|
||||
func (user *User) KeyPass() []byte {
|
||||
return user.vault.getUser(user.userID).KeyPass
|
||||
}
|
||||
|
||||
func (user *User) EventID() string {
|
||||
return user.vault.getUser(user.userID).EventID
|
||||
}
|
||||
|
||||
func (user *User) HasSync() bool {
|
||||
return user.vault.getUser(user.userID).HasSync
|
||||
}
|
||||
|
||||
func (user *User) SetKeyPass(keyPass []byte) error {
|
||||
return user.vault.modUser(user.userID, func(data *UserData) {
|
||||
data.KeyPass = keyPass
|
||||
})
|
||||
}
|
||||
|
||||
// SetAuth sets the auth secrets for the given user.
|
||||
func (user *User) SetAuth(authUID, authRef string) error {
|
||||
return user.vault.modUser(user.userID, func(data *UserData) {
|
||||
@ -88,23 +82,59 @@ func (user *User) SetAuth(authUID, authRef string) error {
|
||||
})
|
||||
}
|
||||
|
||||
// SetAddressMode sets the address mode for the given user.
|
||||
func (user *User) SetAddressMode(mode AddressMode) error {
|
||||
// KeyPass returns the user's (salted) key password.
|
||||
func (user *User) KeyPass() []byte {
|
||||
return user.vault.getUser(user.userID).KeyPass
|
||||
}
|
||||
|
||||
// SetKeyPass sets the user's (salted) key password.
|
||||
func (user *User) SetKeyPass(keyPass []byte) error {
|
||||
return user.vault.modUser(user.userID, func(data *UserData) {
|
||||
data.AddressMode = mode
|
||||
data.KeyPass = keyPass
|
||||
})
|
||||
}
|
||||
|
||||
// SyncStatus return's the user's sync status.
|
||||
func (user *User) SyncStatus() SyncStatus {
|
||||
return user.vault.getUser(user.userID).SyncStatus
|
||||
}
|
||||
|
||||
// SetHasLabels sets whether the user's labels have been synced.
|
||||
func (user *User) SetHasLabels(hasLabels bool) error {
|
||||
return user.vault.modUser(user.userID, func(data *UserData) {
|
||||
data.SyncStatus.HasLabels = hasLabels
|
||||
})
|
||||
}
|
||||
|
||||
// SetHasMessages sets whether the user's messages have been synced.
|
||||
func (user *User) SetHasMessages(hasMessages bool) error {
|
||||
return user.vault.modUser(user.userID, func(data *UserData) {
|
||||
data.SyncStatus.HasMessages = hasMessages
|
||||
})
|
||||
}
|
||||
|
||||
// SetLastMessageID sets the last synced message ID for the given user.
|
||||
func (user *User) SetLastMessageID(messageID string) error {
|
||||
return user.vault.modUser(user.userID, func(data *UserData) {
|
||||
data.SyncStatus.LastMessageID = messageID
|
||||
})
|
||||
}
|
||||
|
||||
// ClearSyncStatus clears the user's sync status.
|
||||
func (user *User) ClearSyncStatus() error {
|
||||
return user.vault.modUser(user.userID, func(data *UserData) {
|
||||
data.SyncStatus = SyncStatus{}
|
||||
})
|
||||
}
|
||||
|
||||
// EventID returns the last processed event ID of the user.
|
||||
func (user *User) EventID() string {
|
||||
return user.vault.getUser(user.userID).EventID
|
||||
}
|
||||
|
||||
// SetEventID sets the event ID for the given user.
|
||||
func (user *User) SetEventID(eventID string) error {
|
||||
return user.vault.modUser(user.userID, func(data *UserData) {
|
||||
data.EventID = eventID
|
||||
})
|
||||
}
|
||||
|
||||
// SetSync sets the sync state for the given user.
|
||||
func (user *User) SetSync(hasSync bool) error {
|
||||
return user.vault.modUser(user.userID, func(data *UserData) {
|
||||
data.HasSync = hasSync
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,14 +1,128 @@
|
||||
package vault_test
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/ProtonMail/gluon/imap"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUser_New(t *testing.T) {
|
||||
// Replace the token generator with a dummy one.
|
||||
vault.RandomToken = func(size int) ([]byte, error) {
|
||||
return []byte("token"), nil
|
||||
}
|
||||
|
||||
// Create a new test vault.
|
||||
s := newVault(t)
|
||||
|
||||
// There should be no users in the store.
|
||||
require.Empty(t, s.GetUserIDs())
|
||||
|
||||
// Create a new user.
|
||||
user, err := s.AddUser("userID", "username", "authUID", "authRef", []byte("keyPass"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// The user should be listed in the store.
|
||||
require.ElementsMatch(t, []string{"userID"}, s.GetUserIDs())
|
||||
|
||||
// Check the user's default user information.
|
||||
require.Equal(t, "userID", user.UserID())
|
||||
require.Equal(t, "username", user.Username())
|
||||
|
||||
// Check the user's default auth information.
|
||||
require.Equal(t, "authUID", user.AuthUID())
|
||||
require.Equal(t, "authRef", user.AuthRef())
|
||||
require.Equal(t, "keyPass", string(user.KeyPass()))
|
||||
|
||||
// Check the user has a random bridge password and gluon key.
|
||||
require.Equal(t, "token", string(user.BridgePass()))
|
||||
require.Equal(t, "token", string(user.GluonKey()))
|
||||
|
||||
// Check the user's initial sync status.
|
||||
require.False(t, user.SyncStatus().HasLabels)
|
||||
require.False(t, user.SyncStatus().HasMessages)
|
||||
}
|
||||
|
||||
func TestUser_Clear(t *testing.T) {
|
||||
// Create a new test vault.
|
||||
s := newVault(t)
|
||||
|
||||
// Create a new user.
|
||||
user, err := s.AddUser("userID", "username", "authUID", "authRef", []byte("keyPass"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the user's default auth information.
|
||||
require.Equal(t, "authUID", user.AuthUID())
|
||||
require.Equal(t, "authRef", user.AuthRef())
|
||||
require.Equal(t, "keyPass", string(user.KeyPass()))
|
||||
|
||||
// Clear the user's auth information.
|
||||
require.NoError(t, s.ClearUser("userID"))
|
||||
|
||||
// Check the user's cleared auth information.
|
||||
require.Empty(t, user.AuthUID())
|
||||
require.Empty(t, user.AuthRef())
|
||||
require.Empty(t, user.KeyPass())
|
||||
}
|
||||
|
||||
func TestUser_Delete(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("userID", "username", "authUID", "authRef", []byte("keyPass"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// The user should be listed in the store.
|
||||
require.ElementsMatch(t, []string{"userID"}, s.GetUserIDs())
|
||||
|
||||
// Clear the user's auth information.
|
||||
require.NoError(t, s.DeleteUser("userID"))
|
||||
|
||||
// The store should have no users again.
|
||||
require.Empty(t, s.GetUserIDs())
|
||||
|
||||
// Attempting to use the user should return an error.
|
||||
require.Panics(t, func() { _ = user.AddressMode() })
|
||||
}
|
||||
|
||||
func TestUser_SyncStatus(t *testing.T) {
|
||||
// Create a new test vault.
|
||||
s := newVault(t)
|
||||
|
||||
// Create a new user.
|
||||
user, err := s.AddUser("userID", "username", "authUID", "authRef", []byte("keyPass"))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check the user's initial sync status.
|
||||
require.False(t, user.SyncStatus().HasLabels)
|
||||
require.False(t, user.SyncStatus().HasMessages)
|
||||
require.Empty(t, user.SyncStatus().LastMessageID)
|
||||
|
||||
// Simulate having synced a message.
|
||||
require.NoError(t, user.SetLastMessageID("test"))
|
||||
require.Equal(t, "test", user.SyncStatus().LastMessageID)
|
||||
|
||||
// Simulate finishing the sync.
|
||||
require.NoError(t, user.SetHasLabels(true))
|
||||
require.NoError(t, user.SetHasMessages(true))
|
||||
require.True(t, user.SyncStatus().HasLabels)
|
||||
require.True(t, user.SyncStatus().HasMessages)
|
||||
|
||||
// Clear the sync status.
|
||||
require.NoError(t, user.ClearSyncStatus())
|
||||
|
||||
// Check the user's cleared sync status.
|
||||
require.False(t, user.SyncStatus().HasLabels)
|
||||
require.False(t, user.SyncStatus().HasMessages)
|
||||
require.Empty(t, user.SyncStatus().LastMessageID)
|
||||
}
|
||||
|
||||
/*
|
||||
func TestUser(t *testing.T) {
|
||||
// Replace the token generator with a dummy one.
|
||||
vault.RandomToken = func(size int) ([]byte, error) {
|
||||
@ -101,3 +215,5 @@ func TestUser(t *testing.T) {
|
||||
// List available userIDs. User 1 should be gone.
|
||||
require.ElementsMatch(t, []string{"userID2"}, s.GetUserIDs())
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ProtonMail/gluon/imap"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/certs"
|
||||
"github.com/bradenaw/juniper/xslices"
|
||||
)
|
||||
@ -100,20 +99,7 @@ func (vault *Vault) AddUser(userID, username, authUID, authRef string, keyPass [
|
||||
}
|
||||
|
||||
if err := vault.mod(func(data *Data) {
|
||||
data.Users = append(data.Users, UserData{
|
||||
UserID: userID,
|
||||
Username: username,
|
||||
|
||||
GluonKey: newRandomToken(32),
|
||||
GluonIDs: make(map[string]string),
|
||||
UIDValidity: make(map[string]imap.UID),
|
||||
BridgePass: newRandomString(16),
|
||||
AddressMode: CombinedMode,
|
||||
|
||||
AuthUID: authUID,
|
||||
AuthRef: authRef,
|
||||
KeyPass: keyPass,
|
||||
})
|
||||
data.Users = append(data.Users, newDefaultUser(userID, username, authUID, authRef, keyPass))
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user