GODT-1815: Combined/Split mode

This commit is contained in:
James Houlahan
2022-09-28 11:29:33 +02:00
parent 9670e29d9f
commit e9672e6bba
55 changed files with 1909 additions and 705 deletions

View File

@ -5,9 +5,14 @@ import (
)
// RandomToken is a function that returns a random token.
var RandomToken func(size int) ([]byte, error)
// By default, we use crypto.RandomToken to generate tokens.
func init() {
RandomToken = crypto.RandomToken
var RandomToken = crypto.RandomToken
func newRandomToken(size int) []byte {
token, err := RandomToken(size)
if err != nil {
panic(err)
}
return token
}

View File

@ -4,6 +4,7 @@ import (
"math/rand"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
)
@ -45,15 +46,24 @@ 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
GluonID string
GluonKey []byte
BridgePass string
GluonKey []byte
GluonIDs map[string]string
UIDValidity map[string]imap.UID
BridgePass []byte
AddressMode AddressMode
AuthUID string
AuthRef string

View File

@ -1,5 +1,11 @@
package vault
import (
"encoding/hex"
"github.com/ProtonMail/gluon/imap"
)
type User struct {
vault *Vault
userID string
@ -13,16 +19,41 @@ func (user *User) Username() string {
return user.vault.getUser(user.userID).Username
}
func (user *User) GluonID() string {
return user.vault.getUser(user.userID).GluonID
func (user *User) GetGluonIDs() map[string]string {
return user.vault.getUser(user.userID).GluonIDs
}
func (user *User) SetGluonID(addrID, gluonID string) error {
return user.vault.modUser(user.userID, func(data *UserData) {
data.GluonIDs[addrID] = gluonID
})
}
func (user *User) GetUIDValidity(addrID string) (imap.UID, bool) {
validity, ok := user.vault.getUser(user.userID).UIDValidity[addrID]
if !ok {
return imap.UID(0), false
}
return validity, true
}
func (user *User) SetUIDValidity(addrID string, validity imap.UID) error {
return user.vault.modUser(user.userID, func(data *UserData) {
data.UIDValidity[addrID] = validity
})
}
func (user *User) GluonKey() []byte {
return user.vault.getUser(user.userID).GluonKey
}
func (user *User) AddressMode() AddressMode {
return user.vault.getUser(user.userID).AddressMode
}
func (user *User) BridgePass() string {
return user.vault.getUser(user.userID).BridgePass
return hex.EncodeToString(user.vault.getUser(user.userID).BridgePass)
}
func (user *User) AuthUID() string {
@ -51,7 +82,7 @@ func (user *User) SetKeyPass(keyPass []byte) error {
})
}
// SetAuth updates the auth secrets for the given user.
// 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) {
data.AuthUID = authUID
@ -59,33 +90,23 @@ func (user *User) SetAuth(authUID, authRef string) error {
})
}
// SetGluonAuth updates the gluon ID and key for the given user.
func (user *User) SetGluonAuth(gluonID string, gluonKey []byte) error {
// 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.GluonID = gluonID
data.GluonKey = gluonKey
data.AddressMode = mode
})
}
// SetEventID updates the event ID for the given user.
// 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 updates the sync state for the given user.
// 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
})
}
// Clear clears the secrets for the given user.
func (user *User) Clear() error {
return user.vault.modUser(user.userID, func(data *UserData) {
data.AuthUID = ""
data.AuthRef = ""
data.KeyPass = nil
})
}

View File

@ -4,6 +4,7 @@ import (
"encoding/hex"
"testing"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
"github.com/stretchr/testify/require"
)
@ -32,30 +33,48 @@ func TestUser(t *testing.T) {
require.NoError(t, user2.SetSync(false))
// Set gluon data for user 1 and 2.
require.NoError(t, user1.SetGluonAuth("gluonID1", []byte("gluonKey1")))
require.NoError(t, user2.SetGluonAuth("gluonID2", []byte("gluonKey2")))
require.NoError(t, user1.SetGluonID("addrID1", "gluonID1"))
require.NoError(t, user2.SetGluonID("addrID2", "gluonID2"))
require.NoError(t, user1.SetUIDValidity("addrID1", imap.UID(1)))
require.NoError(t, user2.SetUIDValidity("addrID2", imap.UID(2)))
// List available users.
require.ElementsMatch(t, []string{"userID1", "userID2"}, s.GetUserIDs())
// Check gluon information for user 1.
gluonID1, ok := user1.GetGluonIDs()["addrID1"]
require.True(t, ok)
require.Equal(t, "gluonID1", gluonID1)
uidValidity1, ok := user1.GetUIDValidity("addrID1")
require.True(t, ok)
require.Equal(t, imap.UID(1), uidValidity1)
require.NotEmpty(t, user1.GluonKey())
// Get auth information for user 1.
require.Equal(t, "userID1", user1.UserID())
require.Equal(t, "user1", user1.Username())
require.Equal(t, "gluonID1", user1.GluonID())
require.Equal(t, []byte("gluonKey1"), user1.GluonKey())
require.Equal(t, hex.EncodeToString([]byte("token")), user1.BridgePass())
require.Equal(t, vault.CombinedMode, user1.AddressMode())
require.Equal(t, "authUID1", user1.AuthUID())
require.Equal(t, "authRef1", user1.AuthRef())
require.Equal(t, []byte("keyPass1"), user1.KeyPass())
require.Equal(t, "eventID1", user1.EventID())
require.Equal(t, true, user1.HasSync())
// Check gluon information for user 1.
gluonID2, ok := user2.GetGluonIDs()["addrID2"]
require.True(t, ok)
require.Equal(t, "gluonID2", gluonID2)
uidValidity2, ok := user2.GetUIDValidity("addrID2")
require.True(t, ok)
require.Equal(t, imap.UID(2), uidValidity2)
require.NotEmpty(t, user2.GluonKey())
// Get auth information for user 2.
require.Equal(t, "userID2", user2.UserID())
require.Equal(t, "user2", user2.Username())
require.Equal(t, "gluonID2", user2.GluonID())
require.Equal(t, []byte("gluonKey2"), user2.GluonKey())
require.Equal(t, hex.EncodeToString([]byte("token")), user2.BridgePass())
require.Equal(t, vault.CombinedMode, user2.AddressMode())
require.Equal(t, "authUID2", user2.AuthUID())
require.Equal(t, "authRef2", user2.AuthRef())
require.Equal(t, []byte("keyPass2"), user2.KeyPass())
@ -63,8 +82,8 @@ func TestUser(t *testing.T) {
require.Equal(t, false, user2.HasSync())
// Clear the users.
require.NoError(t, user1.Clear())
require.NoError(t, user2.Clear())
require.NoError(t, s.ClearUser("userID1"))
require.NoError(t, s.ClearUser("userID2"))
// Their secrets should now be cleared.
require.Equal(t, "", user1.AuthUID())

View File

@ -4,7 +4,6 @@ import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"io/fs"
@ -12,6 +11,7 @@ import (
"os"
"path/filepath"
"github.com/ProtonMail/gluon/imap"
"github.com/ProtonMail/proton-bridge/v2/internal/certs"
"github.com/bradenaw/juniper/xslices"
)
@ -99,16 +99,16 @@ func (vault *Vault) AddUser(userID, username, authUID, authRef string, keyPass [
return nil, errors.New("user already exists")
}
tok, err := RandomToken(16)
if err != nil {
return nil, err
}
if err := vault.mod(func(data *Data) {
data.Users = append(data.Users, UserData{
UserID: userID,
Username: username,
BridgePass: hex.EncodeToString(tok),
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,
@ -121,6 +121,14 @@ func (vault *Vault) AddUser(userID, username, authUID, authRef string, keyPass [
return vault.GetUser(userID)
}
func (vault *Vault) ClearUser(userID string) error {
return vault.modUser(userID, func(data *UserData) {
data.AuthUID = ""
data.AuthRef = ""
data.KeyPass = nil
})
}
// DeleteUser removes the given user from the vault.
func (vault *Vault) DeleteUser(userID string) error {
return vault.mod(func(data *Data) {