mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-17 07:36:44 +00:00
GODT-1917: gRPC service should use random port.
WIP: bridge-gui wait and parse gRPC service config fie.
This commit is contained in:
@ -160,6 +160,7 @@ func main(b *base.Base, c *cli.Context) error { //nolint:funlen
|
|||||||
b.Updater,
|
b.Updater,
|
||||||
bridge,
|
bridge,
|
||||||
b,
|
b,
|
||||||
|
b.Locations,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Watch for updates routine
|
// Watch for updates routine
|
||||||
|
|||||||
@ -37,9 +37,9 @@ QMLBackend::QMLBackend()
|
|||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
//
|
/// \param[in] serviceConfig
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
void QMLBackend::init()
|
void QMLBackend::init(GRPCConfig const &serviceConfig)
|
||||||
{
|
{
|
||||||
users_ = new UserList(this);
|
users_ = new UserList(this);
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ void QMLBackend::init()
|
|||||||
this->connectGrpcEvents();
|
this->connectGrpcEvents();
|
||||||
|
|
||||||
QString error;
|
QString error;
|
||||||
if (app().grpc().connectToServer(error))
|
if (app().grpc().connectToServer(serviceConfig, error))
|
||||||
app().log().info("Connected to backend via gRPC service.");
|
app().log().info("Connected to backend via gRPC service.");
|
||||||
else
|
else
|
||||||
throw Exception(QString("Cannot connectToServer to go backend via gRPC: %1").arg(error));
|
throw Exception(QString("Cannot connectToServer to go backend via gRPC: %1").arg(error));
|
||||||
|
|||||||
@ -42,7 +42,7 @@ public: // member functions.
|
|||||||
~QMLBackend() override = default; ///< Destructor.
|
~QMLBackend() override = default; ///< Destructor.
|
||||||
QMLBackend &operator=(QMLBackend const &) = delete; ///< Disabled assignment operator.
|
QMLBackend &operator=(QMLBackend const &) = delete; ///< Disabled assignment operator.
|
||||||
QMLBackend &operator=(QMLBackend &&) = delete; ///< Disabled move assignment operator.
|
QMLBackend &operator=(QMLBackend &&) = delete; ///< Disabled move assignment operator.
|
||||||
void init(); ///< Initialize the backend.
|
void init(GRPCConfig const &serviceConfig); ///< Initialize the backend.
|
||||||
bool waitForEventStreamReaderToFinish(qint32 timeoutMs); ///< Wait for the event stream reader to finish.
|
bool waitForEventStreamReaderToFinish(qint32 timeoutMs); ///< Wait for the event stream reader to finish.
|
||||||
|
|
||||||
// invokable methods can be called from QML. They generally return a value, which slots cannot do.
|
// invokable methods can be called from QML. They generally return a value, which slots cannot do.
|
||||||
|
|||||||
@ -41,6 +41,7 @@ namespace
|
|||||||
|
|
||||||
QString const bridgeLock = "bridge-gui.lock"; ///< file name used for the lock file.
|
QString const bridgeLock = "bridge-gui.lock"; ///< file name used for the lock file.
|
||||||
QString const exeName = "bridge" + exeSuffix; ///< The bridge executable file name.*
|
QString const exeName = "bridge" + exeSuffix; ///< The bridge executable file name.*
|
||||||
|
qint64 const grpcServiceConfigWaitDelayMs = 60000; ///< The wait delay for the gRPC config file in milliseconds.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -261,9 +262,16 @@ int main(int argc, char *argv[])
|
|||||||
log.setLevel(logLevel);
|
log.setLevel(logLevel);
|
||||||
|
|
||||||
if (!attach)
|
if (!attach)
|
||||||
|
{
|
||||||
|
// before launching bridge, we remove any trailing service config file, because we need to make sure we get a newly generated one.
|
||||||
|
GRPCClient::removeServiceConfigFile();
|
||||||
launchBridge(args);
|
launchBridge(args);
|
||||||
|
}
|
||||||
|
|
||||||
app().backend().init();
|
app().backend().init(GRPCClient::waitAndRetrieveServiceConfig(attach ? 0 : grpcServiceConfigWaitDelayMs));
|
||||||
|
|
||||||
|
if (!attach)
|
||||||
|
GRPCClient::removeServiceConfigFile();
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
std::unique_ptr<QQmlComponent> rootComponent(createRootQmlComponent(engine));
|
std::unique_ptr<QQmlComponent> rootComponent(createRootQmlComponent(engine));
|
||||||
|
|||||||
@ -107,13 +107,13 @@ add_library(bridgepp
|
|||||||
bridgepp/Exception/Exception.h bridgepp/Exception/Exception.cpp
|
bridgepp/Exception/Exception.h bridgepp/Exception/Exception.cpp
|
||||||
bridgepp/GRPC/GRPCClient.cpp bridgepp/GRPC/GRPCClient.h
|
bridgepp/GRPC/GRPCClient.cpp bridgepp/GRPC/GRPCClient.h
|
||||||
bridgepp/GRPC/EventFactory.cpp bridgepp/GRPC/EventFactory.h
|
bridgepp/GRPC/EventFactory.cpp bridgepp/GRPC/EventFactory.h
|
||||||
|
bridgepp/GRPC/GRPCConfig.cpp bridgepp/GRPC/GRPCConfig.h
|
||||||
bridgepp/GRPC/GRPCUtils.cpp bridgepp/GRPC/GRPCUtils.h
|
bridgepp/GRPC/GRPCUtils.cpp bridgepp/GRPC/GRPCUtils.h
|
||||||
${PROTO_CPP_FILE} ${PROTO_H_FILE} ${GRPC_CPP_FILE} ${GRPC_H_FILE}
|
${PROTO_CPP_FILE} ${PROTO_H_FILE} ${GRPC_CPP_FILE} ${GRPC_H_FILE}
|
||||||
bridgepp/Log/Log.h bridgepp/Log/Log.cpp
|
bridgepp/Log/Log.h bridgepp/Log/Log.cpp
|
||||||
bridgepp/ProcessMonitor.cpp bridgepp/ProcessMonitor.h
|
bridgepp/ProcessMonitor.cpp bridgepp/ProcessMonitor.h
|
||||||
bridgepp/User/User.cpp bridgepp/User/User.h
|
bridgepp/User/User.cpp bridgepp/User/User.h
|
||||||
bridgepp/Worker/Worker.h bridgepp/Worker/Overseer.h bridgepp/Worker/Overseer.cpp
|
bridgepp/Worker/Worker.h bridgepp/Worker/Overseer.h bridgepp/Worker/Overseer.cpp)
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(bridgepp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
target_include_directories(bridgepp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
|||||||
@ -43,6 +43,54 @@ int const maxCertificateWaitMsecs = 60 * 1000; ///< Amount of time we wait for h
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
//
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
void GRPCClient::removeServiceConfigFile()
|
||||||
|
{
|
||||||
|
QString const path = serviceConfigPath();
|
||||||
|
if (!QFile(path).exists())
|
||||||
|
return;
|
||||||
|
if (!QFile().remove(path))
|
||||||
|
throw Exception("Could not remove gRPC service config file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \param[in] timeoutMs The timeout in milliseconds
|
||||||
|
/// \return The service config.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
GRPCConfig GRPCClient::waitAndRetrieveServiceConfig(qint64 timeoutMs)
|
||||||
|
{
|
||||||
|
QString const path = serviceConfigPath();
|
||||||
|
QFile file(path);
|
||||||
|
|
||||||
|
QElapsedTimer timer;
|
||||||
|
timer.start();
|
||||||
|
bool found = false;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (file.exists())
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (timer.elapsed() > timeoutMs)
|
||||||
|
break;
|
||||||
|
QThread::msleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
throw Exception("Server did not provide gRPC service configuration in time.");
|
||||||
|
|
||||||
|
GRPCConfig sc;
|
||||||
|
if (!sc.load(path))
|
||||||
|
throw Exception("The gRPC service configuration file is invalid.");
|
||||||
|
|
||||||
|
return sc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \brief wait for certificate generation by Bridge
|
/// \brief wait for certificate generation by Bridge
|
||||||
/// \return server certificate generated by Bridge
|
/// \return server certificate generated by Bridge
|
||||||
@ -110,14 +158,15 @@ void GRPCClient::setLog(Log *log)
|
|||||||
/// \param[out] outError If the function returns false, this variable contains a description of the error.
|
/// \param[out] outError If the function returns false, this variable contains a description of the error.
|
||||||
/// \return true iff the connection was successful.
|
/// \return true iff the connection was successful.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
bool GRPCClient::connectToServer(QString &outError)
|
bool GRPCClient::connectToServer(GRPCConfig const &config, QString &outError)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
SslCredentialsOptions opts;
|
SslCredentialsOptions opts;
|
||||||
opts.pem_root_certs += this->getServerCertificate();
|
opts.pem_root_certs += this->getServerCertificate();
|
||||||
|
|
||||||
channel_ = CreateChannel("127.0.0.1:9292", grpc::SslCredentials(opts));
|
QString const address = QString("127.0.0.1:%1").arg(config.port);
|
||||||
|
channel_ = CreateChannel(address.toStdString(), grpc::SslCredentials(opts));
|
||||||
if (!channel_)
|
if (!channel_)
|
||||||
throw Exception("Channel creation failed.");
|
throw Exception("Channel creation failed.");
|
||||||
|
|
||||||
@ -130,7 +179,7 @@ bool GRPCClient::connectToServer(QString &outError)
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (log_)
|
if (log_)
|
||||||
log_->debug(QString("Connection to gRPC server. attempt #%1").arg(++i));
|
log_->debug(QString("Connection to gRPC server at %1. attempt #%2").arg(address).arg(++i));
|
||||||
|
|
||||||
if (channel_->WaitForConnected(gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), gpr_time_from_seconds(5, GPR_TIMESPAN))))
|
if (channel_->WaitForConnected(gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), gpr_time_from_seconds(5, GPR_TIMESPAN))))
|
||||||
break; // connection established.
|
break; // connection established.
|
||||||
|
|||||||
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
#include "../User/User.h"
|
#include "../User/User.h"
|
||||||
#include "../Log/Log.h"
|
#include "../Log/Log.h"
|
||||||
|
#include "GRPCConfig.h"
|
||||||
#include "bridge.grpc.pb.h"
|
#include "bridge.grpc.pb.h"
|
||||||
#include "grpc++/grpc++.h"
|
#include "grpc++/grpc++.h"
|
||||||
|
|
||||||
@ -46,6 +47,10 @@ typedef grpc::Status (grpc::Bridge::Stub::*StringParamMethod)(grpc::ClientContex
|
|||||||
class GRPCClient : public QObject
|
class GRPCClient : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
public: // static member functions
|
||||||
|
static void removeServiceConfigFile(); ///< Delete the service config file.
|
||||||
|
static GRPCConfig waitAndRetrieveServiceConfig(qint64 timeoutMs); ///< Wait and retrieve the service configuration.
|
||||||
|
|
||||||
public: // member functions.
|
public: // member functions.
|
||||||
GRPCClient() = default; ///< Default constructor.
|
GRPCClient() = default; ///< Default constructor.
|
||||||
GRPCClient(GRPCClient const &) = delete; ///< Disabled copy-constructor.
|
GRPCClient(GRPCClient const &) = delete; ///< Disabled copy-constructor.
|
||||||
@ -54,7 +59,7 @@ public: // member functions.
|
|||||||
GRPCClient &operator=(GRPCClient const &) = delete; ///< Disabled assignment operator.
|
GRPCClient &operator=(GRPCClient const &) = delete; ///< Disabled assignment operator.
|
||||||
GRPCClient &operator=(GRPCClient &&) = delete; ///< Disabled move assignment operator.
|
GRPCClient &operator=(GRPCClient &&) = delete; ///< Disabled move assignment operator.
|
||||||
void setLog(Log *log); ///< Set the log for the client.
|
void setLog(Log *log); ///< Set the log for the client.
|
||||||
bool connectToServer(QString &outError); ///< Establish connection to the gRPC server.
|
bool connectToServer(GRPCConfig const &config, QString &outError); ///< Establish connection to the gRPC server.
|
||||||
|
|
||||||
grpc::Status addLogEntry(Log::Level level, QString const &package, QString const &message); ///< Performs the "AddLogEntry" gRPC call.
|
grpc::Status addLogEntry(Log::Level level, QString const &package, QString const &message); ///< Performs the "AddLogEntry" gRPC call.
|
||||||
grpc::Status guiReady(); ///< performs the "GuiReady" gRPC call.
|
grpc::Status guiReady(); ///< performs the "GuiReady" gRPC call.
|
||||||
@ -218,6 +223,7 @@ private:
|
|||||||
grpc::Status methodWithStringParam(StringParamMethod method, QString const &str); ///< Perform a gRPC call that takes a string as a parameter and returns an Empty.
|
grpc::Status methodWithStringParam(StringParamMethod method, QString const &str); ///< Perform a gRPC call that takes a string as a parameter and returns an Empty.
|
||||||
SPUser parseGRPCUser(grpc::User const &grpcUser); ///< Parse a gRPC user struct and return a User.
|
SPUser parseGRPCUser(grpc::User const &grpcUser); ///< Parse a gRPC user struct and return a User.
|
||||||
|
|
||||||
|
|
||||||
std::string getServerCertificate(); ///< Wait until server certificates is generated and retrieve it.
|
std::string getServerCertificate(); ///< Wait until server certificates is generated and retrieve it.
|
||||||
void processAppEvent(grpc::AppEvent const &event); ///< Process an 'App' event.
|
void processAppEvent(grpc::AppEvent const &event); ///< Process an 'App' event.
|
||||||
void processLoginEvent(grpc::LoginEvent const &event); ///< Process a 'Login' event.
|
void processLoginEvent(grpc::LoginEvent const &event); ///< Process a 'Login' event.
|
||||||
|
|||||||
@ -0,0 +1,133 @@
|
|||||||
|
// Copyright (c) 2022 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#include "GRPCConfig.h"
|
||||||
|
#include "../Exception/Exception.h"
|
||||||
|
|
||||||
|
|
||||||
|
using namespace bridgepp;
|
||||||
|
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
Exception const invalidFileException("The service configuration file is invalid"); // Exception for invalid config.
|
||||||
|
Exception const couldNotSaveException("The service configuration file could not be saved"); ///< Exception for write errors.
|
||||||
|
QString const keyPort = "port"; ///< The JSON key for the port.
|
||||||
|
QString const keyCert = "cert"; ///< The JSON key for the TLS certificate.
|
||||||
|
QString const keyToken = "token"; ///< The JSON key for the identification token.
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \brief read a string value from a JSON object.
|
||||||
|
///
|
||||||
|
/// This function throws an Exception in case of error
|
||||||
|
///
|
||||||
|
/// \param[in] object The JSON object containing the value.
|
||||||
|
/// \param[in] key The key under which the value is stored.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
QString jsonStringValue(QJsonObject const &object, QString const &key)
|
||||||
|
{
|
||||||
|
QJsonValue const v = object[key];
|
||||||
|
if (!v.isString())
|
||||||
|
throw invalidFileException;
|
||||||
|
return v.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \brief read a string value from a JSON object.
|
||||||
|
///
|
||||||
|
/// This function throws an Exception in case of error.
|
||||||
|
///
|
||||||
|
/// \param[in] object The JSON object containing the value.
|
||||||
|
/// \param[in] key The key under which the value is stored.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
qint32 jsonIntValue(QJsonObject const &object, QString const &key)
|
||||||
|
{
|
||||||
|
QJsonValue const v = object[key];
|
||||||
|
if (!v.isDouble())
|
||||||
|
throw invalidFileException;
|
||||||
|
return v.toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \param[in] path The path of the file to load from.
|
||||||
|
/// \param[out] outError if not null and an error occurs, this variable contains a description of the error.
|
||||||
|
/// \return true iff the operation was successful.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
bool GRPCConfig::load(QString const &path, QString *outError)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||||
|
throw Exception("Could not open gRPC service config file.");
|
||||||
|
|
||||||
|
QJsonDocument const doc = QJsonDocument::fromJson(file.readAll());
|
||||||
|
QJsonObject const object = doc.object();
|
||||||
|
port = jsonIntValue(object, keyPort);
|
||||||
|
cert = jsonStringValue(object, keyCert);
|
||||||
|
token = jsonStringValue(object, keyToken);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception const &e)
|
||||||
|
{
|
||||||
|
if (outError)
|
||||||
|
*outError = e.qwhat();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \param[in] path The path of the file to write to.
|
||||||
|
/// \param[out] outError if not null and an error occurs, this variable contains a description of the error.
|
||||||
|
/// \return true iff the operation was successful.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
bool GRPCConfig::save(QString const &path, QString *outError)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
QJsonObject const object;
|
||||||
|
object[keyPort] = port;
|
||||||
|
object[keyCert] = cert;
|
||||||
|
object[keyToken] = token;
|
||||||
|
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||||
|
throw couldNotSaveException;
|
||||||
|
|
||||||
|
QByteArray const array = QJsonDocument(object).toJson();
|
||||||
|
if (array.size() != file.write(array))
|
||||||
|
throw couldNotSaveException;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception const &e)
|
||||||
|
{
|
||||||
|
if (outError)
|
||||||
|
*outError = e.qwhat();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) 2022 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef BRIDGE_PP_GRPC_CONFIG_H
|
||||||
|
#define BRIDGE_PP_GRPC_CONFIG_H
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// Service configuration class.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
struct GRPCConfig
|
||||||
|
{
|
||||||
|
public: // data members
|
||||||
|
qint32 port; ///< The port.
|
||||||
|
QString cert; ///< The server TLS certificate.
|
||||||
|
QString token; ///< The identification token.
|
||||||
|
|
||||||
|
bool load(QString const &path, QString *outError = nullptr); ///< Load the service config from file
|
||||||
|
bool save(QString const &path, QString *outError = nullptr); ///< Save the service config to file
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif //BRIDGE_PP_GRPC_CONFIG_H
|
||||||
@ -38,6 +38,14 @@ QString serverCertificateFilename()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \return the service config file name
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
QString serviceConfigFilename()
|
||||||
|
{
|
||||||
|
return "grpcServiceConfig.json";
|
||||||
|
}
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
//
|
//
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
@ -50,6 +58,15 @@ QString serverKeyFilename()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
/// \return The absolute path of the service config path.
|
||||||
|
//****************************************************************************************************************************************************
|
||||||
|
QString serviceConfigPath()
|
||||||
|
{
|
||||||
|
return QDir(userConfigDir()).absoluteFilePath(serviceConfigFilename());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
/// \return The absolute path of the server certificate.
|
/// \return The absolute path of the server certificate.
|
||||||
//****************************************************************************************************************************************************
|
//****************************************************************************************************************************************************
|
||||||
|
|||||||
@ -31,7 +31,7 @@ namespace bridgepp
|
|||||||
|
|
||||||
typedef std::shared_ptr<grpc::StreamEvent> SPStreamEvent; ///< Type definition for shared pointer to grpc::StreamEvent.
|
typedef std::shared_ptr<grpc::StreamEvent> SPStreamEvent; ///< Type definition for shared pointer to grpc::StreamEvent.
|
||||||
|
|
||||||
|
QString serviceConfigPath(); ///< Return the path of the service config file.
|
||||||
QString serverCertificatePath(); ///< Return the path of the server certificate.
|
QString serverCertificatePath(); ///< Return the path of the server certificate.
|
||||||
QString serverKeyPath(); ///< Return the path of the server key.
|
QString serverKeyPath(); ///< Return the path of the server key.
|
||||||
grpc::LogLevel logLevelToGRPC(Log::Level level); ///< Convert a Log::Level to gRPC enum value.
|
grpc::LogLevel logLevelToGRPC(Log::Level level); ///< Convert a Log::Level to gRPC enum value.
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/cli"
|
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/cli"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/grpc"
|
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/grpc"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/listener"
|
||||||
)
|
)
|
||||||
@ -45,6 +46,7 @@ func New(
|
|||||||
updater types.Updater,
|
updater types.Updater,
|
||||||
bridge *bridge.Bridge,
|
bridge *bridge.Bridge,
|
||||||
restarter types.Restarter,
|
restarter types.Restarter,
|
||||||
|
locations *locations.Locations,
|
||||||
) Frontend {
|
) Frontend {
|
||||||
switch frontendType {
|
switch frontendType {
|
||||||
case "grpc":
|
case "grpc":
|
||||||
@ -55,6 +57,7 @@ func New(
|
|||||||
updater,
|
updater,
|
||||||
bridge,
|
bridge,
|
||||||
restarter,
|
restarter,
|
||||||
|
locations,
|
||||||
)
|
)
|
||||||
|
|
||||||
case "cli":
|
case "cli":
|
||||||
|
|||||||
65
internal/frontend/grpc/config.go
Normal file
65
internal/frontend/grpc/config.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) 2022 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// config is a structure containing the service configuration data that are exchanged by the gRPC server and client.
|
||||||
|
type config struct {
|
||||||
|
Port int `json:"port"`
|
||||||
|
Cert string `json:"cert"` // coming soon
|
||||||
|
Token string `json:"token"` // coming soon
|
||||||
|
}
|
||||||
|
|
||||||
|
// save saves a gRPC service configuration to file.
|
||||||
|
func (s *config) save(path string) error {
|
||||||
|
// Another process may be waiting for this file to be available. In order to prevent this process to open
|
||||||
|
// the file while we are writing in it, we write it with a temp file name, then rename it.
|
||||||
|
tempPath := path + "_"
|
||||||
|
if err := s._save(tempPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Rename(tempPath, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *config) _save(path string) error {
|
||||||
|
f, err := os.Create(path) //nolint:errcheck,gosec
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
|
return json.NewEncoder(f).Encode(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
|
return json.NewDecoder(f).Decode(s)
|
||||||
|
}
|
||||||
55
internal/frontend/grpc/config_test.go
Normal file
55
internal/frontend/grpc/config_test.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) 2022 Proton AG
|
||||||
|
//
|
||||||
|
// This file is part of Proton Mail 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package grpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dummyPort = 12
|
||||||
|
dummyCert = "A dummy cert"
|
||||||
|
dummyToken = "A dummy token"
|
||||||
|
tempFileName = "test.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfig(t *testing.T) {
|
||||||
|
conf1 := config{
|
||||||
|
Port: dummyPort,
|
||||||
|
Cert: dummyCert,
|
||||||
|
Token: dummyToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read-back test
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
tempFilePath := filepath.Join(tempDir, tempFileName)
|
||||||
|
require.NoError(t, conf1.save(tempFilePath))
|
||||||
|
|
||||||
|
conf2 := config{}
|
||||||
|
require.NoError(t, conf2.load(tempFilePath))
|
||||||
|
require.Equal(t, conf1, conf2)
|
||||||
|
|
||||||
|
// failure to load
|
||||||
|
require.Error(t, conf2.load(tempFilePath+"_"))
|
||||||
|
|
||||||
|
// failure to save
|
||||||
|
require.Error(t, conf2.save(filepath.Join(tempDir, "non/existing/folder", tempFileName)))
|
||||||
|
}
|
||||||
@ -22,7 +22,9 @@ package grpc
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
cryptotls "crypto/tls"
|
cryptotls "crypto/tls"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -31,6 +33,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/users"
|
"github.com/ProtonMail/proton-bridge/v2/internal/users"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
|
||||||
@ -43,6 +46,10 @@ import (
|
|||||||
"google.golang.org/protobuf/types/known/emptypb"
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
serviceConfigFileName = "grpcServiceConfig.json"
|
||||||
|
)
|
||||||
|
|
||||||
// Service is the RPC service struct.
|
// Service is the RPC service struct.
|
||||||
type Service struct { // nolint:structcheck
|
type Service struct { // nolint:structcheck
|
||||||
UnimplementedBridgeServer
|
UnimplementedBridgeServer
|
||||||
@ -68,6 +75,7 @@ type Service struct { // nolint:structcheck
|
|||||||
initializing sync.WaitGroup
|
initializing sync.WaitGroup
|
||||||
initializationDone sync.Once
|
initializationDone sync.Once
|
||||||
firstTimeAutostart sync.Once
|
firstTimeAutostart sync.Once
|
||||||
|
locations *locations.Locations
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService returns a new instance of the service.
|
// NewService returns a new instance of the service.
|
||||||
@ -78,6 +86,7 @@ func NewService(
|
|||||||
updater types.Updater,
|
updater types.Updater,
|
||||||
bridge types.Bridger,
|
bridge types.Bridger,
|
||||||
restarter types.Restarter,
|
restarter types.Restarter,
|
||||||
|
locations *locations.Locations,
|
||||||
) *Service {
|
) *Service {
|
||||||
s := Service{
|
s := Service{
|
||||||
UnimplementedBridgeServer: UnimplementedBridgeServer{},
|
UnimplementedBridgeServer: UnimplementedBridgeServer{},
|
||||||
@ -92,6 +101,7 @@ func NewService(
|
|||||||
initializing: sync.WaitGroup{},
|
initializing: sync.WaitGroup{},
|
||||||
initializationDone: sync.Once{},
|
initializationDone: sync.Once{},
|
||||||
firstTimeAutostart: sync.Once{},
|
firstTimeAutostart: sync.Once{},
|
||||||
|
locations: locations,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initializing.Done is only called sync.Once. Please keep the increment
|
// Initializing.Done is only called sync.Once. Please keep the increment
|
||||||
@ -111,12 +121,18 @@ func NewService(
|
|||||||
|
|
||||||
RegisterBridgeServer(s.grpcServer, &s)
|
RegisterBridgeServer(s.grpcServer, &s)
|
||||||
|
|
||||||
s.listener, err = net.Listen("tcp", "127.0.0.1:9292") // Port should be configurable from the command-line.
|
s.listener, err = net.Listen("tcp", "127.0.0.1:0") // Port 0 means that the port is randomly picked by the system.
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.WithError(err).Error("could not create listener")
|
s.log.WithError(err).Panic("could not create listener")
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := s.saveGRPCServerConfigFile(); err != nil {
|
||||||
|
s.log.WithError(err).Panic("could not write gRPC service configuration file")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.Info("gRPC server listening at ", s.listener.Addr())
|
||||||
|
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,3 +403,19 @@ func (s *Service) installUpdate() {
|
|||||||
|
|
||||||
_ = s.SendEvent(NewUpdateSilentRestartNeededEvent())
|
_ = s.SendEvent(NewUpdateSilentRestartNeededEvent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) saveGRPCServerConfigFile() error {
|
||||||
|
address, ok := s.listener.Addr().(*net.TCPAddr)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("could not retrieve gRPC service listener address")
|
||||||
|
}
|
||||||
|
|
||||||
|
sc := config{Port: address.Port}
|
||||||
|
|
||||||
|
settingsPath, err := s.locations.ProvideSettingsPath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return sc.save(filepath.Join(settingsPath, serviceConfigFileName))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user