GODT-1815: Start with missing gluon files

This commit is contained in:
James Houlahan
2022-09-27 13:22:07 +02:00
parent 612fb7ad7b
commit 9670e29d9f
18 changed files with 241 additions and 101 deletions

View File

@ -50,9 +50,8 @@ type Bridge struct {
imapListener net.Listener
// smtpServer is the bridge's SMTP server.
smtpServer *smtp.Server
smtpBackend *smtpBackend
smtpListener net.Listener
smtpServer *smtp.Server
smtpBackend *smtpBackend
// updater is the bridge's updater.
updater Updater
@ -107,7 +106,13 @@ func New(
return nil, fmt.Errorf("failed to load TLS config: %w", err)
}
imapServer, err := newIMAPServer(vault.GetGluonDir(), curVersion, tlsConfig)
// TODO: Handle case that the gluon directory is missing!
gluonDir, err := getGluonDir(vault)
if err != nil {
return nil, fmt.Errorf("failed to get Gluon directory: %w", err)
}
imapServer, err := newIMAPServer(gluonDir, curVersion, tlsConfig)
if err != nil {
return nil, fmt.Errorf("failed to create IMAP server: %w", err)
}

View File

@ -2,6 +2,7 @@ package bridge_test
import (
"context"
"os"
"testing"
"github.com/Masterminds/semver/v3"
@ -282,6 +283,31 @@ func TestBridge_BadVaultKey(t *testing.T) {
})
}
func TestBridge_MissingGluonDir(t *testing.T) {
withEnv(t, func(ctx context.Context, s *server.Server, dialer *bridge.TestDialer, locator bridge.Locator, vaultKey []byte) {
var gluonDir string
withBridge(t, ctx, s.GetHostURL(), dialer, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
_, err := bridge.LoginUser(context.Background(), username, password, nil, nil)
require.NoError(t, err)
// Move the gluon dir.
bridge.SetGluonDir(ctx, t.TempDir())
// Get the gluon dir.
gluonDir = bridge.GetGluonDir()
})
// The user removes the gluon dir while bridge is not running.
require.NoError(t, os.RemoveAll(gluonDir))
// Bridge starts but can't find the gluon dir; there should be no error.
withBridge(t, ctx, s.GetHostURL(), dialer, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
// ...
})
})
}
// withEnv creates the full test environment and runs the tests.
func withEnv(t *testing.T, tests func(ctx context.Context, server *server.Server, dialer *bridge.TestDialer, locator bridge.Locator, vaultKey []byte)) {
// Create test API.
@ -305,7 +331,7 @@ func withEnv(t *testing.T, tests func(ctx context.Context, server *server.Server
ctx,
server,
bridge.NewTestDialer(),
locations.New(bridge.NewTestLocationsProvider(t), "config-name"),
locations.New(bridge.NewTestLocationsProvider(t.TempDir()), "config-name"),
vaultKey,
)
}

View File

@ -3,12 +3,16 @@ package bridge
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io/fs"
"os"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gluon"
imapEvents "github.com/ProtonMail/gluon/events"
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
"github.com/sirupsen/logrus"
)
@ -17,38 +21,6 @@ const (
defaultClientVersion = "0.0.1"
)
func (bridge *Bridge) GetIMAPPort() int {
return bridge.vault.GetIMAPPort()
}
func (bridge *Bridge) SetIMAPPort(newPort int) error {
if newPort == bridge.vault.GetIMAPPort() {
return nil
}
if err := bridge.vault.SetIMAPPort(newPort); err != nil {
return err
}
return bridge.restartIMAP(context.Background())
}
func (bridge *Bridge) GetIMAPSSL() bool {
return bridge.vault.GetIMAPSSL()
}
func (bridge *Bridge) SetIMAPSSL(newSSL bool) error {
if newSSL == bridge.vault.GetIMAPSSL() {
return nil
}
if err := bridge.vault.SetIMAPSSL(newSSL); err != nil {
return err
}
return bridge.restartIMAP(context.Background())
}
func (bridge *Bridge) serveIMAP() error {
imapListener, err := newListener(bridge.vault.GetIMAPPort(), bridge.vault.GetIMAPSSL(), bridge.tlsConfig)
if err != nil {
@ -83,15 +55,34 @@ func (bridge *Bridge) closeIMAP(ctx context.Context) error {
func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
switch event := event.(type) {
case imapEvents.SessionAdded:
if !bridge.identifier.HasClient() {
bridge.identifier.SetClient(defaultClientName, defaultClientVersion)
if bridge.identifier.HasClient() {
return
}
bridge.identifier.SetClient(defaultClientName, defaultClientVersion)
case imapEvents.IMAPID:
bridge.identifier.SetClient(event.IMAPID.Name, event.IMAPID.Version)
}
}
func getGluonDir(encVault *vault.Vault) (string, error) {
empty, err := isEmpty(encVault.GetGluonDir())
if err != nil {
return "", fmt.Errorf("failed to check if gluon dir is empty: %w", err)
}
if empty {
if err := encVault.ForUser(func(user *vault.User) error {
return user.SetSync(false)
}); err != nil {
return "", fmt.Errorf("failed to reset user sync status: %w", err)
}
}
return encVault.GetGluonDir(), nil
}
func newIMAPServer(gluonDir string, version *semver.Version, tlsConfig *tls.Config) (*gluon.Server, error) {
imapServer, err := gluon.New(
gluon.WithTLS(tlsConfig),
@ -115,3 +106,20 @@ func newIMAPServer(gluonDir string, version *semver.Version, tlsConfig *tls.Conf
return imapServer, nil
}
func isEmpty(dir string) (bool, error) {
if _, err := os.Stat(dir); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return false, fmt.Errorf("failed to stat %s: %w", dir, err)
}
return true, nil
}
entries, err := os.ReadDir(dir)
if err != nil {
return false, fmt.Errorf("failed to read dir %s: %w", dir, err)
}
return len(entries) == 0, nil
}

View File

@ -5,6 +5,7 @@ import (
"crypto/tls"
"errors"
"net"
"os"
"testing"
"github.com/Masterminds/semver/v3"
@ -75,10 +76,20 @@ type TestLocationsProvider struct {
config, cache string
}
func NewTestLocationsProvider(tb testing.TB) *TestLocationsProvider {
func NewTestLocationsProvider(dir string) *TestLocationsProvider {
config, err := os.MkdirTemp(dir, "config")
if err != nil {
panic(err)
}
cache, err := os.MkdirTemp(dir, "cache")
if err != nil {
panic(err)
}
return &TestLocationsProvider{
config: tb.TempDir(),
cache: tb.TempDir(),
config: config,
cache: cache,
}
}

View File

@ -26,6 +26,70 @@ func (bridge *Bridge) SetKeychainApp(helper string) error {
return vault.SetHelper(vaultDir, helper)
}
func (bridge *Bridge) GetIMAPPort() int {
return bridge.vault.GetIMAPPort()
}
func (bridge *Bridge) SetIMAPPort(newPort int) error {
if newPort == bridge.vault.GetIMAPPort() {
return nil
}
if err := bridge.vault.SetIMAPPort(newPort); err != nil {
return err
}
return bridge.restartIMAP(context.Background())
}
func (bridge *Bridge) GetIMAPSSL() bool {
return bridge.vault.GetIMAPSSL()
}
func (bridge *Bridge) SetIMAPSSL(newSSL bool) error {
if newSSL == bridge.vault.GetIMAPSSL() {
return nil
}
if err := bridge.vault.SetIMAPSSL(newSSL); err != nil {
return err
}
return bridge.restartIMAP(context.Background())
}
func (bridge *Bridge) GetSMTPPort() int {
return bridge.vault.GetSMTPPort()
}
func (bridge *Bridge) SetSMTPPort(newPort int) error {
if newPort == bridge.vault.GetSMTPPort() {
return nil
}
if err := bridge.vault.SetSMTPPort(newPort); err != nil {
return err
}
return bridge.restartSMTP()
}
func (bridge *Bridge) GetSMTPSSL() bool {
return bridge.vault.GetSMTPSSL()
}
func (bridge *Bridge) SetSMTPSSL(newSSL bool) error {
if newSSL == bridge.vault.GetSMTPSSL() {
return nil
}
if err := bridge.vault.SetSMTPSSL(newSSL); err != nil {
return err
}
return bridge.restartSMTP()
}
func (bridge *Bridge) GetGluonDir() string {
return bridge.vault.GetGluonDir()
}

View File

@ -10,48 +10,14 @@ import (
"github.com/sirupsen/logrus"
)
func (bridge *Bridge) GetSMTPPort() int {
return bridge.vault.GetSMTPPort()
}
func (bridge *Bridge) SetSMTPPort(newPort int) error {
if newPort == bridge.vault.GetSMTPPort() {
return nil
}
if err := bridge.vault.SetSMTPPort(newPort); err != nil {
return err
}
return bridge.restartSMTP()
}
func (bridge *Bridge) GetSMTPSSL() bool {
return bridge.vault.GetSMTPSSL()
}
func (bridge *Bridge) SetSMTPSSL(newSSL bool) error {
if newSSL == bridge.vault.GetSMTPSSL() {
return nil
}
if err := bridge.vault.SetSMTPSSL(newSSL); err != nil {
return err
}
return bridge.restartSMTP()
}
func (bridge *Bridge) serveSMTP() error {
smtpListener, err := newListener(bridge.vault.GetSMTPPort(), bridge.vault.GetSMTPSSL(), bridge.tlsConfig)
if err != nil {
return fmt.Errorf("failed to create SMTP listener: %w", err)
}
bridge.smtpListener = smtpListener
go func() {
if err := bridge.smtpServer.Serve(bridge.smtpListener); err != nil {
if err := bridge.smtpServer.Serve(smtpListener); err != nil {
logrus.WithError(err).Error("SMTP server stopped")
}
}()

View File

@ -308,7 +308,7 @@ func (bridge *Bridge) addNewUser(
return nil, err
}
if err := vaultUser.UpdateGluonData(gluonID, gluonKey); err != nil {
if err := vaultUser.SetGluonAuth(gluonID, gluonKey); err != nil {
return nil, err
}
@ -336,11 +336,11 @@ func (bridge *Bridge) addExistingUser(
return nil, err
}
if err := vaultUser.UpdateAuth(authUID, authRef); err != nil {
if err := vaultUser.SetAuth(authUID, authRef); err != nil {
return nil, err
}
if err := vaultUser.UpdateKeyPass(saltedKeyPass); err != nil {
if err := vaultUser.SetKeyPass(saltedKeyPass); err != nil {
return nil, err
}

View File

@ -34,7 +34,7 @@ func (user *User) sync(ctx context.Context) error {
UserID: user.ID(),
}
if err := user.vault.UpdateSync(true); err != nil {
if err := user.vault.SetSync(true); err != nil {
return fmt.Errorf("failed to update sync status: %w", err)
}

View File

@ -56,7 +56,7 @@ func New(
return nil, err
}
if err := vault.UpdateEventID(eventID); err != nil {
if err := vault.SetEventID(eventID); err != nil {
return nil, err
}
}
@ -85,7 +85,7 @@ func New(
// When we receive an auth object, we update it in the store.
// This will be used to authorize the user on the next run.
client.AddAuthHandler(func(auth liteapi.Auth) {
if err := user.vault.UpdateAuth(auth.UID, auth.RefreshToken); err != nil {
if err := user.vault.SetAuth(auth.UID, auth.RefreshToken); err != nil {
logrus.WithError(err).Error("Failed to update auth")
}
})
@ -104,7 +104,7 @@ func New(
if err := user.handleAPIEvent(event); err != nil {
logrus.WithError(err).Error("Failed to handle event")
} else {
if err := user.vault.UpdateEventID(event.EventID); err != nil {
if err := user.vault.SetEventID(event.EventID); err != nil {
logrus.WithError(err).Error("Failed to update event ID")
}
}

View File

@ -45,37 +45,37 @@ func (user *User) HasSync() bool {
return user.vault.getUser(user.userID).HasSync
}
func (user *User) UpdateKeyPass(keyPass []byte) error {
func (user *User) SetKeyPass(keyPass []byte) error {
return user.vault.modUser(user.userID, func(data *UserData) {
data.KeyPass = keyPass
})
}
// UpdateAuth updates the auth secrets for the given user.
func (user *User) UpdateAuth(authUID, authRef string) error {
// SetAuth updates 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
data.AuthRef = authRef
})
}
// UpdateGluonData updates the gluon ID and key for the given user.
func (user *User) UpdateGluonData(gluonID string, gluonKey []byte) error {
// SetGluonAuth updates the gluon ID and key for the given user.
func (user *User) SetGluonAuth(gluonID string, gluonKey []byte) error {
return user.vault.modUser(user.userID, func(data *UserData) {
data.GluonID = gluonID
data.GluonKey = gluonKey
})
}
// UpdateEventID updates the event ID for the given user.
func (user *User) UpdateEventID(eventID string) error {
// SetEventID updates 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
})
}
// UpdateSync updates the sync state for the given user.
func (user *User) UpdateSync(hasSync bool) error {
// SetSync updates 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
})

View File

@ -24,16 +24,16 @@ func TestUser(t *testing.T) {
require.NoError(t, err)
// Set event IDs for user 1 and 2.
require.NoError(t, user1.UpdateEventID("eventID1"))
require.NoError(t, user2.UpdateEventID("eventID2"))
require.NoError(t, user1.SetEventID("eventID1"))
require.NoError(t, user2.SetEventID("eventID2"))
// Set sync state for user 1 and 2.
require.NoError(t, user1.UpdateSync(true))
require.NoError(t, user2.UpdateSync(false))
require.NoError(t, user1.SetSync(true))
require.NoError(t, user2.SetSync(false))
// Set gluon data for user 1 and 2.
require.NoError(t, user1.UpdateGluonData("gluonID1", []byte("gluonKey1")))
require.NoError(t, user2.UpdateGluonData("gluonID2", []byte("gluonKey2")))
require.NoError(t, user1.SetGluonAuth("gluonID1", []byte("gluonKey1")))
require.NoError(t, user2.SetGluonAuth("gluonID2", []byte("gluonKey2")))
// List available users.
require.ElementsMatch(t, []string{"userID1", "userID2"}, s.GetUserIDs())

View File

@ -74,6 +74,22 @@ func (vault *Vault) GetUser(userID string) (*User, error) {
}, nil
}
// ForUser executes a callback for each user in the vault.
func (vault *Vault) ForUser(fn func(*User) error) error {
for _, userID := range vault.GetUserIDs() {
user, err := vault.GetUser(userID)
if err != nil {
return err
}
if err := fn(user); err != nil {
return err
}
}
return nil
}
// AddUser creates a new user in the vault with the given ID and username.
// A bridge password is generated using the package's token generator.
func (vault *Vault) AddUser(userID, username, authUID, authRef string, keyPass []byte) (*User, error) {