Implement basic functionality of JSON RPC server

This commit is contained in:
Simon Stürz 2018-08-09 14:19:40 +02:00
parent f20173df9f
commit de6c757a26
42 changed files with 1044 additions and 187 deletions

View File

@ -46,12 +46,11 @@ In order to run the test, you can call `make check` in the build directory or ru
If you want to create a line coverage report from the tests simply run following command in the source directory: If you want to create a line coverage report from the tests simply run following command in the source directory:
$ apt install lcov $ apt install lcov gcovr
$ ./create-coverage-html.sh $ ./create-coverage-html.sh
The resulting coverage report will be place in the `coverage-html` directory. The resulting coverage report will be place in the `coverage-html` directory.
# Usage # Usage
In order to get information about the server you can start the command with the `--help` parameter. In order to get information about the server you can start the command with the `--help` parameter.

View File

@ -1,22 +1,18 @@
#include "authenticationreply.h" #include "authenticationreply.h"
#include "authentication/authenticator.h" #include "authentication/authenticator.h"
AuthenticationReply::AuthenticationReply(const QUuid clientId, const QString &token, QObject *parent) : namespace remoteproxy {
AuthenticationReply::AuthenticationReply(ProxyClient *proxyClient, QObject *parent) :
QObject(parent), QObject(parent),
m_clientId(clientId), m_proxyClient(proxyClient)
m_token(token)
{ {
} }
QUuid AuthenticationReply::clientId() const ProxyClient *AuthenticationReply::proxyClient() const
{ {
return m_clientId; return m_proxyClient;
}
QString AuthenticationReply::token() const
{
return m_token;
} }
bool AuthenticationReply::isTimedOut() const bool AuthenticationReply::isTimedOut() const
@ -29,18 +25,32 @@ bool AuthenticationReply::isFinished() const
return m_finished; return m_finished;
} }
Authenticator::AuthenticationError AuthenticationReply::error() const
{
return m_error;
}
void AuthenticationReply::setError(Authenticator::AuthenticationError error) void AuthenticationReply::setError(Authenticator::AuthenticationError error)
{ {
m_error = error; m_error = error;
} }
void AuthenticationReply::setFinished()
{
emit finished();
}
void AuthenticationReply::onTimeout() void AuthenticationReply::onTimeout()
{ {
m_timedOut = true;
m_error = Authenticator::AuthenticationErrorTimeout;
emit finished();
} }
void AuthenticationReply::abort() void AuthenticationReply::abort()
{ {
m_error = Authenticator::AuthenticationErrorAborted;
emit finished();
}
} }

View File

@ -8,16 +8,15 @@
#include "authenticator.h" #include "authenticator.h"
namespace remoteproxy {
class AuthenticationReply : public QObject class AuthenticationReply : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
friend class Authenticator; friend class Authenticator;
explicit AuthenticationReply(const QUuid clientId, const QString &token, QObject *parent = nullptr); ProxyClient *proxyClient() const;
QUuid clientId() const;
QString token() const;
bool isTimedOut() const; bool isTimedOut() const;
bool isFinished() const; bool isFinished() const;
@ -25,15 +24,18 @@ public:
Authenticator::AuthenticationError error() const; Authenticator::AuthenticationError error() const;
private: private:
QUuid m_clientId; explicit AuthenticationReply(ProxyClient *proxyClient, QObject *parent = nullptr);
QString m_token;
ProxyClient *m_proxyClient = nullptr;
QTimer m_timer; QTimer m_timer;
bool m_timedOut = false; bool m_timedOut = false;
bool m_finished = false; bool m_finished = false;
Authenticator::AuthenticationError m_error;
Authenticator::AuthenticationError m_error = Authenticator::AuthenticationErrorUnknown;
void setError(Authenticator::AuthenticationError error); void setError(Authenticator::AuthenticationError error);
void setFinished();
signals: signals:
void finished(); void finished();
@ -46,4 +48,6 @@ public slots:
}; };
}
#endif // AUTHENTICATIONREPLY_H #endif // AUTHENTICATIONREPLY_H

View File

@ -1,13 +1,33 @@
#include "proxyclient.h"
#include "authenticator.h" #include "authenticator.h"
#include "authenticationreply.h" #include "authenticationreply.h"
namespace remoteproxy {
Authenticator::Authenticator(QObject *parent) : Authenticator::Authenticator(QObject *parent) :
QObject(parent) QObject(parent)
{ {
} }
void Authenticator::setReplyError(AuthenticationReply *reply, Authenticator::AuthenticationError error)
{
reply->setError(error);
}
void Authenticator::setReplyFinished(AuthenticationReply *reply)
{
reply->setFinished();
}
AuthenticationReply *Authenticator::createAuthenticationReply(ProxyClient *proxyClient, QObject *parent)
{
return new AuthenticationReply(proxyClient, parent);
}
Authenticator::~Authenticator() Authenticator::~Authenticator()
{ {
} }
}

View File

@ -4,6 +4,9 @@
#include <QUuid> #include <QUuid>
#include <QObject> #include <QObject>
namespace remoteproxy {
class ProxyClient;
class AuthenticationReply; class AuthenticationReply;
class Authenticator : public QObject class Authenticator : public QObject
@ -12,6 +15,7 @@ class Authenticator : public QObject
public: public:
enum AuthenticationError { enum AuthenticationError {
AuthenticationErrorNoError, AuthenticationErrorNoError,
AuthenticationErrorUnknown,
AuthenticationErrorTimeout, AuthenticationErrorTimeout,
AuthenticationErrorAborted, AuthenticationErrorAborted,
AuthenticationErrorAuthenticationFailed, AuthenticationErrorAuthenticationFailed,
@ -22,8 +26,18 @@ public:
explicit Authenticator(QObject *parent = nullptr); explicit Authenticator(QObject *parent = nullptr);
virtual ~Authenticator() = 0; virtual ~Authenticator() = 0;
virtual QString name() const = 0;
public slots: public slots:
virtual AuthenticationReply *authenticate(const QUuid &clientId, const QString &token) = 0; virtual AuthenticationReply *authenticate(ProxyClient *proxyClient) = 0;
protected:
void setReplyError(AuthenticationReply *reply, AuthenticationError error);
void setReplyFinished(AuthenticationReply *reply);
AuthenticationReply *createAuthenticationReply(ProxyClient *proxyClient, QObject *parent = nullptr);
}; };
}
#endif // AUTHENTICATOR_H #endif // AUTHENTICATOR_H

View File

@ -1,17 +1,25 @@
#include "proxyclient.h"
#include "awsauthenticator.h" #include "awsauthenticator.h"
#include "loggingcategories.h" #include "loggingcategories.h"
namespace remoteproxy {
AwsAuthenticator::AwsAuthenticator(QObject *parent) : AwsAuthenticator::AwsAuthenticator(QObject *parent) :
Authenticator(parent) Authenticator(parent)
{ {
} }
AuthenticationReply *AwsAuthenticator::authenticate(const QUuid &clientId, const QString &token) QString AwsAuthenticator::name() const
{ {
qCDebug(dcAuthenticator()) << "Start authenticating" << clientId << "using token" << token; return "AWS authenticator";
}
AuthenticationReply *reply = new AuthenticationReply(clientId, token, this);
AuthenticationReply *AwsAuthenticator::authenticate(ProxyClient *proxyClient)
{
qCDebug(dcAuthenticator()) << name() << "Start authenticating" << proxyClient << "using token" << proxyClient->token();
AuthenticationReply *reply = createAuthenticationReply(proxyClient, this);
return reply; return reply;
} }
}

View File

@ -6,6 +6,8 @@
#include "authenticator.h" #include "authenticator.h"
#include "authenticationreply.h" #include "authenticationreply.h"
namespace remoteproxy {
class AwsAuthenticator : public Authenticator class AwsAuthenticator : public Authenticator
{ {
Q_OBJECT Q_OBJECT
@ -13,9 +15,13 @@ public:
explicit AwsAuthenticator(QObject *parent = nullptr); explicit AwsAuthenticator(QObject *parent = nullptr);
~AwsAuthenticator() override = default; ~AwsAuthenticator() override = default;
QString name() const override;
public slots: public slots:
AuthenticationReply *authenticate(const QUuid &clientId, const QString &token) override; AuthenticationReply *authenticate(ProxyClient *proxyClient) override;
}; };
}
#endif // AWSAUTHENTICATOR_H #endif // AWSAUTHENTICATOR_H

View File

@ -1,6 +1,8 @@
#include "engine.h" #include "engine.h"
#include "loggingcategories.h" #include "loggingcategories.h"
namespace remoteproxy {
Engine *Engine::s_instance = nullptr; Engine *Engine::s_instance = nullptr;
Engine *Engine::instance() Engine *Engine::instance()
@ -73,12 +75,12 @@ void Engine::stop()
if (m_proxyServer) { if (m_proxyServer) {
m_proxyServer->stopServer(); m_proxyServer->stopServer();
m_proxyServer->deleteLater(); delete m_proxyServer;
m_proxyServer = nullptr; m_proxyServer = nullptr;
} }
if (m_webSocketServer) { if (m_webSocketServer) {
m_webSocketServer->deleteLater(); delete m_webSocketServer;
m_webSocketServer = nullptr; m_webSocketServer = nullptr;
} }
@ -114,7 +116,7 @@ void Engine::setWebSocketServerPort(const quint16 &port)
void Engine::setSslConfiguration(const QSslConfiguration &configuration) void Engine::setSslConfiguration(const QSslConfiguration &configuration)
{ {
qCDebug(dcEngine()) << "SSL Configuration:"; qCDebug(dcEngine()) << "SSL certificate information:";
qCDebug(dcEngine()) << " Common name:" << configuration.localCertificate().issuerInfo(QSslCertificate::CommonName); qCDebug(dcEngine()) << " Common name:" << configuration.localCertificate().issuerInfo(QSslCertificate::CommonName);
qCDebug(dcEngine()) << " Organisation:" << configuration.localCertificate().issuerInfo(QSslCertificate::Organization); qCDebug(dcEngine()) << " Organisation:" << configuration.localCertificate().issuerInfo(QSslCertificate::Organization);
qCDebug(dcEngine()) << " Organisation unit name:" << configuration.localCertificate().issuerInfo(QSslCertificate::OrganizationalUnitName); qCDebug(dcEngine()) << " Organisation unit name:" << configuration.localCertificate().issuerInfo(QSslCertificate::OrganizationalUnitName);
@ -134,18 +136,18 @@ void Engine::setAuthenticationServerUrl(const QUrl &url)
void Engine::setAuthenticator(Authenticator *authenticator) void Engine::setAuthenticator(Authenticator *authenticator)
{ {
if (m_authenticator == authenticator)
return;
if (m_authenticator) { if (m_authenticator) {
qCDebug(dcEngine()) << "There is already an authenticator registered. Unregister default authenticator"; qCDebug(dcEngine()) << "There is already an authenticator registered. Unregister default authenticator";
m_authenticator->deleteLater();
m_authenticator = nullptr; m_authenticator = nullptr;
// FIXME: do unconnect // FIXME: do unconnect
} }
m_authenticator = authenticator; m_authenticator = authenticator;
// FIXME: connect
} }
Authenticator *Engine::authenticator() const Authenticator *Engine::authenticator() const
@ -183,3 +185,5 @@ void Engine::setRunning(bool running)
m_running = running; m_running = running;
emit runningChanged(m_running); emit runningChanged(m_running);
} }
}

View File

@ -10,6 +10,8 @@
#include "websocketserver.h" #include "websocketserver.h"
#include "authentication/authenticator.h" #include "authentication/authenticator.h"
namespace remoteproxy {
class Engine : public QObject class Engine : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -62,4 +64,6 @@ signals:
}; };
}
#endif // ENGINE_H #endif // ENGINE_H

View File

@ -4,6 +4,8 @@
#include "engine.h" #include "engine.h"
namespace remoteproxy {
AuthenticationHandler::AuthenticationHandler(QObject *parent) : AuthenticationHandler::AuthenticationHandler(QObject *parent) :
JsonHandler(parent) JsonHandler(parent)
{ {
@ -11,31 +13,34 @@ AuthenticationHandler::AuthenticationHandler(QObject *parent) :
QVariantMap params; QVariantMap returns; QVariantMap params; QVariantMap returns;
setDescription("Authenticate", "Authenticate this connection. This should always be the first request to the server. The given id is the unique server/client uuid (i.e. the uuid of server/client)."); setDescription("Authenticate", "Authenticate this connection. This should always be the first request to the server. The given id is the unique server/client uuid (i.e. the uuid of server/client).");
params.insert("id", JsonTypes::basicTypeToString(JsonTypes::Uuid)); params.insert("uuid", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("name", JsonTypes::basicTypeToString(JsonTypes::String)); params.insert("name", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("token", JsonTypes::basicTypeToString(JsonTypes::String)); params.insert("token", JsonTypes::basicTypeToString(JsonTypes::String));
setParams("Authenticate", params); setParams("Authenticate", params);
returns.insert("authenticationError", JsonTypes::authenticationErrorRef()); returns.insert("authenticationError", JsonTypes::authenticationErrorRef());
setReturns("Authenticate", returns); setReturns("Authenticate", returns);
} }
QString AuthenticationHandler::name() const QString AuthenticationHandler::name() const
{ {
return "Authentication"; return "Authentication";
} }
JsonReply *AuthenticationHandler::Authenticate(const QVariantMap &params, const QUuid &clientId) JsonReply *AuthenticationHandler::Authenticate(const QVariantMap &params, ProxyClient *proxyClient)
{ {
qCDebug(dcJsonRpc()) << "Authenticate:" << clientId.toString(); QString uuid = params.value("uuid").toString();
QString name = params.value("name").toString();
QString clientName = params.value("name").toString(); QString token = params.value("token").toString();
QString clientToken = params.value("token").toString();
QUuid clientDeviceId = QUuid(params.value("id").toString());
Q_UNUSED(clientDeviceId);
qCDebug(dcJsonRpc()) << "Authenticate:" << name << uuid << token;
JsonReply *jsonReply = createAsyncReply("Authenticate"); JsonReply *jsonReply = createAsyncReply("Authenticate");
AuthenticationReply *authReply = Engine::instance()->authenticator()->authenticate(clientId, clientToken);
// Set the token for this proxy client
proxyClient->setUuid(uuid);
proxyClient->setName(name);
proxyClient->setToken(token);
AuthenticationReply *authReply = Engine::instance()->authenticator()->authenticate(proxyClient);
connect(authReply, &AuthenticationReply::finished, this, &AuthenticationHandler::onAuthenticationFinished); connect(authReply, &AuthenticationReply::finished, this, &AuthenticationHandler::onAuthenticationFinished);
m_runningAuthentications.insert(authReply, jsonReply); m_runningAuthentications.insert(authReply, jsonReply);
@ -45,8 +50,22 @@ JsonReply *AuthenticationHandler::Authenticate(const QVariantMap &params, const
void AuthenticationHandler::onAuthenticationFinished() void AuthenticationHandler::onAuthenticationFinished()
{ {
//AuthenticationReply *authReply = static_cast<AuthenticationReply *>(sender()); AuthenticationReply *authenticationReply = static_cast<AuthenticationReply *>(sender());
//JsonReply *jsonReply = m_runningAuthentications.take(authReply); JsonReply *jsonReply = m_runningAuthentications.take(authenticationReply);
authenticationReply->deleteLater();
qCDebug(dcJsonRpc()) << "Authentication respons ready for" << authenticationReply->proxyClient() << authenticationReply->error();
if (authenticationReply->error() != Authenticator::AuthenticationErrorNoError) {
qCWarning(dcJsonRpc()) << "Authentication error occured" << authenticationReply->error();
jsonReply->setSuccess(true);
} else {
jsonReply->setSuccess(false);
}
jsonReply->setData(errorToReply(authenticationReply->error()));
jsonReply->finished();
}
//emit asyncReply()
} }

View File

@ -6,6 +6,8 @@
#include "jsonhandler.h" #include "jsonhandler.h"
#include "authentication/authenticationreply.h" #include "authentication/authenticationreply.h"
namespace remoteproxy {
class AuthenticationHandler : public JsonHandler class AuthenticationHandler : public JsonHandler
{ {
Q_OBJECT Q_OBJECT
@ -15,7 +17,7 @@ public:
QString name() const override; QString name() const override;
Q_INVOKABLE JsonReply *Authenticate(const QVariantMap &params, const QUuid &clientId); Q_INVOKABLE JsonReply *Authenticate(const QVariantMap &params, ProxyClient *proxyClient);
private: private:
QHash<AuthenticationReply *, JsonReply *> m_runningAuthentications; QHash<AuthenticationReply *, JsonReply *> m_runningAuthentications;
@ -26,4 +28,6 @@ private slots:
}; };
}
#endif // AUTHENTICATIONHANDLER_H #endif // AUTHENTICATIONHANDLER_H

View File

@ -7,6 +7,8 @@
#include "jsontypes.h" #include "jsontypes.h"
#include "loggingcategories.h" #include "loggingcategories.h"
namespace remoteproxy {
JsonHandler::JsonHandler(QObject *parent): JsonHandler::JsonHandler(QObject *parent):
QObject(parent) QObject(parent)
{ {
@ -106,6 +108,13 @@ void JsonHandler::setReturns(const QString &methodName, const QVariantMap &retur
qCWarning(dcJsonRpc()) << "Cannot set returns. No such method:" << methodName; qCWarning(dcJsonRpc()) << "Cannot set returns. No such method:" << methodName;
} }
QVariantMap JsonHandler::errorToReply(Authenticator::AuthenticationError error) const
{
QVariantMap returns;
returns.insert("authenticationError", JsonTypes::authenticationErrorToString(error));
return returns;
}
JsonReply *JsonHandler::createReply(const QVariantMap &data) const JsonReply *JsonHandler::createReply(const QVariantMap &data) const
{ {
return JsonReply::createReply(const_cast<JsonHandler*>(this), data); return JsonReply::createReply(const_cast<JsonHandler*>(this), data);
@ -115,3 +124,5 @@ JsonReply *JsonHandler::createAsyncReply(const QString &method) const
{ {
return JsonReply::createAsyncReply(const_cast<JsonHandler*>(this), method); return JsonReply::createAsyncReply(const_cast<JsonHandler*>(this), method);
} }
}

View File

@ -7,6 +7,10 @@
#include <QVariantMap> #include <QVariantMap>
#include <QMetaMethod> #include <QMetaMethod>
#include "authentication/authenticator.h"
namespace remoteproxy {
class JsonReply; class JsonReply;
class JsonHandler : public QObject class JsonHandler : public QObject
@ -36,8 +40,14 @@ protected:
void setParams(const QString &methodName, const QVariantMap &params); void setParams(const QString &methodName, const QVariantMap &params);
void setReturns(const QString &methodName, const QVariantMap &returns); void setReturns(const QString &methodName, const QVariantMap &returns);
QVariantMap errorToReply(Authenticator::AuthenticationError error) const;
JsonReply *createReply(const QVariantMap &data) const; JsonReply *createReply(const QVariantMap &data) const;
JsonReply *createAsyncReply(const QString &method) const; JsonReply *createAsyncReply(const QString &method) const;
}; };
}
#endif // JSONHANDLER_H #endif // JSONHANDLER_H

View File

@ -1,5 +1,7 @@
#include "jsonreply.h" #include "jsonreply.h"
namespace remoteproxy {
JsonReply *JsonReply::createReply(JsonHandler *handler, const QVariantMap &data) JsonReply *JsonReply::createReply(JsonHandler *handler, const QVariantMap &data)
{ {
return new JsonReply(TypeSync, handler, QString(), data); return new JsonReply(TypeSync, handler, QString(), data);
@ -35,14 +37,14 @@ QString JsonReply::method() const
return m_method; return m_method;
} }
QUuid JsonReply::connectionId() const QUuid JsonReply::clientId() const
{ {
return m_connectionId; return m_clientId;
} }
void JsonReply::setClientId(const QUuid &connectionId) void JsonReply::setClientId(const QUuid &clientId)
{ {
m_connectionId = connectionId; m_clientId = clientId;
} }
int JsonReply::commandId() const int JsonReply::commandId() const
@ -55,6 +57,16 @@ void JsonReply::setCommandId(int commandId)
m_commandId = commandId; m_commandId = commandId;
} }
bool JsonReply::success() const
{
return m_success;
}
void JsonReply::setSuccess(bool success)
{
m_success = success;
}
bool JsonReply::timedOut() const bool JsonReply::timedOut() const
{ {
return m_timedOut; return m_timedOut;
@ -71,12 +83,16 @@ void JsonReply::timeout()
emit finished(); emit finished();
} }
JsonReply::JsonReply(JsonReply::Type type, JsonHandler *handler, const QString &method, const QVariantMap &data): JsonReply::JsonReply(JsonReply::Type type, JsonHandler *handler, const QString &method, const QVariantMap &data, bool success):
m_type(type), m_type(type),
m_data(data), m_data(data),
m_handler(handler), m_handler(handler),
m_method(method), m_method(method),
m_timedOut(false) m_success(success)
{ {
connect(&m_timeout, &QTimer::timeout, this, &JsonReply::timeout); connect(&m_timeout, &QTimer::timeout, this, &JsonReply::timeout);
} }
}

View File

@ -7,6 +7,8 @@
#include "jsonhandler.h" #include "jsonhandler.h"
namespace remoteproxy {
class JsonReply: public QObject class JsonReply: public QObject
{ {
Q_OBJECT Q_OBJECT
@ -16,22 +18,29 @@ public:
TypeAsync TypeAsync
}; };
friend class JsonRpcServer;
static JsonReply *createReply(JsonHandler *handler, const QVariantMap &data); static JsonReply *createReply(JsonHandler *handler, const QVariantMap &data);
static JsonReply *createAsyncReply(JsonHandler *handler, const QString &method); static JsonReply *createAsyncReply(JsonHandler *handler, const QString &method);
Type type() const; Type type() const;
QVariantMap data() const; QVariantMap data() const;
void setData(const QVariantMap &data); void setData(const QVariantMap &data);
JsonHandler *handler() const; JsonHandler *handler() const;
QString method() const; QString method() const;
QUuid connectionId() const; QUuid clientId() const;
void setClientId(const QUuid &connectionId); void setClientId(const QUuid &clientId);
int commandId() const; int commandId() const;
void setCommandId(int commandId); void setCommandId(int commandId);
bool success() const;
void setSuccess(bool success);
bool timedOut() const; bool timedOut() const;
public slots: public slots:
@ -44,17 +53,22 @@ private slots:
void timeout(); void timeout();
private: private:
JsonReply(Type type, JsonHandler *handler, const QString &method, const QVariantMap &data = QVariantMap()); JsonReply(Type type, JsonHandler *handler, const QString &method, const QVariantMap &data = QVariantMap(), bool success = true);
Type m_type;
Type m_type = TypeSync;
QVariantMap m_data; QVariantMap m_data;
JsonHandler *m_handler; JsonHandler *m_handler = nullptr;
QString m_method; QString m_method;
QUuid m_connectionId; QUuid m_clientId;
int m_commandId; int m_commandId;
bool m_timedOut; bool m_timedOut = false;
bool m_success = false;
QTimer m_timeout; QTimer m_timeout;
}; };
}
#endif // JSONRPCREPLY_H #endif // JSONRPCREPLY_H

View File

@ -5,6 +5,8 @@
#include "loggingcategories.h" #include "loggingcategories.h"
namespace remoteproxy {
bool JsonTypes::s_initialized = false; bool JsonTypes::s_initialized = false;
QString JsonTypes::s_lastError; QString JsonTypes::s_lastError;
@ -198,6 +200,9 @@ QPair<bool, QString> JsonTypes::validateList(const QVariantList &templateList, c
QPair<bool, QString> JsonTypes::validateBasicType(const QVariant &variant) QPair<bool, QString> JsonTypes::validateBasicType(const QVariant &variant)
{ {
if (variant.canConvert(QVariant::Uuid) && QVariant(variant).convert(QVariant::Uuid)) { if (variant.canConvert(QVariant::Uuid) && QVariant(variant).convert(QVariant::Uuid)) {
if (QUuid(variant.toString()).isNull()) {
return report(false, "Invalid uuid format.");
}
return report(true, ""); return report(true, "");
} }
if (variant.canConvert(QVariant::String) && QVariant(variant).convert(QVariant::String)) { if (variant.canConvert(QVariant::String) && QVariant(variant).convert(QVariant::String)) {
@ -259,3 +264,5 @@ QVariantList JsonTypes::enumToStrings(const QMetaObject &metaObject, const QStri
} }
return enumStrings; return enumStrings;
} }
}

View File

@ -8,6 +8,8 @@
#include "authentication/authenticator.h" #include "authentication/authenticator.h"
namespace remoteproxy {
#define DECLARE_OBJECT(typeName, jsonName) \ #define DECLARE_OBJECT(typeName, jsonName) \
public: \ public: \
static QString typeName##Ref() { return QStringLiteral("$ref:") + QStringLiteral(jsonName); if (!s_initialized) { init(); } } \ static QString typeName##Ref() { return QStringLiteral("$ref:") + QStringLiteral(jsonName); if (!s_initialized) { init(); } } \
@ -87,4 +89,6 @@ private:
static QVariantList enumToStrings(const QMetaObject &metaObject, const QString &enumName); static QVariantList enumToStrings(const QMetaObject &metaObject, const QString &enumName);
}; };
}
#endif // JSONTYPES_H #endif // JSONTYPES_H

View File

@ -4,8 +4,11 @@
#include "jsonrpc/jsontypes.h" #include "jsonrpc/jsontypes.h"
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonParseError>
#include <QCoreApplication> #include <QCoreApplication>
namespace remoteproxy {
JsonRpcServer::JsonRpcServer(QObject *parent) : JsonRpcServer::JsonRpcServer(QObject *parent) :
JsonHandler(parent) JsonHandler(parent)
{ {
@ -13,7 +16,8 @@ JsonRpcServer::JsonRpcServer(QObject *parent) :
QVariantMap params; QVariantMap returns; QVariantMap params; QVariantMap returns;
params.clear(); returns.clear(); params.clear(); returns.clear();
setDescription("Hello", "Once connected to this server, a client can get information about the server by saying Hello. The response informs the client about the server."); setDescription("Hello", "Once connected to this server, a client can get information about the server by saying Hello. "
"The response informs the client about the server.");
setParams("Hello", params); setParams("Hello", params);
returns.insert("server", JsonTypes::basicTypeToString(JsonTypes::String)); returns.insert("server", JsonTypes::basicTypeToString(JsonTypes::String));
returns.insert("name", JsonTypes::basicTypeToString(JsonTypes::String)); returns.insert("name", JsonTypes::basicTypeToString(JsonTypes::String));
@ -35,6 +39,9 @@ JsonRpcServer::JsonRpcServer(QObject *parent) :
JsonRpcServer::~JsonRpcServer() JsonRpcServer::~JsonRpcServer()
{ {
qCDebug(dcJsonRpc()) << "Shutting down Json RPC server"; qCDebug(dcJsonRpc()) << "Shutting down Json RPC server";
foreach (JsonHandler *handler, m_handlers.values()) {
unregisterHandler(handler);
}
} }
QString JsonRpcServer::name() const QString JsonRpcServer::name() const
@ -42,33 +49,10 @@ QString JsonRpcServer::name() const
return "RemoteProxy"; return "RemoteProxy";
} }
QHash<QString, JsonHandler *> JsonRpcServer::handlers() const JsonReply *JsonRpcServer::Hello(const QVariantMap &params, ProxyClient *proxyClient) const
{
return m_handlers;
}
void JsonRpcServer::registerTransportInterface(TransportInterface *interface)
{
if (m_interfaces.contains(interface))
return;
connect(interface, &TransportInterface::clientConnected, this, &JsonRpcServer::clientConnected);
connect(interface, &TransportInterface::clientDisconnected, this, &JsonRpcServer::clientDisconnected);
connect(interface, &TransportInterface::dataAvailable, this, &JsonRpcServer::processData);
m_interfaces.append(interface);
}
void JsonRpcServer::unregisterTransportInterface(TransportInterface *interface)
{
disconnect(interface, &TransportInterface::clientConnected, this, &JsonRpcServer::clientConnected);
disconnect(interface, &TransportInterface::clientDisconnected, this, &JsonRpcServer::clientDisconnected);
disconnect(interface, &TransportInterface::dataAvailable, this, &JsonRpcServer::processData);
m_interfaces.removeOne(interface);
}
JsonReply *JsonRpcServer::Hello(const QVariantMap &params) const
{ {
Q_UNUSED(params) Q_UNUSED(params)
Q_UNUSED(proxyClient)
QVariantMap data; QVariantMap data;
data.insert("server", QCoreApplication::applicationName()); data.insert("server", QCoreApplication::applicationName());
@ -79,9 +63,12 @@ JsonReply *JsonRpcServer::Hello(const QVariantMap &params) const
return createReply(data); return createReply(data);
} }
JsonReply *JsonRpcServer::Introspect(const QVariantMap &params) const JsonReply *JsonRpcServer::Introspect(const QVariantMap &params, ProxyClient *proxyClient) const
{ {
Q_UNUSED(params) Q_UNUSED(params)
Q_UNUSED(proxyClient)
qCDebug(dcJsonRpc()) << "Introspect called.";
QVariantMap data; QVariantMap data;
data.insert("types", JsonTypes::allTypes()); data.insert("types", JsonTypes::allTypes());
@ -103,7 +90,7 @@ JsonReply *JsonRpcServer::Introspect(const QVariantMap &params) const
return createReply(data); return createReply(data);
} }
void JsonRpcServer::sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap &params) void JsonRpcServer::sendResponse(ProxyClient *client, int commandId, const QVariantMap &params)
{ {
QVariantMap response; QVariantMap response;
response.insert("id", commandId); response.insert("id", commandId);
@ -112,10 +99,10 @@ void JsonRpcServer::sendResponse(TransportInterface *interface, const QUuid &cli
QByteArray data = QJsonDocument::fromVariant(response).toJson(QJsonDocument::Compact); QByteArray data = QJsonDocument::fromVariant(response).toJson(QJsonDocument::Compact);
qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data; qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data;
interface->sendData(clientId, data); client->interface()->sendData(client->clientId(), data);
} }
void JsonRpcServer::sendErrorResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error) void JsonRpcServer::sendErrorResponse(ProxyClient *client, int commandId, const QString &error)
{ {
QVariantMap errorResponse; QVariantMap errorResponse;
errorResponse.insert("id", commandId); errorResponse.insert("id", commandId);
@ -124,7 +111,7 @@ void JsonRpcServer::sendErrorResponse(TransportInterface *interface, const QUuid
QByteArray data = QJsonDocument::fromVariant(errorResponse).toJson(QJsonDocument::Compact); QByteArray data = QJsonDocument::fromVariant(errorResponse).toJson(QJsonDocument::Compact);
qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data; qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data;
interface->sendData(clientId, data); client->interface()->sendData(client->clientId(), data);
} }
QString JsonRpcServer::formatAssertion(const QString &targetNamespace, const QString &method, JsonHandler *handler, const QVariantMap &data) const QString JsonRpcServer::formatAssertion(const QString &targetNamespace, const QString &method, JsonHandler *handler, const QVariantMap &data) const
@ -149,25 +136,149 @@ void JsonRpcServer::registerHandler(JsonHandler *handler)
} }
} }
void JsonRpcServer::unregisterHandler(JsonHandler *handler)
{
m_handlers.remove(handler->name());
qCDebug(dcJsonRpc()) << "Unregister handler" << handler->name();
for (int i = 0; i < handler->metaObject()->methodCount(); ++i) {
QMetaMethod method = handler->metaObject()->method(i);
if (method.methodType() == QMetaMethod::Signal && QString(method.name()).contains(QRegExp("^[A-Z]"))) {
QObject::connect(handler, method, this, metaObject()->method(metaObject()->indexOfSlot("sendNotification(QVariantMap)")));
}
}
}
void JsonRpcServer::setup() void JsonRpcServer::setup()
{ {
registerHandler(this); registerHandler(this);
registerHandler(new AuthenticationHandler(this)); registerHandler(new AuthenticationHandler(this));
} }
void JsonRpcServer::clientConnected(const QUuid &clientId) void JsonRpcServer::asyncReplyFinished()
{ {
Q_UNUSED(clientId) JsonReply *reply = static_cast<JsonReply *>(sender());
ProxyClient *proxyClient = m_asyncReplies.take(reply);
reply->deleteLater();
if (!proxyClient) {
qCWarning(dcJsonRpc()) << "Got an async reply but the client does not exist any more";
return;
}
if (!reply->timedOut()) {
Q_ASSERT_X(reply->handler()->validateReturns(reply->method(), reply->data()).first
,"validating return value", formatAssertion(reply->handler()->name(), reply->method(), reply->handler(), reply->data()).toLatin1().data());
sendResponse(proxyClient, reply->commandId(), reply->data());
if (!reply->success()) {
// Disconnect this client since the request was not successfully
proxyClient->interface()->killClientConnection(proxyClient->clientId());
}
} else {
sendErrorResponse(proxyClient, reply->commandId(), "Command timed out");
// Disconnect this client since he requested something that created a timeout
proxyClient->interface()->killClientConnection(proxyClient->clientId());
}
} }
void JsonRpcServer::clientDisconnected(const QUuid &clientId) void JsonRpcServer::registerClient(ProxyClient *proxyClient)
{ {
Q_UNUSED(clientId) qCDebug(dcJsonRpc()) << "Register client" << proxyClient;
if (m_clients.contains(proxyClient)) {
qCWarning(dcJsonRpc()) << "Client already registered" << proxyClient;
return;
}
m_clients.append(proxyClient);
} }
void JsonRpcServer::processData(const QUuid &clientId, const QByteArray &data) void JsonRpcServer::unregisterClient(ProxyClient *proxyClient)
{ {
Q_UNUSED(clientId) qCDebug(dcJsonRpc()) << "Unregister client" << proxyClient;
Q_UNUSED(data) if (!m_clients.contains(proxyClient)) {
qCWarning(dcJsonRpc()) << "Client was not registered" << proxyClient;
return;
}
m_clients.removeAll(proxyClient);
} }
void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data)
{
if (!m_clients.contains(proxyClient))
return;
qCDebug(dcJsonRpcTraffic()) << "Incoming data from" << proxyClient << ": " << data;
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if(error.error != QJsonParseError::NoError) {
qCWarning(dcJsonRpc) << "Failed to parse JSON data" << data << ":" << error.errorString();
sendErrorResponse(proxyClient, -1, QString("Failed to parse JSON data: %1").arg(error.errorString()));
proxyClient->interface()->killClientConnection(proxyClient->clientId());
return;
}
QVariantMap message = jsonDoc.toVariant().toMap();
bool success = false;
int commandId = message.value("id").toInt(&success);
if (!success) {
qCWarning(dcJsonRpc()) << "Error parsing command. Missing \"id\":" << message;
sendErrorResponse(proxyClient, -1, "Error parsing command. Missing 'id'");
proxyClient->interface()->killClientConnection(proxyClient->clientId());
return;
}
QStringList commandList = message.value("method").toString().split('.');
if (commandList.count() != 2) {
qCWarning(dcJsonRpc) << "Error parsing method.\nGot:" << message.value("method").toString() << "\nExpected: \"Namespace.method\"";
sendErrorResponse(proxyClient, commandId, QString("Error parsing method. Got: '%1'', Expected: 'Namespace.method'").arg(message.value("method").toString()));
proxyClient->interface()->killClientConnection(proxyClient->clientId());
return;
}
QString targetNamespace = commandList.first();
QString method = commandList.last();
JsonHandler *handler = m_handlers.value(targetNamespace);
if (!handler) {
sendErrorResponse(proxyClient, commandId, "No such namespace");
proxyClient->interface()->killClientConnection(proxyClient->clientId());
return;
}
if (!handler->hasMethod(method)) {
sendErrorResponse(proxyClient, commandId, "No such namespace");
proxyClient->interface()->killClientConnection(proxyClient->clientId());
return;
}
QVariantMap params = message.value("params").toMap();
QPair<bool, QString> validationResult = handler->validateParams(method, params);
if (!validationResult.first) {
sendErrorResponse(proxyClient, commandId, "Invalid params: " + validationResult.second);
proxyClient->interface()->killClientConnection(proxyClient->clientId());
return;
}
JsonReply *reply;
QMetaObject::invokeMethod(handler, method.toLatin1().data(), Q_RETURN_ARG(JsonReply*, reply), Q_ARG(QVariantMap, params), Q_ARG(ProxyClient *, proxyClient));
if (reply->type() == JsonReply::TypeAsync) {
m_asyncReplies.insert(reply, proxyClient);
reply->setClientId(proxyClient->clientId());
reply->setCommandId(commandId);
connect(reply, &JsonReply::finished, this, &JsonRpcServer::asyncReplyFinished);
reply->startWait();
} else {
Q_ASSERT_X((targetNamespace == "RemoteProxy" && method == "Introspect") || handler->validateReturns(method, reply->data()).first
,"validating return value", formatAssertion(targetNamespace, method, handler, reply->data()).toLatin1().data());
reply->setClientId(proxyClient->clientId());
reply->setCommandId(commandId);
sendResponse(proxyClient, commandId, reply->data());
}
}
}

View File

@ -2,11 +2,16 @@
#define JSONRPCSERVER_H #define JSONRPCSERVER_H
#include <QObject> #include <QObject>
#include <QVariant>
#include "proxyclient.h"
#include "jsonrpc/jsonreply.h"
#include "transportinterface.h" #include "transportinterface.h"
#include "jsonrpc/jsonhandler.h" #include "jsonrpc/jsonhandler.h"
#include "jsonrpc/authenticationhandler.h" #include "jsonrpc/authenticationhandler.h"
namespace remoteproxy {
class JsonRpcServer : public JsonHandler class JsonRpcServer : public JsonHandler
{ {
Q_OBJECT Q_OBJECT
@ -16,39 +21,40 @@ public:
QString name() const override; QString name() const override;
QHash<QString, JsonHandler *> handlers() const; Q_INVOKABLE JsonReply *Hello(const QVariantMap &params, ProxyClient *proxyClient = nullptr) const;
Q_INVOKABLE JsonReply *Introspect(const QVariantMap &params, ProxyClient *proxyClient = nullptr) const;
void registerTransportInterface(TransportInterface *interface); signals:
void unregisterTransportInterface(TransportInterface *interface); void TunnelEstablished(const QVariantMap &params, ProxyClient *proxyClient = nullptr);
Q_INVOKABLE JsonReply *Hello(const QVariantMap &params) const;
Q_INVOKABLE JsonReply *Introspect(const QVariantMap &params) const;
private: private:
QList<TransportInterface *> m_interfaces;
QHash<QString, JsonHandler *> m_handlers; QHash<QString, JsonHandler *> m_handlers;
QHash<JsonReply *, TransportInterface *> m_asyncReplies; QHash<JsonReply *, ProxyClient *> m_asyncReplies;
QHash<QUuid, TransportInterface*> m_clientTransports; QList<ProxyClient *> m_clients;
QHash<QString, JsonReply*> m_pairingRequests;
int m_notificationId; int m_notificationId;
void sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap &params = QVariantMap()); void sendResponse(ProxyClient *client, int commandId, const QVariantMap &params = QVariantMap());
void sendErrorResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error); void sendErrorResponse(ProxyClient *client, int commandId, const QString &error);
QString formatAssertion(const QString &targetNamespace, const QString &method, JsonHandler *handler, const QVariantMap &data) const; QString formatAssertion(const QString &targetNamespace, const QString &method, JsonHandler *handler, const QVariantMap &data) const;
void registerHandler(JsonHandler *handler); void registerHandler(JsonHandler *handler);
void unregisterHandler(JsonHandler *handler);
private slots: private slots:
void setup(); void setup();
void clientConnected(const QUuid &clientId); void asyncReplyFinished();
void clientDisconnected(const QUuid &clientId);
void processData(const QUuid &clientId, const QByteArray &data); public slots:
// void sendNotification(const QVariantMap &params); // Client registration for JSON RPC traffic
// void asyncReplyFinished(); void registerClient(ProxyClient *proxyClient);
void unregisterClient(ProxyClient *proxyClient);
// Process data from client
void processData(ProxyClient *proxyClient, const QByteArray &data);
}; };
}
#endif // JSONRPCSERVER_H #endif // JSONRPCSERVER_H

View File

@ -1,7 +1,10 @@
#include "proxyclient.h" #include "proxyclient.h"
ProxyClient::ProxyClient(const QUuid &clientId, QObject *parent) : namespace remoteproxy {
ProxyClient::ProxyClient(TransportInterface *interface, const QUuid &clientId, QObject *parent) :
QObject(parent), QObject(parent),
m_interface(interface),
m_clientId(clientId) m_clientId(clientId)
{ {
@ -17,7 +20,77 @@ bool ProxyClient::authenticated() const
return m_authenticated; return m_authenticated;
} }
void ProxyClient::setAuthenticated(bool authenticated)
{
if (m_authenticated == authenticated)
return;
m_authenticated = authenticated;
emit authenticatedChanged(m_authenticated);
}
bool ProxyClient::tunnelConnected() const bool ProxyClient::tunnelConnected() const
{ {
return m_tunnelConnected; return m_tunnelConnected;
} }
void ProxyClient::setTunnelConnected(bool tunnelConnected)
{
if (m_tunnelConnected == tunnelConnected)
return;
m_tunnelConnected = tunnelConnected;
emit tunnelConnectedChanged(m_tunnelConnected);
}
TransportInterface *ProxyClient::interface() const
{
return m_interface;
}
QString ProxyClient::uuid() const
{
return m_uuid;
}
void ProxyClient::setUuid(const QString &uuid)
{
m_uuid = uuid;
}
QString ProxyClient::name() const
{
return m_name;
}
void ProxyClient::setName(const QString &name)
{
m_name = name;
}
QString ProxyClient::token() const
{
return m_token;
}
void ProxyClient::setToken(const QString &token)
{
m_token = token;
}
QDebug operator<<(QDebug debug, ProxyClient *proxyClient)
{
debug.nospace() << "ProxyClient(" << proxyClient->interface()->serverName();
debug.nospace() << ", " << proxyClient->clientId().toString() << ") :" << endl;
debug.nospace() << " tunnel: " << proxyClient->tunnelConnected() << endl;
debug.nospace() << " authenticated: " << proxyClient->authenticated() << endl;
if (!proxyClient->name().isEmpty() && !proxyClient->token().isEmpty() && !proxyClient->uuid().isEmpty()) {
debug.nospace() << " name: " << proxyClient->name() << endl;
debug.nospace() << " uuid: " << proxyClient->uuid() << endl;
debug.nospace() << " token: " << proxyClient->token() << endl;
}
return debug;
}
}

View File

@ -2,25 +2,58 @@
#define PROXYCLIENT_H #define PROXYCLIENT_H
#include <QUuid> #include <QUuid>
#include <QDebug>
#include <QObject> #include <QObject>
#include "transportinterface.h"
namespace remoteproxy {
class ProxyClient : public QObject class ProxyClient : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit ProxyClient(const QUuid &clientId, QObject *parent = nullptr); explicit ProxyClient(TransportInterface *interface, const QUuid &clientId, QObject *parent = nullptr);
QUuid clientId() const; QUuid clientId() const;
bool authenticated() const; bool authenticated() const;
void setAuthenticated(bool authenticated);
bool tunnelConnected() const; bool tunnelConnected() const;
void setTunnelConnected(bool tunnelConnected);
TransportInterface *interface() const;
// Properties from auth request
QString uuid() const;
void setUuid(const QString &uuid);
QString name() const;
void setName(const QString &name);
QString token() const;
void setToken(const QString &token);
private: private:
TransportInterface *m_interface = nullptr;
QUuid m_clientId; QUuid m_clientId;
bool m_authenticated = false; bool m_authenticated = false;
bool m_tunnelConnected = false; bool m_tunnelConnected = false;
QString m_uuid;
QString m_name;
QString m_token;
signals:
void authenticatedChanged(bool authenticated);
void tunnelConnectedChanged(bool tunnelConnected);
}; };
QDebug operator<< (QDebug debug, ProxyClient *proxyClient);
}
#endif // PROXYCLIENT_H #endif // PROXYCLIENT_H

View File

@ -1,6 +1,10 @@
#include "proxyconfiguration.h" #include "proxyconfiguration.h"
namespace remoteproxy {
ProxyConfiguration::ProxyConfiguration(QObject *parent) : QObject(parent) ProxyConfiguration::ProxyConfiguration(QObject *parent) : QObject(parent)
{ {
} }
}

View File

@ -3,6 +3,8 @@
#include <QObject> #include <QObject>
namespace remoteproxy {
class ProxyConfiguration : public QObject class ProxyConfiguration : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -15,4 +17,6 @@ public slots:
}; };
}
#endif // PROXYCONFIGURATION_H #endif // PROXYCONFIGURATION_H

View File

@ -1,11 +1,21 @@
#include "proxyserver.h" #include "proxyserver.h"
#include "loggingcategories.h" #include "loggingcategories.h"
#include <QJsonDocument>
namespace remoteproxy {
ProxyServer::ProxyServer(QObject *parent) : QObject(parent) ProxyServer::ProxyServer(QObject *parent) : QObject(parent)
{ {
m_jsonRpcServer = new JsonRpcServer(this);
} }
ProxyServer::~ProxyServer()
{
qCDebug(dcProxyServer()) << "Shutting down proxy server";
}
void ProxyServer::registerTransportInterface(TransportInterface *interface) void ProxyServer::registerTransportInterface(TransportInterface *interface)
{ {
qCDebug(dcProxyServer()) << "Register transport interface" << interface->serverName(); qCDebug(dcProxyServer()) << "Register transport interface" << interface->serverName();
@ -22,12 +32,23 @@ void ProxyServer::registerTransportInterface(TransportInterface *interface)
m_transportInterfaces.append(interface); m_transportInterfaces.append(interface);
} }
void ProxyServer::sendResponse(TransportInterface *interface, const QUuid &clientId, const QVariantMap &response)
{
QByteArray data = QJsonDocument::fromVariant(response).toJson(QJsonDocument::Compact);
qCDebug(dcJsonRpcTraffic()) << "Sending data:" << data;
interface->sendData(clientId, data);
}
void ProxyServer::onClientConnected(const QUuid &clientId) void ProxyServer::onClientConnected(const QUuid &clientId)
{ {
TransportInterface *interface = static_cast<TransportInterface *>(sender()); TransportInterface *interface = static_cast<TransportInterface *>(sender());
qCDebug(dcProxyServer()) << "New client connected" << interface->serverName() << clientId.toString(); qCDebug(dcProxyServer()) << "New client connected" << interface->serverName() << clientId.toString();
m_unauthenticatedClients.append(clientId); ProxyClient *proxyClient = new ProxyClient(interface, clientId, this);
m_proxyClients.insert(clientId, proxyClient);
m_jsonRpcServer->registerClient(proxyClient);
} }
void ProxyServer::onClientDisconnected(const QUuid &clientId) void ProxyServer::onClientDisconnected(const QUuid &clientId)
@ -35,14 +56,33 @@ void ProxyServer::onClientDisconnected(const QUuid &clientId)
TransportInterface *interface = static_cast<TransportInterface *>(sender()); TransportInterface *interface = static_cast<TransportInterface *>(sender());
qCDebug(dcProxyServer()) << "Client disconnected" << interface->serverName() << clientId.toString(); qCDebug(dcProxyServer()) << "Client disconnected" << interface->serverName() << clientId.toString();
// Clean up this client since it does not exist any more if (m_proxyClients.contains(clientId)) {
m_unauthenticatedClients.removeAll(clientId); ProxyClient *proxyClient = m_proxyClients.take(clientId);
m_jsonRpcServer->unregisterClient(proxyClient);
proxyClient->deleteLater();
// Check if client is in tunnel and clean up tunnel
// Disconnect also the other tunnel client
}
// TODO: Clean up this client since it does not exist any more
} }
void ProxyServer::onClientDataAvailable(const QUuid &clientId, const QByteArray &data) void ProxyServer::onClientDataAvailable(const QUuid &clientId, const QByteArray &data)
{ {
TransportInterface *interface = static_cast<TransportInterface *>(sender()); ProxyClient *proxyClient = m_proxyClients.value(clientId);
qCDebug(dcProxyServer()) << "Client data available" << interface->serverName() << clientId.toString() << qUtf8Printable(data); if (!proxyClient) {
qCWarning(dcProxyServer()) << "Could not find client for uuid" << clientId;
return;
}
qCDebug(dcProxyServer()) << "Client data available" << proxyClient << qUtf8Printable(data);
if (proxyClient->tunnelConnected()) {
// Pipe the data
} else {
// Pipe data into json rpc server
m_jsonRpcServer->processData(proxyClient, data);
}
} }
void ProxyServer::startServer() void ProxyServer::startServer()
@ -60,3 +100,5 @@ void ProxyServer::stopServer()
interface->stopServer(); interface->stopServer();
} }
} }
}

View File

@ -2,22 +2,32 @@
#define PROXYSERVER_H #define PROXYSERVER_H
#include <QUuid> #include <QUuid>
#include <QHash>
#include <QObject> #include <QObject>
#include "proxyclient.h" #include "proxyclient.h"
#include "jsonrpcserver.h"
#include "transportinterface.h" #include "transportinterface.h"
namespace remoteproxy {
class ProxyServer : public QObject class ProxyServer : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit ProxyServer(QObject *parent = nullptr); explicit ProxyServer(QObject *parent = nullptr);
~ProxyServer();
void registerTransportInterface(TransportInterface *interface); void registerTransportInterface(TransportInterface *interface);
private: private:
JsonRpcServer *m_jsonRpcServer = nullptr;
QList<TransportInterface *> m_transportInterfaces; QList<TransportInterface *> m_transportInterfaces;
QList<QUuid> m_unauthenticatedClients;
QHash<QUuid, ProxyClient *> m_proxyClients;
QHash<ProxyClient *, ProxyClient *> m_tunnels;
void sendResponse(TransportInterface *interface, const QUuid &clientId, const QVariantMap &response = QVariantMap());
private slots: private slots:
void onClientConnected(const QUuid &clientId); void onClientConnected(const QUuid &clientId);
@ -30,4 +40,6 @@ public slots:
}; };
}
#endif // PROXYSERVER_H #endif // PROXYSERVER_H

View File

@ -1,5 +1,7 @@
#include "transportinterface.h" #include "transportinterface.h"
namespace remoteproxy {
TransportInterface::TransportInterface(QObject *parent) : TransportInterface::TransportInterface(QObject *parent) :
QObject(parent) QObject(parent)
{ {
@ -15,3 +17,5 @@ TransportInterface::~TransportInterface()
{ {
} }
}

View File

@ -3,6 +3,8 @@
#include <QObject> #include <QObject>
namespace remoteproxy {
class TransportInterface : public QObject class TransportInterface : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -15,6 +17,8 @@ public:
virtual void sendData(const QUuid &clientId, const QByteArray &data) = 0; virtual void sendData(const QUuid &clientId, const QByteArray &data) = 0;
virtual void sendData(const QList<QUuid> &clients, const QByteArray &data) = 0; virtual void sendData(const QList<QUuid> &clients, const QByteArray &data) = 0;
virtual void killClientConnection(const QUuid &clientId) = 0;
signals: signals:
void clientConnected(const QUuid &clientId); void clientConnected(const QUuid &clientId);
void clientDisconnected(const QUuid &clientId); void clientDisconnected(const QUuid &clientId);
@ -29,4 +33,6 @@ public slots:
}; };
}
#endif // TRANSPORTINTERFACE_H #endif // TRANSPORTINTERFACE_H

View File

@ -3,6 +3,8 @@
#include <QCoreApplication> #include <QCoreApplication>
namespace remoteproxy {
WebSocketServer::WebSocketServer(const QSslConfiguration &sslConfiguration, QObject *parent) : WebSocketServer::WebSocketServer(const QSslConfiguration &sslConfiguration, QObject *parent) :
TransportInterface(parent), TransportInterface(parent),
m_sslConfiguration(sslConfiguration) m_sslConfiguration(sslConfiguration)
@ -54,6 +56,16 @@ void WebSocketServer::sendData(const QList<QUuid> &clients, const QByteArray &da
} }
} }
void WebSocketServer::killClientConnection(const QUuid &clientId)
{
QWebSocket *client = m_clientList.value(clientId);
if (!client)
return;
qCWarning(dcWebSocketServer()) << "Kill client connection" << clientId.toString();
client->close(QWebSocketProtocol::CloseCodeBadOperation);
}
void WebSocketServer::onClientConnected() void WebSocketServer::onClientConnected()
{ {
// Got a new client connected // Got a new client connected
@ -69,7 +81,7 @@ void WebSocketServer::onClientConnected()
// Create new uuid for this connection // Create new uuid for this connection
QUuid clientId = QUuid::createUuid(); QUuid clientId = QUuid::createUuid();
qCDebug(dcWebSocketServer()) << "New client connected:" << client->peerAddress().toString() << clientId; qCDebug(dcWebSocketServer()) << "New client connected:" << client << client->peerAddress().toString() << clientId.toString();
// Append the new client to the client list // Append the new client to the client list
m_clientList.insert(clientId, client); m_clientList.insert(clientId, client);
@ -88,7 +100,7 @@ void WebSocketServer::onClientDisconnected()
QWebSocket *client = static_cast<QWebSocket *>(sender()); QWebSocket *client = static_cast<QWebSocket *>(sender());
QUuid clientId = m_clientList.key(client); QUuid clientId = m_clientList.key(client);
qCDebug(dcWebSocketServer()) << "Client disconnected:" << client->peerAddress().toString() << clientId; qCDebug(dcWebSocketServer()) << "Client disconnected:" << client << client->peerAddress().toString() << clientId.toString();
m_clientList.take(clientId)->deleteLater(); m_clientList.take(clientId)->deleteLater();
emit clientDisconnected(clientId); emit clientDisconnected(clientId);
@ -159,3 +171,5 @@ bool WebSocketServer::stopServer()
return true; return true;
} }
}

View File

@ -10,6 +10,8 @@
#include "transportinterface.h" #include "transportinterface.h"
namespace remoteproxy {
class WebSocketServer : public TransportInterface class WebSocketServer : public TransportInterface
{ {
Q_OBJECT Q_OBJECT
@ -27,6 +29,8 @@ public:
void sendData(const QUuid &clientId, const QByteArray &data) override; void sendData(const QUuid &clientId, const QByteArray &data) override;
void sendData(const QList<QUuid> &clients, const QByteArray &data) override; void sendData(const QList<QUuid> &clients, const QByteArray &data) override;
void killClientConnection(const QUuid &clientId) override;
private: private:
QUrl m_serverUrl; QUrl m_serverUrl;
QWebSocketServer *m_server = nullptr; QWebSocketServer *m_server = nullptr;
@ -50,4 +54,6 @@ public slots:
}; };
}
#endif // WEBSOCKETSERVER_H #endif // WEBSOCKETSERVER_H

View File

@ -0,0 +1,6 @@
#include "jsonrpcclient.h"
JsonRpcClient::JsonRpcClient(QObject *parent) : QObject(parent)
{
}

View File

@ -0,0 +1,17 @@
#ifndef JSONRPCCLIENT_H
#define JSONRPCCLIENT_H
#include <QObject>
class JsonRpcClient : public QObject
{
Q_OBJECT
public:
explicit JsonRpcClient(QObject *parent = nullptr);
signals:
public slots:
};
#endif // JSONRPCCLIENT_H

View File

@ -5,12 +5,16 @@ TARGET = nymea-remoteproxyclient
HEADERS += \ HEADERS += \
remoteproxyconnector.h \ remoteproxyconnector.h \
websocketconnector.h websocketconnector.h \
socketconnector.h \
jsonrpcclient.h
SOURCES += \ SOURCES += \
remoteproxyconnector.cpp \ remoteproxyconnector.cpp \
websocketconnector.cpp websocketconnector.cpp \
socketconnector.cpp \
jsonrpcclient.cpp
# install header file with relative subdirectory # install header file with relative subdirectory

View File

@ -119,6 +119,12 @@ bool RemoteProxyConnector::sendData(const QByteArray &data)
return false; return false;
} }
if (!isConnected()) {
qCWarning(dcRemoteProxyConnector()) << "Not connected";
return false;
}
qCDebug(dcRemoteProxyConnector()) << "Sending data:" << data;
qint64 dataSendCount = m_webSocket->sendTextMessage(QString::fromUtf8(data)); qint64 dataSendCount = m_webSocket->sendTextMessage(QString::fromUtf8(data));
if (dataSendCount != data.count()) { if (dataSendCount != data.count()) {
qCWarning(dcRemoteProxyConnector()) << "Could not send all data to" << serverUrl().toString(); qCWarning(dcRemoteProxyConnector()) << "Could not send all data to" << serverUrl().toString();

View File

@ -0,0 +1,11 @@
#include "socketconnector.h"
SocketConnector::SocketConnector(QObject *parent) : QObject(parent)
{
}
SocketConnector::~SocketConnector()
{
}

View File

@ -0,0 +1,29 @@
#ifndef SOCKETCONNECTOR_H
#define SOCKETCONNECTOR_H
#include <QObject>
#include <QHostAddress>
class SocketConnector : public QObject
{
Q_OBJECT
public:
explicit SocketConnector(QObject *parent = nullptr);
virtual ~SocketConnector() = 0;
virtual void sendData(const QByteArray &data) = 0;
virtual bool isConnected() = 0;
signals:
void connected();
void disconnected();
void dataReceived(const QByteArray &data);
public slots:
virtual void connectServer(const QHostAddress &address, quint16 port) = 0;
virtual void disconnectServer() = 0;
};
#endif // SOCKETCONNECTOR_H

View File

@ -1,6 +1,12 @@
#include "websocketconnector.h" #include "websocketconnector.h"
WebSocketConnector::WebSocketConnector(QObject *parent) : QObject(parent) WebSocketConnector::WebSocketConnector(QObject *parent) :
SocketConnector(parent)
{ {
} }
void WebSocketConnector::sendData(const QByteArray &data)
{
Q_UNUSED(data)
}

View File

@ -3,12 +3,16 @@
#include <QObject> #include <QObject>
class WebSocketConnector : public QObject #include "socketconnector.h"
class WebSocketConnector : public SocketConnector
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit WebSocketConnector(QObject *parent = nullptr); explicit WebSocketConnector(QObject *parent = nullptr);
void sendData(const QByteArray &data) override;
signals: signals:
public slots: public slots:

View File

@ -21,6 +21,7 @@
#include "loggingcategories.h" #include "loggingcategories.h"
#include "authentication/awsauthenticator.h" #include "authentication/awsauthenticator.h"
using namespace remoteproxy;
static QHash<QString, bool> s_loggingFilters; static QHash<QString, bool> s_loggingFilters;

View File

@ -1,25 +1,59 @@
#include "proxyclient.h"
#include "mockauthenticator.h" #include "mockauthenticator.h"
#include "loggingcategories.h" #include "loggingcategories.h"
#include "authentication/authenticationreply.h" #include "authentication/authenticationreply.h"
MockAuthenticator::MockAuthenticator(QObject *parent) : MockAuthenticator::MockAuthenticator(QObject *parent) :
Authenticator(parent) Authenticator(parent)
{ {
m_timer = new QTimer(this);
m_timer->setSingleShot(true);
connect(m_timer, &QTimer::timeout, this, &MockAuthenticator::onTimeout);
} }
void MockAuthenticator::onTimeout() QString MockAuthenticator::name() const
{ {
return "Mock authenticator";
} }
AuthenticationReply *MockAuthenticator::authenticate(const QUuid &clientId, const QString &token) void MockAuthenticator::setTimeoutDuration(int timeout)
{ {
qCDebug(dcAuthenticator()) << "MockAuthenticator: Start authentication for" << clientId << "using token" << token; m_timeoutDuration = timeout;
AuthenticationReply *reply = new AuthenticationReply(clientId, token, this);
return reply;
} }
void MockAuthenticator::setExpectedAuthenticationError(Authenticator::AuthenticationError error)
{
m_expectedError = error;
}
void MockAuthenticator::replyFinished()
{
MockAuthenticationReply *reply = static_cast<MockAuthenticationReply *>(sender());
reply->deleteLater();
qCDebug(dcAuthenticator()) << name() << "Authentication finished.";
setReplyError(reply->authenticationReply(), reply->error());
setReplyFinished(reply->authenticationReply());
}
AuthenticationReply *MockAuthenticator::authenticate(ProxyClient *proxyClient)
{
qCDebug(dcAuthenticator()) << name() << "Start authentication for" << proxyClient << "using token" << proxyClient->token();
AuthenticationReply *authenticationReply = createAuthenticationReply(proxyClient, this);
MockAuthenticationReply *reply = new MockAuthenticationReply(m_timeoutDuration, m_expectedError, authenticationReply, this);
connect(reply, &MockAuthenticationReply::finished, this, &MockAuthenticator::replyFinished);
return authenticationReply;
}
MockAuthenticationReply::MockAuthenticationReply(int timeout, Authenticator::AuthenticationError error, AuthenticationReply *authenticationReply, QObject *parent) :
QObject(parent),
m_error(error),
m_authenticationReply(authenticationReply)
{
QTimer::singleShot(timeout, this, &MockAuthenticationReply::finished);
}

View File

@ -6,25 +6,47 @@
#include "authentication/authenticator.h" #include "authentication/authenticator.h"
using namespace remoteproxy;
class MockAuthenticationReply : public QObject
{
Q_OBJECT
public:
explicit MockAuthenticationReply(int timeout, Authenticator::AuthenticationError error, AuthenticationReply *authenticationReply, QObject *parent = nullptr);
AuthenticationReply *authenticationReply() const { return m_authenticationReply; }
Authenticator::AuthenticationError error() const { return m_error; }
private:
Authenticator::AuthenticationError m_error;
AuthenticationReply *m_authenticationReply;
signals:
void finished();
};
class MockAuthenticator : public Authenticator class MockAuthenticator : public Authenticator
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit MockAuthenticator(QObject *parent = nullptr); explicit MockAuthenticator(QObject *parent = nullptr);
QString name() const override;
void setTimeoutDuration(int timeout); void setTimeoutDuration(int timeout);
void setExpectedAuthenticationError(Authenticator::AuthenticationError error); void setExpectedAuthenticationError(Authenticator::AuthenticationError error = AuthenticationErrorNoError);
private: private:
QTimer * m_timer = nullptr;
int m_timeoutDuration = 1000; int m_timeoutDuration = 1000;
Authenticator::AuthenticationError m_expectedError; Authenticator::AuthenticationError m_expectedError;
private slots: private slots:
void onTimeout(); void replyFinished();
public slots: public slots:
AuthenticationReply *authenticate(const QUuid &clientId, const QString &token) override; AuthenticationReply *authenticate(ProxyClient *proxyClient) override;
}; };
#endif // MOCKAUTHENTICATOR_H #endif // MOCKAUTHENTICATOR_H

View File

@ -7,6 +7,7 @@
#include <QMetaType> #include <QMetaType>
#include <QSignalSpy> #include <QSignalSpy>
#include <QWebSocket> #include <QWebSocket>
#include <QJsonDocument>
#include <QWebSocketServer> #include <QWebSocketServer>
RemoteProxyTests::RemoteProxyTests(QObject *parent) : RemoteProxyTests::RemoteProxyTests(QObject *parent) :
@ -40,6 +41,7 @@ RemoteProxyTests::RemoteProxyTests(QObject *parent) :
void RemoteProxyTests::cleanUpEngine() void RemoteProxyTests::cleanUpEngine()
{ {
if (Engine::exists()) { if (Engine::exists()) {
Engine::instance()->stop();
Engine::instance()->destroy(); Engine::instance()->destroy();
QVERIFY(!Engine::exists()); QVERIFY(!Engine::exists());
} }
@ -48,16 +50,22 @@ void RemoteProxyTests::cleanUpEngine()
void RemoteProxyTests::restartEngine() void RemoteProxyTests::restartEngine()
{ {
cleanUpEngine(); cleanUpEngine();
startEngine();
QVERIFY(Engine::instance() != nullptr);
QVERIFY(Engine::exists());
} }
void RemoteProxyTests::startEngine() void RemoteProxyTests::startEngine()
{ {
if (!Engine::exists()) { if (!Engine::exists()) {
Engine::instance(); QString serverName = "nymea-remoteproxy-testserver";
Engine::instance()->setAuthenticator(m_authenticator);
Engine::instance()->setAuthenticationServerUrl(QUrl("https://localhost"));
Engine::instance()->setServerName(serverName);
Engine::instance()->setWebSocketServerPort(m_port);
Engine::instance()->setWebSocketServerHostAddress(QHostAddress::LocalHost);
Engine::instance()->setSslConfiguration(m_sslConfiguration);
QVERIFY(Engine::exists()); QVERIFY(Engine::exists());
QVERIFY(Engine::instance()->serverName() == serverName);
} }
} }
@ -65,16 +73,7 @@ void RemoteProxyTests::startServer()
{ {
startEngine(); startEngine();
QString serverName = "nymea-remoteproxy-testserver";
Engine::instance()->setAuthenticationServerUrl(QUrl("https://localhost"));
Engine::instance()->setServerName(serverName);
Engine::instance()->setAuthenticator(m_authenticator);
Engine::instance()->setWebSocketServerPort(m_port);
Engine::instance()->setWebSocketServerHostAddress(QHostAddress::LocalHost);
Engine::instance()->setSslConfiguration(m_sslConfiguration);
Engine::instance()->start(); Engine::instance()->start();
QVERIFY(Engine::instance()->serverName() == serverName);
QVERIFY(Engine::instance()->running()); QVERIFY(Engine::instance()->running());
QVERIFY(Engine::instance()->webSocketServer()->running()); QVERIFY(Engine::instance()->webSocketServer()->running());
} }
@ -88,6 +87,99 @@ void RemoteProxyTests::stopServer()
QVERIFY(!Engine::instance()->running()); QVERIFY(!Engine::instance()->running());
} }
QVariant RemoteProxyTests::invokeApiCall(const QString &method, const QVariantMap params, bool remainsConnected)
{
Q_UNUSED(remainsConnected)
QVariantMap request;
request.insert("id", m_commandCounter);
request.insert("method", method);
request.insert("params", params);
QJsonDocument jsonDoc = QJsonDocument::fromVariant(request);
QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13);
connect(socket, &QWebSocket::sslErrors, this, &RemoteProxyTests::sslErrors);
QSignalSpy spyConnection(socket, SIGNAL(connected()));
socket->open(Engine::instance()->webSocketServer()->serverUrl());
spyConnection.wait();
if (spyConnection.count() == 0) {
return QVariant();
}
// QSignalSpy disconnectedSpy(socket, SIGNAL(disconnected()));
QSignalSpy dataSpy(socket, SIGNAL(textMessageReceived(QString)));
socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact)));
dataSpy.wait();
// if (remainsConnected) {
// disconnectedSpy.wait(1000);
// if (socket->state() != QAbstractSocket::UnconnectedState) {
// qWarning() << "!!!!!!!!!!!!! socket still connected but should be disconnected!";
// }
// } else {
// disconnectedSpy.wait();
// if (socket->state() != QAbstractSocket::ConnectedState) {
// qWarning() << "!!!!!!!!!!!!! socket not connected but should be!";
// }
// }
socket->close();
socket->deleteLater();
for (int i = 0; i < dataSpy.count(); i++) {
// Make sure the response it a valid JSON string
QJsonParseError error;
jsonDoc = QJsonDocument::fromJson(dataSpy.at(i).last().toByteArray(), &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "JSON parser error" << error.errorString();
return QVariant();
}
QVariantMap response = jsonDoc.toVariant().toMap();
// skip notifications
if (response.contains("notification"))
continue;
if (response.value("id").toInt() == m_commandCounter) {
m_commandCounter++;
return jsonDoc.toVariant();
}
}
m_commandCounter++;
return QVariant();
}
QVariant RemoteProxyTests::injectSocketData(const QByteArray &data)
{
QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13);
connect(socket, &QWebSocket::sslErrors, this, &RemoteProxyTests::sslErrors);
QSignalSpy spyConnection(socket, SIGNAL(connected()));
socket->open(Engine::instance()->webSocketServer()->serverUrl());
spyConnection.wait();
if (spyConnection.count() == 0) {
return QVariant();
}
QSignalSpy spy(socket, SIGNAL(textMessageReceived(QString)));
socket->sendTextMessage(QString(data));
spy.wait();
socket->close();
socket->deleteLater();
for (int i = 0; i < spy.count(); i++) {
// Make sure the response it a valid JSON string
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.at(i).last().toByteArray(), &error);
if (error.error != QJsonParseError::NoError) {
qWarning() << "JSON parser error" << error.errorString();
return QVariant();
}
return jsonDoc.toVariant();
}
m_commandCounter++;
return QVariant();
}
void RemoteProxyTests::initTestCase() void RemoteProxyTests::initTestCase()
{ {
qRegisterMetaType<RemoteProxyConnector::Error>(); qRegisterMetaType<RemoteProxyConnector::Error>();
@ -104,10 +196,8 @@ void RemoteProxyTests::cleanupTestCase()
void RemoteProxyTests::startStopServer() void RemoteProxyTests::startStopServer()
{ {
startEngine();
startServer(); startServer();
stopServer(); stopServer();
cleanUpEngine();
} }
void RemoteProxyTests::webserverConnectionBlocked() void RemoteProxyTests::webserverConnectionBlocked()
@ -134,7 +224,6 @@ void RemoteProxyTests::webserverConnectionBlocked()
// Clean up // Clean up
stopServer(); stopServer();
cleanUpEngine();
} }
void RemoteProxyTests::webserverSocketVersion() void RemoteProxyTests::webserverSocketVersion()
@ -153,20 +242,11 @@ void RemoteProxyTests::webserverSocketVersion()
// Clean up // Clean up
stopServer(); stopServer();
cleanUpEngine();
} }
void RemoteProxyTests::webserverConnection() void RemoteProxyTests::webserverConnection()
{ {
// Start the server
startServer();
// Clean up
stopServer();
cleanUpEngine();
} }
void RemoteProxyTests::sslConfigurations() void RemoteProxyTests::sslConfigurations()
@ -199,25 +279,143 @@ void RemoteProxyTests::sslConfigurations()
QVERIFY(!connector->isConnected()); QVERIFY(!connector->isConnected());
connector->deleteLater(); connector->deleteLater();
Engine::instance()->stop(); stopServer();
}
void RemoteProxyTests::getIntrospect()
{
// Start the server
startServer();
QVariant response = invokeApiCall("RemoteProxy.Introspect");
qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
// Clean up
stopServer();
}
void RemoteProxyTests::getHello()
{
// Start the server
startServer();
QVariant response = invokeApiCall("RemoteProxy.Hello");
qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
// Clean up
stopServer();
}
void RemoteProxyTests::apiBasicCalls_data()
{
QTest::addColumn<QByteArray>("data");
QTest::addColumn<bool>("valid");
QTest::newRow("valid call") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\"}") << true;
QTest::newRow("missing id") << QByteArray("{\"method\":\"RemoteProxy.Hello\"}")<< false;
QTest::newRow("missing method") << QByteArray("{\"id\":42}") << false;
QTest::newRow("borked") << QByteArray("{\"id\":42, \"method\":\"RemoteProx")<< false;
QTest::newRow("invalid function") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Explode\"}") << false;
QTest::newRow("invalid namespace") << QByteArray("{\"id\":42, \"method\":\"ProxyRemote.Hello\"}") << false;
QTest::newRow("missing dot") << QByteArray("{\"id\":42, \"method\":\"RemoteProxyHello\"}") << false;
QTest::newRow("invalid params") << QByteArray("{\"id\":42, \"method\":\"RemoteProxy.Hello\", \"params\":{\"törööö\":\"chooo-chooo\"}}") << false;
}
void RemoteProxyTests::apiBasicCalls()
{
QFETCH(QByteArray, data);
QFETCH(bool, valid);
// Start the server
startServer();
QVariant response = injectSocketData(data);
if (valid) {
QVERIFY2(response.toMap().value("status").toString() == "success", "Call wasn't parsed correctly by nymea.");
}
// Clean up
stopServer();
}
void RemoteProxyTests::authenticate_data()
{
QTest::addColumn<QString>("uuid");
QTest::addColumn<QString>("name");
QTest::addColumn<QString>("token");
QTest::addColumn<int>("timeout");
QTest::addColumn<Authenticator::AuthenticationError>("expectedError");
QTest::newRow("success") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "Hh5JrkdFVstVVyCnfE3vVT3zG_JTacXEhPLZhCei"
<< 100 << Authenticator::AuthenticationErrorNoError;
QTest::newRow("failed") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "invalid_token_42"
<< 100 << Authenticator::AuthenticationErrorAuthenticationFailed;
QTest::newRow("not responding") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "invalid_token_42"
<< 200 << Authenticator::AuthenticationErrorAuthenticationServerNotResponding;
QTest::newRow("aborted") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "invalid_token_42"
<< 100 << Authenticator::AuthenticationErrorAborted;
QTest::newRow("unknown") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << "invalid_token_42"
<< 100 << Authenticator::AuthenticationErrorUnknown;
} }
void RemoteProxyTests::authenticate() void RemoteProxyTests::authenticate()
{ {
// // Start the server QFETCH(QString, uuid);
// startServer(); QFETCH(QString, name);
QFETCH(QString, token);
QFETCH(int, timeout);
QFETCH(Authenticator::AuthenticationError, expectedError);
// // Connect to the server // Start the server
// RemoteProxyConnector *connector = new RemoteProxyConnector(this); startServer();
// connector->setInsecureConnection(true);
// QSignalSpy spy(connector, &RemoteProxyConnector::connected); // Configure result
// connector->connectServer(QHostAddress::LocalHost, m_port); m_authenticator->setExpectedAuthenticationError(expectedError);
// spy.wait(); m_authenticator->setTimeoutDuration(timeout);
// connector->disconnectServer(); // Create request
// connector->deleteLater(); QVariantMap params;
// Engine::instance()->stop(); params.insert("uuid", uuid);
params.insert("name", name);
params.insert("token", token);
QVariant response = invokeApiCall("Authentication.Authenticate", params);
qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
verifyAuthenticationError(response, expectedError);
// Clean up
stopServer();
}
void RemoteProxyTests::timeout()
{
// Start the server
startServer();
// Configure result
// Start the server
startServer();
// Configure result
m_authenticator->setExpectedAuthenticationError();
m_authenticator->setTimeoutDuration(6000);
// Create request
QVariantMap params;
params.insert("uuid", "uuid");
params.insert("name", "name");
params.insert("token", "token");
QVariant response = invokeApiCall("Authentication.Authenticate", params);
qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
// Clean up
stopServer();
} }

View File

@ -5,12 +5,16 @@
#include <QtTest> #include <QtTest>
#include <QSslKey> #include <QSslKey>
#include <QObject> #include <QObject>
#include <QWebSocket>
#include <QHostAddress> #include <QHostAddress>
#include <QSslCertificate> #include <QSslCertificate>
#include <QSslConfiguration> #include <QSslConfiguration>
#include "jsonrpc/jsontypes.h"
#include "mockauthenticator.h" #include "mockauthenticator.h"
using namespace remoteproxy;
class RemoteProxyTests : public QObject class RemoteProxyTests : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -23,25 +27,71 @@ private:
QSslConfiguration m_sslConfiguration; QSslConfiguration m_sslConfiguration;
MockAuthenticator *m_authenticator = nullptr; MockAuthenticator *m_authenticator = nullptr;
int m_commandCounter = 0;
void cleanUpEngine(); void cleanUpEngine();
void restartEngine(); void restartEngine();
void startEngine(); void startEngine();
void startServer(); void startServer();
void stopServer(); void stopServer();
QVariant invokeApiCall(const QString &method, const QVariantMap params = QVariantMap(), bool remainsConnected = true);
QVariant injectSocketData(const QByteArray &data);
protected slots: protected slots:
void initTestCase(); void initTestCase();
void cleanupTestCase(); void cleanupTestCase();
private slots: private slots:
// Basic stuff
void startStopServer(); void startStopServer();
// WebSocket connection
void webserverConnectionBlocked(); void webserverConnectionBlocked();
void webserverSocketVersion(); void webserverSocketVersion();
void webserverConnection(); void webserverConnection();
void sslConfigurations(); void sslConfigurations();
// Api
void getIntrospect();
void getHello();
void apiBasicCalls_data();
void apiBasicCalls();
void authenticate_data();
void authenticate(); void authenticate();
void timeout();
public slots:
void sslErrors(const QList<QSslError> &) {
QWebSocket *socket = static_cast<QWebSocket*>(sender());
socket->ignoreSslErrors();
}
inline void verifyError(const QVariant &response, const QString &fieldName, const QString &error)
{
QJsonDocument jsonDoc = QJsonDocument::fromVariant(response);
QVERIFY2(response.toMap().value("status").toString() == QString("success"),
QString("\nExpected status: \"success\"\nGot: %2\nFull message: %3")
.arg(response.toMap().value("status").toString())
.arg(jsonDoc.toJson().data())
.toLatin1().data());
QVERIFY2(response.toMap().value("params").toMap().value(fieldName).toString() == error,
QString("\nExpected: %1\nGot: %2\nFull message: %3\n")
.arg(error)
.arg(response.toMap().value("params").toMap().value(fieldName).toString())
.arg(jsonDoc.toJson().data())
.toLatin1().data());
}
inline void verifyAuthenticationError(const QVariant &response, Authenticator::AuthenticationError error = Authenticator::AuthenticationErrorNoError) {
verifyError(response, "authenticationError", JsonTypes::authenticationErrorToString(error));
}
}; };
#endif // NYMEA_REMOTEPROXY_TESTS_H #endif // NYMEA_REMOTEPROXY_TESTS_H