From 1c88ce3cc0e5d81c33e31321623aee6fc366bce2 Mon Sep 17 00:00:00 2001 From: Romain Le Jeune Date: Wed, 8 Feb 2023 10:06:53 +0000 Subject: [PATCH] feat(GODT-2255): Randomize the focus service port. --- README.md | 29 ++++----- internal/app/app.go | 13 ++-- internal/app/singleinstance.go | 4 +- internal/bridge/bridge.go | 2 +- internal/bridge/bridge_test.go | 5 +- internal/focus/client.go | 19 ++++-- internal/focus/focus_test.go | 60 +++++++++++++++++-- internal/focus/service.go | 37 ++++++++---- .../frontend/bridge-gui/bridge-gui/main.cpp | 16 ++++- .../bridgepp/FocusGRPC/FocusGRPCClient.cpp | 35 ++++++++++- .../bridgepp/FocusGRPC/FocusGRPCClient.h | 5 +- internal/frontend/grpc/service.go | 18 ++---- internal/frontend/grpc/service_methods.go | 5 +- internal/frontend/grpc/types.go | 4 -- internal/{frontend/grpc => service}/config.go | 19 +++++- .../{frontend/grpc => service}/config_test.go | 6 +- internal/service/types.go | 22 +++++++ tests/ctx_bridge_test.go | 3 +- 18 files changed, 226 insertions(+), 76 deletions(-) rename internal/{frontend/grpc => service}/config.go (79%) rename internal/{frontend/grpc => service}/config_test.go (93%) create mode 100644 internal/service/types.go diff --git a/README.md b/README.md index 60f890ed..0fd5c853 100644 --- a/README.md +++ b/README.md @@ -67,25 +67,26 @@ There are now three types of system folders which Bridge recognises: |--------|-------------------------------------|-----------------------------------------------------|-------------------------------------|---------------------------------------| | config | %APPDATA%\protonmail\bridge-v3 | ~/Library/Application Support/protonmail/bridge-v3 | ~/.config/protonmail/bridge-v3 | $XDG_CONFIG_HOME/protonmail/bridge-v3 | | cache | %LOCALAPPDATA%\protonmail\bridge-v3 | ~/Library/Caches/protonmail/bridge-v3 | ~/.cache/protonmail/bridge-v3 | $XDG_CACHE_HOME/protonmail/bridge-v3 | -| data | %APPDATA%\protonmail\bridge-v3 | ~/Library/Application Support/protonmail/bridge-v3 | ~/.local/share/protonmail/bridge-v3 | $XDG_DATA_HOME/protonmail/bridge-v3 | +| data | %APPDATA%\protonmail\bridge-v3 | ~/Library/Application Support/protonmail/bridge-v3 | ~/.local/share/protonmail/bridge-v3 | $XDG_DATA_HOME/protonmail/bridge-v3 | | temp | %LOCALAPPDATA%\Temp | $TMPDIR if non-empty, else /tmp | $TMPDIR if non-empty, else /tmp | $TMPDIR if non-empty, else /tmp | ## Files -| | Base Dir | Path | -|-----------------------|----------|----------------------------| -| bridge lock file | cache | bridge.lock | -| bridge-gui lock file | cache | bridge-gui.lock | -| vault | config | vault.enc | -| gRPC server json | config | grpcServerConfig.json | -| gRPC client json | config | grpcClientConfig_.json | -| Logs | data | logs | -| gluon DB | data | gluon/backend/db | -| gluon messages | data | gluon/backend/store | -| Update files | data | updates | -| sentry cache | data | sentry_cache | -| Mac/Linux File Socket | temp | bridge{4_DIGITS} | +| | Base Dir | Path | +|------------------------|----------|----------------------------| +| bridge lock file | cache | bridge.lock | +| bridge-gui lock file | cache | bridge-gui.lock | +| vault | config | vault.enc | +| gRPC server json | config | grpcServerConfig.json | +| gRPC client json | config | grpcClientConfig_.json | +| gRPC Focus server json | config | grpcFocusServerConfig.json | +| Logs | data | logs | +| gluon DB | data | gluon/backend/db | +| gluon messages | data | gluon/backend/store | +| Update files | data | updates | +| sentry cache | data | sentry_cache | +| Mac/Linux File Socket | temp | bridge{4_DIGITS} | diff --git a/internal/app/app.go b/internal/app/app.go index 3167456f..3a259a26 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -208,7 +208,12 @@ func run(c *cli.Context) error { } // Ensure we are the only instance running. - return withSingleInstance(locations, version, func() error { + settings, err := locations.ProvideSettingsPath() + if err != nil { + logrus.WithError(err).Error("Failed to get settings path") + } + + return withSingleInstance(settings, locations.GetLockFile(), version, func() error { // Unlock the encrypted vault. return WithVault(locations, func(v *vault.Vault, insecure, corrupt bool) error { // Report insecure vault. @@ -278,15 +283,15 @@ func run(c *cli.Context) error { } // If there's another instance already running, try to raise it and exit. -func withSingleInstance(locations *locations.Locations, version *semver.Version, fn func() error) error { +func withSingleInstance(settingPath, lockFile string, version *semver.Version, fn func() error) error { logrus.Debug("Checking for other instances") defer logrus.Debug("Single instance stopped") - lock, err := checkSingleInstance(locations.GetLockFile(), version) + lock, err := checkSingleInstance(settingPath, lockFile, version) if err != nil { logrus.Info("Another instance is already running; raising it") - if ok := focus.TryRaise(); !ok { + if ok := focus.TryRaise(settingPath); !ok { return fmt.Errorf("another instance is already running but it could not be raised") } diff --git a/internal/app/singleinstance.go b/internal/app/singleinstance.go index da9ce775..4e6b6dc7 100644 --- a/internal/app/singleinstance.go +++ b/internal/app/singleinstance.go @@ -34,7 +34,7 @@ import ( // // For macOS and Linux when already running version is older than this instance // it will kill old and continue with this new bridge (i.e. no error returned). -func checkSingleInstance(lockFilePath string, curVersion *semver.Version) (*os.File, error) { +func checkSingleInstance(settingPath, lockFilePath string, curVersion *semver.Version) (*os.File, error) { if lock, err := singleinstance.CreateLockFile(lockFilePath); err == nil { logrus.WithField("path", lockFilePath).Debug("Created lock file; no other instance is running") return lock, nil @@ -44,7 +44,7 @@ func checkSingleInstance(lockFilePath string, curVersion *semver.Version) (*os.F // We couldn't create the lock file, so another instance is probably running. // Check if it's an older version of the app. - lastVersion, ok := focus.TryVersion() + lastVersion, ok := focus.TryVersion(settingPath) if !ok { return nil, fmt.Errorf("failed to determine version of running instance") } diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index 1a5432cd..75b3b428 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -251,7 +251,7 @@ func newBridge( return nil, fmt.Errorf("failed to create IMAP server: %w", err) } - focusService, err := focus.NewService(curVersion) + focusService, err := focus.NewService(locator, curVersion) if err != nil { return nil, fmt.Errorf("failed to create focus service: %w", err) } diff --git a/internal/bridge/bridge_test.go b/internal/bridge/bridge_test.go index c3406628..ec4f5b83 100644 --- a/internal/bridge/bridge_test.go +++ b/internal/bridge/bridge_test.go @@ -123,8 +123,11 @@ func TestBridge_Focus(t *testing.T) { raiseCh, done := bridge.GetEvents(events.Raise{}) defer done() + settingsFolder, err := locator.ProvideSettingsPath() + require.NoError(t, err) + // Simulate a focus event. - focus.TryRaise() + focus.TryRaise(settingsFolder) // Wait for the event. require.IsType(t, events.Raise{}, <-raiseCh) diff --git a/internal/focus/client.go b/internal/focus/client.go index 0c5d8834..773c2e9e 100644 --- a/internal/focus/client.go +++ b/internal/focus/client.go @@ -21,9 +21,11 @@ import ( "context" "fmt" "net" + "path/filepath" "github.com/Masterminds/semver/v3" "github.com/ProtonMail/proton-bridge/v3/internal/focus/proto" + "github.com/ProtonMail/proton-bridge/v3/internal/service" "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -32,10 +34,10 @@ import ( // TryRaise tries to raise the application by dialing the focus service. // It returns true if the service is running and the application was told to raise. -func TryRaise() bool { +func TryRaise(settingsPath string) bool { var raised bool - if err := withClientConn(context.Background(), func(ctx context.Context, client proto.FocusClient) error { + if err := withClientConn(context.Background(), settingsPath, func(ctx context.Context, client proto.FocusClient) error { if _, err := client.Raise(ctx, &emptypb.Empty{}); err != nil { return fmt.Errorf("failed to call client.Raise: %w", err) } @@ -53,10 +55,10 @@ func TryRaise() bool { // TryVersion tries to determine the version of the running application instance. // It returns the version and true if the version could be determined. -func TryVersion() (*semver.Version, bool) { +func TryVersion(settingsPath string) (*semver.Version, bool) { var version *semver.Version - if err := withClientConn(context.Background(), func(ctx context.Context, client proto.FocusClient) error { + if err := withClientConn(context.Background(), settingsPath, func(ctx context.Context, client proto.FocusClient) error { raw, err := client.Version(ctx, &emptypb.Empty{}) if err != nil { return fmt.Errorf("failed to call client.Version: %w", err) @@ -78,10 +80,15 @@ func TryVersion() (*semver.Version, bool) { return version, true } -func withClientConn(ctx context.Context, fn func(context.Context, proto.FocusClient) error) error { +func withClientConn(ctx context.Context, settingsPath string, fn func(context.Context, proto.FocusClient) error) error { + var config = service.Config{} + err := config.Load(filepath.Join(settingsPath, serverConfigFileName)) + if err != nil { + return err + } cc, err := grpc.DialContext( ctx, - net.JoinHostPort(Host, fmt.Sprint(Port)), + net.JoinHostPort(Host, fmt.Sprint(config.Port)), grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { diff --git a/internal/focus/focus_test.go b/internal/focus/focus_test.go index b6dd359b..059d243d 100644 --- a/internal/focus/focus_test.go +++ b/internal/focus/focus_test.go @@ -18,19 +18,25 @@ package focus import ( + "os" "testing" "github.com/Masterminds/semver/v3" + "github.com/ProtonMail/proton-bridge/v3/internal/locations" "github.com/stretchr/testify/require" ) func TestFocus_Raise(t *testing.T) { + tmpDir := t.TempDir() + locations := locations.New(newTestLocationsProvider(tmpDir), "config-name") // Start the focus service. - service, err := NewService(semver.MustParse("1.2.3")) + service, err := NewService(locations, semver.MustParse("1.2.3")) require.NoError(t, err) + settingsFolder, err := locations.ProvideSettingsPath() + require.NoError(t, err) // Try to dial it, it should succeed. - require.True(t, TryRaise()) + require.True(t, TryRaise(settingsFolder)) // The service should report a raise call. <-service.GetRaiseCh() @@ -39,16 +45,60 @@ func TestFocus_Raise(t *testing.T) { service.Close() // Try to dial it, it should fail. - require.False(t, TryRaise()) + require.False(t, TryRaise(settingsFolder)) } func TestFocus_Version(t *testing.T) { + tmpDir := t.TempDir() + locations := locations.New(newTestLocationsProvider(tmpDir), "config-name") // Start the focus service. - _, err := NewService(semver.MustParse("1.2.3")) + _, err := NewService(locations, semver.MustParse("1.2.3")) + require.NoError(t, err) + + settingsFolder, err := locations.ProvideSettingsPath() require.NoError(t, err) // Try to dial it, it should succeed. - version, ok := TryVersion() + version, ok := TryVersion(settingsFolder) require.True(t, ok) require.Equal(t, "1.2.3", version.String()) } + +type TestLocationsProvider struct { + config, data, cache string +} + +func newTestLocationsProvider(dir string) *TestLocationsProvider { + config, err := os.MkdirTemp(dir, "config") + if err != nil { + panic(err) + } + + data, err := os.MkdirTemp(dir, "data") + if err != nil { + panic(err) + } + + cache, err := os.MkdirTemp(dir, "cache") + if err != nil { + panic(err) + } + + return &TestLocationsProvider{ + config: config, + data: data, + cache: cache, + } +} + +func (provider *TestLocationsProvider) UserConfig() string { + return provider.config +} + +func (provider *TestLocationsProvider) UserData() string { + return provider.data +} + +func (provider *TestLocationsProvider) UserCache() string { + return provider.cache +} diff --git a/internal/focus/service.go b/internal/focus/service.go index a7728dfb..ab2d0127 100644 --- a/internal/focus/service.go +++ b/internal/focus/service.go @@ -25,16 +25,16 @@ import ( "github.com/Masterminds/semver/v3" "github.com/ProtonMail/proton-bridge/v3/internal/focus/proto" + "github.com/ProtonMail/proton-bridge/v3/internal/service" "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/protobuf/types/known/emptypb" ) -// Host is the local host to listen on. -const Host = "127.0.0.1" - -// Port is the port to listen on. -var Port = 1042 // nolint:gochecknoglobals +const ( + Host = "127.0.0.1" + serverConfigFileName = "grpcFocusServerConfig.json" +) // Service is a gRPC service that can be used to raise the application. type Service struct { @@ -47,26 +47,39 @@ type Service struct { // NewService creates a new focus service. // It listens on the local host and port 1042 (by default). -func NewService(version *semver.Version) (*Service, error) { - service := &Service{ +func NewService(locator service.Locator, version *semver.Version) (*Service, error) { + serv := &Service{ server: grpc.NewServer(), raiseCh: make(chan struct{}, 1), version: version, } - proto.RegisterFocusServer(service.server, service) + proto.RegisterFocusServer(serv.server, serv) - if listener, err := net.Listen("tcp", net.JoinHostPort(Host, fmt.Sprint(Port))); err != nil { - logrus.WithError(err).Warn("Failed to start focus service") + if listener, err := net.Listen("tcp", net.JoinHostPort(Host, fmt.Sprint(0))); err != nil { + logrus.WithError(err).Warn("Failed to start focus serv") } else { + config := service.Config{} + // retrieve the port assigned by the system, so that we can put it in the config file. + address, ok := listener.Addr().(*net.TCPAddr) + if !ok { + return nil, fmt.Errorf("could not retrieve gRPC service listener address") + } + config.Port = address.Port + if path, err := service.SaveGRPCServerConfigFile(locator, &config, serverConfigFileName); err != nil { + logrus.WithError(err).WithField("path", path).Warn("Could not write focus gRPC service config file") + } else { + logrus.WithField("path", path).Info("Successfully saved gRPC Focus service config file") + } + go func() { - if err := service.server.Serve(listener); err != nil { + if err := serv.server.Serve(listener); err != nil { fmt.Printf("failed to serve: %v", err) } }() } - return service, nil + return serv, nil } // Raise implements the gRPC FocusService interface; it raises the application. diff --git a/internal/frontend/bridge-gui/bridge-gui/main.cpp b/internal/frontend/bridge-gui/bridge-gui/main.cpp index 7e2f89c7..b71a1606 100644 --- a/internal/frontend/bridge-gui/bridge-gui/main.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/main.cpp @@ -229,8 +229,21 @@ bool isBridgeRunning() { void focusOtherInstance() { try { FocusGRPCClient client; + GRPCConfig sc; + QString const path = FocusGRPCClient::grpcFocusServerConfigPath(); + QFile file(path); + if (file.exists()) { + if (!sc.load(path)) { + throw Exception("The gRPC focus service configuration file is invalid."); + } + } + else { + throw Exception("Server did not provide gRPC Focus service configuration."); + } + + QString error; - if (!client.connectToServer(5000, &error)) { + if (!client.connectToServer(5000, sc.port, &error)) { throw Exception(QString("Could not connect to bridge focus service for a raise call: %1").arg(error)); } if (!client.raise().ok()) { @@ -344,6 +357,7 @@ int main(int argc, char *argv[]) { } // before launching bridge, we remove any trailing service config file, because we need to make sure we get a newly generated one. + FocusGRPCClient::removeServiceConfigFile(); GRPCClient::removeServiceConfigFile(); launchBridge(cliOptions.bridgeArgs); } diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.cpp index ca1e751e..3f9be02f 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.cpp @@ -17,6 +17,7 @@ #include "FocusGRPCClient.h" +#include "../BridgeUtils.h" #include "../Exception/Exception.h" @@ -29,7 +30,6 @@ namespace { Empty empty; ///< Empty protobuf message, re-used across calls. -qint64 const port = 1042; ///< The port for the focus service. QString const hostname = "127.0.0.1"; ///< The hostname of the focus service. @@ -39,12 +39,43 @@ QString const hostname = "127.0.0.1"; ///< The hostname of the focus service. namespace bridgepp { +//**************************************************************************************************************************************************** +/// \return the gRPC Focus server config file name +//**************************************************************************************************************************************************** +QString grpcFocusServerConfigFilename() { + return "grpcFocusServerConfig.json"; +} + + +//**************************************************************************************************************************************************** +/// \return The absolute path of the focus service config path. +//**************************************************************************************************************************************************** +QString FocusGRPCClient::grpcFocusServerConfigPath() { + return QDir(userConfigDir()).absoluteFilePath(grpcFocusServerConfigFilename()); +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void FocusGRPCClient::removeServiceConfigFile() { + QString const path = grpcFocusServerConfigPath(); + if (!QFile(path).exists()) { + return; + } + if (!QFile().remove(path)) { + throw Exception("Could not remove gRPC focus service config file."); + } +} + + //**************************************************************************************************************************************************** /// \param[in] timeoutMs The timeout for the connexion. +/// \param[in] port The gRPC server port. /// \param[out] outError if not null and the function returns false. /// \return true iff the connexion was successfully established. //**************************************************************************************************************************************************** -bool FocusGRPCClient::connectToServer(qint64 timeoutMs, QString *outError) { +bool FocusGRPCClient::connectToServer(qint64 timeoutMs, quint16 port, QString *outError) { try { QString const address = QString("%1:%2").arg(hostname).arg(port); channel_ = grpc::CreateChannel(address.toStdString(), grpc::InsecureChannelCredentials()); diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.h index 6b6bd9e8..2c597fac 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.h @@ -31,6 +31,9 @@ namespace bridgepp { /// \brief Focus GRPC client class //********************************************************************************************************************** class FocusGRPCClient { +public: // static member functions + static void removeServiceConfigFile(); ///< Delete the service config file. + static QString grpcFocusServerConfigPath(); ///< Return the path of the gRPC Focus server config file. public: // member functions. FocusGRPCClient() = default; ///< Default constructor. FocusGRPCClient(FocusGRPCClient const &) = delete; ///< Disabled copy-constructor. @@ -38,7 +41,7 @@ public: // member functions. ~FocusGRPCClient() = default; ///< Destructor. FocusGRPCClient &operator=(FocusGRPCClient const &) = delete; ///< Disabled assignment operator. FocusGRPCClient &operator=(FocusGRPCClient &&) = delete; ///< Disabled move assignment operator. - bool connectToServer(qint64 timeoutMs, QString *outError = nullptr); ///< Connect to the focus server + bool connectToServer(qint64 timeoutMs, quint16 port, QString *outError = nullptr); ///< Connect to the focus server grpc::Status raise(); ///< Performs the 'raise' call. grpc::Status version(QString &outVersion); ///< Performs the 'version' call. diff --git a/internal/frontend/grpc/service.go b/internal/frontend/grpc/service.go index 9c429378..732ef823 100644 --- a/internal/frontend/grpc/service.go +++ b/internal/frontend/grpc/service.go @@ -38,6 +38,7 @@ import ( "github.com/ProtonMail/proton-bridge/v3/internal/certs" "github.com/ProtonMail/proton-bridge/v3/internal/events" "github.com/ProtonMail/proton-bridge/v3/internal/safe" + "github.com/ProtonMail/proton-bridge/v3/internal/service" "github.com/ProtonMail/proton-bridge/v3/internal/updater" "github.com/bradenaw/juniper/xslices" "github.com/elastic/go-sysinfo" @@ -97,7 +98,7 @@ type Service struct { // nolint:structcheck func NewService( panicHandler CrashHandler, restarter Restarter, - locations Locator, + locations service.Locator, bridge *bridge.Bridge, eventCh <-chan events.Event, quitCh <-chan struct{}, @@ -109,7 +110,7 @@ func NewService( logrus.WithError(err).Panic("Could not generate gRPC TLS config") } - config := Config{ + config := service.Config{ Cert: string(certPEM), Token: uuid.NewString(), } @@ -140,7 +141,7 @@ func NewService( config.Port = address.Port } - if path, err := saveGRPCServerConfigFile(locations, &config); err != nil { + if path, err := service.SaveGRPCServerConfigFile(locations, &config, serverConfigFileName); err != nil { logrus.WithError(err).WithField("path", path).Panic("Could not write gRPC service config file") } else { logrus.WithField("path", path).Info("Successfully saved gRPC service config file") @@ -486,17 +487,6 @@ func newTLSConfig() (*tls.Config, []byte, error) { }, certPEM, nil } -func saveGRPCServerConfigFile(locations Locator, config *Config) (string, error) { - settingsPath, err := locations.ProvideSettingsPath() - if err != nil { - return "", err - } - - configPath := filepath.Join(settingsPath, serverConfigFileName) - - return configPath, config.save(configPath) -} - // validateServerToken verify that the server token provided by the client is valid. func validateServerToken(ctx context.Context, wantToken string) error { values, ok := metadata.FromIncomingContext(ctx) diff --git a/internal/frontend/grpc/service_methods.go b/internal/frontend/grpc/service_methods.go index 749c09f7..8d71c67c 100644 --- a/internal/frontend/grpc/service_methods.go +++ b/internal/frontend/grpc/service_methods.go @@ -32,6 +32,7 @@ import ( "github.com/ProtonMail/proton-bridge/v3/internal/events" "github.com/ProtonMail/proton-bridge/v3/internal/frontend/theme" "github.com/ProtonMail/proton-bridge/v3/internal/safe" + "github.com/ProtonMail/proton-bridge/v3/internal/service" "github.com/ProtonMail/proton-bridge/v3/internal/updater" "github.com/ProtonMail/proton-bridge/v3/pkg/keychain" "github.com/ProtonMail/proton-bridge/v3/pkg/ports" @@ -51,8 +52,8 @@ func (s *Service) CheckTokens(ctx context.Context, clientConfigPath *wrapperspb. path := clientConfigPath.Value logEntry := s.log.WithField("path", path) - var clientConfig Config - if err := clientConfig.load(path); err != nil { + var clientConfig service.Config + if err := clientConfig.Load(path); err != nil { logEntry.WithError(err).Error("Could not read gRPC client config file") return nil, err diff --git a/internal/frontend/grpc/types.go b/internal/frontend/grpc/types.go index 72aa819d..f92f8b3b 100644 --- a/internal/frontend/grpc/types.go +++ b/internal/frontend/grpc/types.go @@ -26,7 +26,3 @@ type Restarter interface { AddFlags(flags ...string) Override(exe string) } - -type Locator interface { - ProvideSettingsPath() (string, error) -} diff --git a/internal/frontend/grpc/config.go b/internal/service/config.go similarity index 79% rename from internal/frontend/grpc/config.go rename to internal/service/config.go index 03842e63..7392b1ba 100644 --- a/internal/frontend/grpc/config.go +++ b/internal/service/config.go @@ -15,11 +15,12 @@ // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . -package grpc +package service import ( "encoding/json" "os" + "path/filepath" ) // Config is a structure containing the service configuration data that are exchanged by the gRPC server and client. @@ -53,8 +54,8 @@ func (s *Config) _save(path string) error { return json.NewEncoder(f).Encode(s) } -// load loads a gRPC service configuration from file. -func (s *Config) load(path string) error { +// Load loads a gRPC service configuration from file. +func (s *Config) Load(path string) error { f, err := os.Open(path) //nolint:errcheck,gosec if err != nil { return err @@ -64,3 +65,15 @@ func (s *Config) load(path string) error { return json.NewDecoder(f).Decode(s) } + +// SaveGRPCServerConfigFile save GRPC configuration file. +func SaveGRPCServerConfigFile(locations Locator, config *Config, filename string) (string, error) { + settingsPath, err := locations.ProvideSettingsPath() + if err != nil { + return "", err + } + + configPath := filepath.Join(settingsPath, filename) + + return configPath, config.save(configPath) +} diff --git a/internal/frontend/grpc/config_test.go b/internal/service/config_test.go similarity index 93% rename from internal/frontend/grpc/config_test.go rename to internal/service/config_test.go index 432c74ba..ecc3a4e4 100644 --- a/internal/frontend/grpc/config_test.go +++ b/internal/service/config_test.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . -package grpc +package service import ( "path/filepath" @@ -46,11 +46,11 @@ func TestConfig(t *testing.T) { require.NoError(t, conf1.save(tempFilePath)) conf2 := Config{} - require.NoError(t, conf2.load(tempFilePath)) + require.NoError(t, conf2.Load(tempFilePath)) require.Equal(t, conf1, conf2) // failure to load - require.Error(t, conf2.load(tempFilePath+"_")) + require.Error(t, conf2.Load(tempFilePath+"_")) // failure to save require.Error(t, conf2.save(filepath.Join(tempDir, "non/existing/folder", tempFileName))) diff --git a/internal/service/types.go b/internal/service/types.go new file mode 100644 index 00000000..faa77624 --- /dev/null +++ b/internal/service/types.go @@ -0,0 +1,22 @@ +// Copyright (c) 2023 Proton AG +// +// This file is part of Proton Mail Bridge.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 . + +package service + +type Locator interface { + ProvideSettingsPath() (string, error) +} diff --git a/tests/ctx_bridge_test.go b/tests/ctx_bridge_test.go index 3d7a75bd..cb0a2446 100644 --- a/tests/ctx_bridge_test.go +++ b/tests/ctx_bridge_test.go @@ -36,6 +36,7 @@ import ( "github.com/ProtonMail/proton-bridge/v3/internal/cookies" "github.com/ProtonMail/proton-bridge/v3/internal/events" frontend "github.com/ProtonMail/proton-bridge/v3/internal/frontend/grpc" + "github.com/ProtonMail/proton-bridge/v3/internal/service" "github.com/ProtonMail/proton-bridge/v3/internal/useragent" "github.com/ProtonMail/proton-bridge/v3/internal/vault" "github.com/sirupsen/logrus" @@ -262,7 +263,7 @@ func (t *testCtx) initFrontendClient() error { return fmt.Errorf("could not read grpcServerConfig.json: %w", err) } - var cfg frontend.Config + var cfg service.Config if err := json.Unmarshal(b, &cfg); err != nil { return fmt.Errorf("could not unmarshal grpcServerConfig.json: %w", err)