First working client authentication

This commit is contained in:
Simon Stürz 2018-08-10 11:53:00 +02:00
parent 06c27fb5aa
commit ca54d5b44d
18 changed files with 266 additions and 84 deletions

View File

@ -156,6 +156,74 @@ Once the other client is here and ready, the server will send a notification to
} }
## The complete API
s"methods": {
"Authentication.Authenticate": {
"description": "Authenticate this connection. The returned AuthenticationError informs about the result. If the authentication was not successfull, the server will close the connection immediatly after sending the error response. The given id should be a unique id the other tunnel client can understand. Once the authentication was successfull, you can wait for the RemoteProxy.TunnelEstablished notification. If you send any data before getting this notification, the server will close the connection. If the tunnel client does not show up within 10 seconds, the server will close the connection.",
"params": {
"name": "String",
"token": "String",
"uuid": "String"
},
"returns": {
"authenticationError": "$ref:AuthenticationError"
}
},
"RemoteProxy.Hello": {
"description": "Once connected to this server, a client can get information about the server by saying Hello. The response informs the client about this proxy server.",
"params": {
},
"returns": {
"apiVersion": "String",
"name": "String",
"server": "String",
"version": "String"
}
},
"RemoteProxy.Introspect": {
"description": "Introspect this API.",
"params": {
},
"returns": {
"methods": "Object",
"notifications": "Object",
"types": "Object"
}
}
},
"notifications": {
"RemoteProxy.TunnelEstablished": {
"description": "Emitted whenever the tunnel has been established successfully. This is the last message from the remote proxy server! Any following data will be from the other tunnel client until the connection will be closed. The parameter contain some information about the other tunnel client.",
"params": {
"name": "String",
"uuid": "String"
}
}
},
"types": {
"AuthenticationError": [
"AuthenticationErrorNoError",
"AuthenticationErrorUnknown",
"AuthenticationErrorTimeout",
"AuthenticationErrorAborted",
"AuthenticationErrorAuthenticationFailed",
"AuthenticationErrorAuthenticationServerNotResponding"
],
"BasicType": [
"Uuid",
"String",
"Int",
"UInt",
"Double",
"Bool",
"Variant",
"Object"
]
}
# License # License
This 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, version 3 of the License. This 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, version 3 of the License.

View File

@ -65,11 +65,12 @@ void AuthenticationHandler::onAuthenticationFinished()
if (authenticationReply->error() != Authenticator::AuthenticationErrorNoError) { if (authenticationReply->error() != Authenticator::AuthenticationErrorNoError) {
qCWarning(dcJsonRpc()) << "Authentication error occured" << authenticationReply->error(); qCWarning(dcJsonRpc()) << "Authentication error occured" << authenticationReply->error();
jsonReply->setSuccess(true);
authenticationReply->proxyClient()->setAuthenticated(true);
} else {
authenticationReply->proxyClient()->setAuthenticated(false);
jsonReply->setSuccess(false); jsonReply->setSuccess(false);
authenticationReply->proxyClient()->setAuthenticated(false);
} else {
// Successfully authenticated
authenticationReply->proxyClient()->setAuthenticated(true);
jsonReply->setSuccess(true);
} }
jsonReply->setData(errorToReply(authenticationReply->error())); jsonReply->setData(errorToReply(authenticationReply->error()));

View File

@ -65,9 +65,9 @@ JsonReply *JsonRpcServer::Hello(const QVariantMap &params, ProxyClient *proxyCli
Q_UNUSED(proxyClient) Q_UNUSED(proxyClient)
QVariantMap data; QVariantMap data;
data.insert("server", QCoreApplication::applicationName()); data.insert("server", SERVER_NAME_STRING);
data.insert("name", Engine::instance()->serverName()); data.insert("name", Engine::instance()->serverName());
data.insert("version", QCoreApplication::applicationVersion()); data.insert("version", SERVER_VERSION_STRING);
data.insert("apiVersion", API_VERSION_STRING); data.insert("apiVersion", API_VERSION_STRING);
return createReply(data); return createReply(data);
@ -182,12 +182,12 @@ void JsonRpcServer::asyncReplyFinished()
sendResponse(proxyClient, reply->commandId(), reply->data()); sendResponse(proxyClient, reply->commandId(), reply->data());
if (!reply->success()) { if (!reply->success()) {
// Disconnect this client since the request was not successfully // Disconnect this client since the request was not successfully
proxyClient->interface()->killClientConnection(proxyClient->clientId()); proxyClient->interface()->killClientConnection(proxyClient->clientId(), "API call was not successfully.");
} }
} else { } else {
sendErrorResponse(proxyClient, reply->commandId(), "Command timed out"); sendErrorResponse(proxyClient, reply->commandId(), "Command timed out");
// Disconnect this client since he requested something that created a timeout // Disconnect this client since he requested something that created a timeout
proxyClient->interface()->killClientConnection(proxyClient->clientId()); proxyClient->interface()->killClientConnection(proxyClient->clientId(), "API call timeouted.");
} }
} }
@ -225,7 +225,7 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data
if(error.error != QJsonParseError::NoError) { if(error.error != QJsonParseError::NoError) {
qCWarning(dcJsonRpc) << "Failed to parse JSON data" << data << ":" << error.errorString(); qCWarning(dcJsonRpc) << "Failed to parse JSON data" << data << ":" << error.errorString();
sendErrorResponse(proxyClient, -1, QString("Failed to parse JSON data: %1").arg(error.errorString())); sendErrorResponse(proxyClient, -1, QString("Failed to parse JSON data: %1").arg(error.errorString()));
proxyClient->interface()->killClientConnection(proxyClient->clientId()); proxyClient->interface()->killClientConnection(proxyClient->clientId(), "Invalid JSON data received.");
return; return;
} }
@ -236,7 +236,7 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data
if (!success) { if (!success) {
qCWarning(dcJsonRpc()) << "Error parsing command. Missing \"id\":" << message; qCWarning(dcJsonRpc()) << "Error parsing command. Missing \"id\":" << message;
sendErrorResponse(proxyClient, -1, "Error parsing command. Missing 'id'"); sendErrorResponse(proxyClient, -1, "Error parsing command. Missing 'id'");
proxyClient->interface()->killClientConnection(proxyClient->clientId()); proxyClient->interface()->killClientConnection(proxyClient->clientId(), "The id property is missing in the request.");
return; return;
} }
@ -244,7 +244,7 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data
if (commandList.count() != 2) { if (commandList.count() != 2) {
qCWarning(dcJsonRpc) << "Error parsing method.\nGot:" << message.value("method").toString() << "\nExpected: \"Namespace.method\""; 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())); sendErrorResponse(proxyClient, commandId, QString("Error parsing method. Got: '%1'', Expected: 'Namespace.method'").arg(message.value("method").toString()));
proxyClient->interface()->killClientConnection(proxyClient->clientId()); proxyClient->interface()->killClientConnection(proxyClient->clientId(), "Invalid method passed.");
return; return;
} }
@ -254,13 +254,13 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data
JsonHandler *handler = m_handlers.value(targetNamespace); JsonHandler *handler = m_handlers.value(targetNamespace);
if (!handler) { if (!handler) {
sendErrorResponse(proxyClient, commandId, "No such namespace"); sendErrorResponse(proxyClient, commandId, "No such namespace");
proxyClient->interface()->killClientConnection(proxyClient->clientId()); proxyClient->interface()->killClientConnection(proxyClient->clientId(), "No such namespace.");
return; return;
} }
if (!handler->hasMethod(method)) { if (!handler->hasMethod(method)) {
sendErrorResponse(proxyClient, commandId, "No such namespace"); sendErrorResponse(proxyClient, commandId, "No such method");
proxyClient->interface()->killClientConnection(proxyClient->clientId()); proxyClient->interface()->killClientConnection(proxyClient->clientId(), "No such method.");
return; return;
} }
@ -269,7 +269,7 @@ void JsonRpcServer::processData(ProxyClient *proxyClient, const QByteArray &data
QPair<bool, QString> validationResult = handler->validateParams(method, params); QPair<bool, QString> validationResult = handler->validateParams(method, params);
if (!validationResult.first) { if (!validationResult.first) {
sendErrorResponse(proxyClient, commandId, "Invalid params: " + validationResult.second); sendErrorResponse(proxyClient, commandId, "Invalid params: " + validationResult.second);
proxyClient->interface()->killClientConnection(proxyClient->clientId()); proxyClient->interface()->killClientConnection(proxyClient->clientId(), "Invalid params passed.");
return; return;
} }

View File

@ -100,7 +100,7 @@ void ProxyServer::onClientDataAvailable(const QUuid &clientId, const QByteArray
qCWarning(dcProxyServer()) << "An authenticated client sent data without tunnel connection. This is not allowed."; qCWarning(dcProxyServer()) << "An authenticated client sent data without tunnel connection. This is not allowed.";
m_jsonRpcServer->unregisterClient(proxyClient); m_jsonRpcServer->unregisterClient(proxyClient);
// The client is authenticated and tries to send data, this is not allowed. // The client is authenticated and tries to send data, this is not allowed.
proxyClient->interface()->killClientConnection(proxyClient->clientId()); proxyClient->interface()->killClientConnection(proxyClient->clientId(), "Data received while authenticated but not remote connected.");
return; return;
} }

View File

@ -17,7 +17,7 @@ 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; virtual void killClientConnection(const QUuid &clientId, const QString &killReason) = 0;
signals: signals:
void clientConnected(const QUuid &clientId); void clientConnected(const QUuid &clientId);

View File

@ -59,14 +59,14 @@ void WebSocketServer::sendData(const QList<QUuid> &clients, const QByteArray &da
} }
} }
void WebSocketServer::killClientConnection(const QUuid &clientId) void WebSocketServer::killClientConnection(const QUuid &clientId, const QString &killReason)
{ {
QWebSocket *client = m_clientList.value(clientId); QWebSocket *client = m_clientList.value(clientId);
if (!client) if (!client)
return; return;
qCWarning(dcWebSocketServer()) << "Kill client connection" << clientId.toString(); qCWarning(dcWebSocketServer()) << "Kill client connection" << clientId.toString() << "Reason:" << killReason;
client->close(QWebSocketProtocol::CloseCodeBadOperation); client->close(QWebSocketProtocol::CloseCodeBadOperation, killReason);
} }
void WebSocketServer::onClientConnected() void WebSocketServer::onClientConnected()

View File

@ -29,7 +29,7 @@ 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; void killClientConnection(const QUuid &clientId, const QString &killReason) override;
private: private:
QUrl m_serverUrl; QUrl m_serverUrl;

View File

@ -40,6 +40,7 @@ QVariantMap JsonReply::requestMap()
if (!m_params.isEmpty()) if (!m_params.isEmpty())
request.insert("params", m_params); request.insert("params", m_params);
m_commandId++;
return request; return request;
} }

View File

@ -8,37 +8,52 @@ Q_LOGGING_CATEGORY(dcRemoteProxyClientJsonRpcTraffic, "RemoteProxyClientJsonRpcT
namespace remoteproxyclient { namespace remoteproxyclient {
JsonRpcClient::JsonRpcClient(QObject *parent) : JsonRpcClient::JsonRpcClient(ProxyConnection *connection, QObject *parent) :
QObject(parent) QObject(parent),
m_connection(connection)
{ {
} }
JsonReply *JsonRpcClient::callHello() JsonReply *JsonRpcClient::callHello()
{ {
JsonReply *reply = new JsonReply(m_commandId, "RemoteProxy", "Hello", QVariantMap(), this); JsonReply *reply = new JsonReply(m_commandId, "RemoteProxy", "Hello", QVariantMap(), this);
qCDebug(dcRemoteProxyClientJsonRpc()) << "Calling" << QString("%1.%2").arg(reply->nameSpace()).arg(reply->method());
sendRequest(reply->requestMap());
m_replies.insert(m_commandId, reply);
return reply;
}
JsonReply *JsonRpcClient::callAuthenticate(const QUuid &clientUuid, const QString &clientName, const QString &token)
{
QVariantMap params;
params.insert("name", clientName);
params.insert("uuid", clientUuid.toString());
params.insert("token", token);
JsonReply *reply = new JsonReply(m_commandId, "Authentication", "Authenticate", params, this);
qCDebug(dcRemoteProxyClientJsonRpc()) << "Calling" << QString("%1.%2").arg(reply->nameSpace()).arg(reply->method()) << params;
sendRequest(reply->requestMap());
m_replies.insert(m_commandId, reply);
return reply; return reply;
} }
void JsonRpcClient::sendRequest(const QVariantMap &request) void JsonRpcClient::sendRequest(const QVariantMap &request)
{ {
m_connection->sendData(QJsonDocument::fromVariant(request).toJson(QJsonDocument::Compact) + '\n'); QByteArray data = QJsonDocument::fromVariant(request).toJson(QJsonDocument::Compact) + '\n';
qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Sending" << qUtf8Printable(data);
m_connection->sendData(data);
} }
void JsonRpcClient::onConnectedChanged(bool connected) void JsonRpcClient::onConnectedChanged(bool connected)
{ {
if (!connected) { Q_UNUSED(connected)
m_serverName = QString();
m_proxyServerName = QString();
m_proxyServerVersion = QString();
m_proxyApiVersion = QString();
}
} }
void JsonRpcClient::processData(const QByteArray &data) void JsonRpcClient::processData(const QByteArray &data)
{ {
qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << "Received data:" << qUtf8Printable(data);
QJsonParseError error; QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error); QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) { if (error.error != QJsonParseError::NoError) {
@ -52,15 +67,10 @@ void JsonRpcClient::processData(const QByteArray &data)
int commandId = dataMap.value("id").toInt(); int commandId = dataMap.value("id").toInt();
JsonReply *reply = m_replies.take(commandId); JsonReply *reply = m_replies.take(commandId);
if (reply) { if (reply) {
reply->deleteLater(); qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << QString("Got response for %1.%2: %3").arg(reply->nameSpace(), reply->method(), QString::fromUtf8(jsonDoc.toJson(QJsonDocument::Indented)));
qCDebug(dcRemoteProxyClientJsonRpcTraffic()) << QString("Got response for %1.%2: %3").arg(reply->nameSpace(),
reply->method(),
QString::fromUtf8(jsonDoc.toJson(QJsonDocument::Indented)));
if (dataMap.value("status").toString() == "error") { if (dataMap.value("status").toString() == "error") {
qCWarning(dcRemoteProxyClientJsonRpc()) << "Api error happend" << dataMap.value("error").toString(); qCWarning(dcRemoteProxyClientJsonRpc()) << "Api error happend" << dataMap.value("error").toString();
return;
} }
reply->setResponse(dataMap); reply->setResponse(dataMap);

View File

@ -1,6 +1,7 @@
#ifndef JSONRPCCLIENT_H #ifndef JSONRPCCLIENT_H
#define JSONRPCCLIENT_H #define JSONRPCCLIENT_H
#include <QUuid>
#include <QObject> #include <QObject>
#include <QVariantMap> #include <QVariantMap>
#include <QLoggingCategory> #include <QLoggingCategory>
@ -17,9 +18,10 @@ class JsonRpcClient : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit JsonRpcClient(QObject *parent = nullptr); explicit JsonRpcClient(ProxyConnection *connection, QObject *parent = nullptr);
JsonReply *callHello(); JsonReply *callHello();
JsonReply *callAuthenticate(const QUuid &clientUuid, const QString &clientName, const QString &token);
private: private:
ProxyConnection *m_connection = nullptr; ProxyConnection *m_connection = nullptr;
@ -28,11 +30,6 @@ private:
QHash<int, JsonReply *> m_replies; QHash<int, JsonReply *> m_replies;
QString m_serverName;
QString m_proxyServerName;
QString m_proxyServerVersion;
QString m_proxyApiVersion;
void sendRequest(const QVariantMap &request); void sendRequest(const QVariantMap &request);
signals: signals:

View File

@ -6,9 +6,11 @@ Q_LOGGING_CATEGORY(dcRemoteProxyClientConnectionTraffic, "RemoteProxyClientConne
namespace remoteproxyclient { namespace remoteproxyclient {
RemoteProxyConnection::RemoteProxyConnection(ConnectionType connectionType, QObject *parent) : RemoteProxyConnection::RemoteProxyConnection(const QUuid &clientUuid, const QString &clientName, ConnectionType connectionType, QObject *parent) :
QObject(parent), QObject(parent),
m_connectionType(connectionType) m_connectionType(connectionType),
m_clientUuid(clientUuid),
m_clientName(clientName)
{ {
} }
@ -54,7 +56,12 @@ QString RemoteProxyConnection::errorString() const
bool RemoteProxyConnection::isConnected() const bool RemoteProxyConnection::isConnected() const
{ {
return m_state == StateConnected || m_state == StateAuthenticating || m_state == StateWaitTunnel || m_state == StateRemoteConnected; return m_state == StateConnected
|| m_state == StateInitializing
|| m_state == StateReady
|| m_state == StateAuthenticating
|| m_state == StateWaitTunnel
|| m_state == StateRemoteConnected;
} }
bool RemoteProxyConnection::isRemoteConnected() const bool RemoteProxyConnection::isRemoteConnected() const
@ -77,6 +84,26 @@ quint16 RemoteProxyConnection::serverPort() const
return m_serverPort; return m_serverPort;
} }
QString RemoteProxyConnection::serverName() const
{
return m_serverName;
}
QString RemoteProxyConnection::proxyServerName() const
{
return m_proxyServerName;
}
QString RemoteProxyConnection::proxyServerVersion() const
{
return m_proxyServerVersion;
}
QString RemoteProxyConnection::proxyServerApiVersion() const
{
return m_proxyServerApiVersion;
}
bool RemoteProxyConnection::insecureConnection() const bool RemoteProxyConnection::insecureConnection() const
{ {
return m_insecureConnection; return m_insecureConnection;
@ -147,6 +174,7 @@ void RemoteProxyConnection::onConnectionChanged(bool isConnected)
setState(StateConnected); setState(StateConnected);
emit connected(); emit connected();
setState(StateInitializing);
JsonReply *reply = m_jsonClient->callHello(); JsonReply *reply = m_jsonClient->callHello();
connect(reply, &JsonReply::finished, this, &RemoteProxyConnection::onHelloFinished); connect(reply, &JsonReply::finished, this, &RemoteProxyConnection::onHelloFinished);
} }
@ -155,13 +183,20 @@ void RemoteProxyConnection::onConnectionChanged(bool isConnected)
void RemoteProxyConnection::onConnectionDataAvailable(const QByteArray &data) void RemoteProxyConnection::onConnectionDataAvailable(const QByteArray &data)
{ {
switch (m_state) { switch (m_state) {
case StateConnecting:
case StateConnected: case StateConnected:
case StateInitializing:
case StateReady:
case StateAuthenticating:
m_jsonClient->processData(data); m_jsonClient->processData(data);
break;
case StateRemoteConnected: case StateRemoteConnected:
// Remote data arrived // Remote data arrived
emit dataReady(data); emit dataReady(data);
break;
default: default:
qCDebug(dcRemoteProxyClientConnection()) << "Unhandled: Data reviced" << data; qCWarning(dcRemoteProxyClientConnection()) << "Unhandled: Data reviced" << data;
break;
} }
} }
@ -178,15 +213,35 @@ void RemoteProxyConnection::onConnectionSslError()
void RemoteProxyConnection::onHelloFinished() void RemoteProxyConnection::onHelloFinished()
{ {
JsonReply *reply = static_cast<JsonReply *>(sender()); JsonReply *reply = static_cast<JsonReply *>(sender());
reply->deleteLater();
QVariantMap response = reply->response(); QVariantMap response = reply->response();
qCDebug(dcRemoteProxyClientConnection()) << "Hello response ready" << response; qCDebug(dcRemoteProxyClientConnection()) << "Hello response ready" << response;
if (response.value("status").toString() != "success") {
qCWarning(dcRemoteProxyClientConnection()) << "Could not get initial information from proxy server.";
m_connection->disconnectServer();
return;
}
QVariantMap responseParams = response.value("params").toMap();
// TODO: verify success
m_serverName = response.value("server").toString();
m_proxyServerName = response.value("name").toString();
m_proxyServerVersion = response.value("version").toString();
m_proxyServerApiVersion = response.value("apiVersion").toString();
setState(StateReady);
emit ready();
} }
void RemoteProxyConnection::onAuthenticateFinished() void RemoteProxyConnection::onAuthenticateFinished()
{ {
JsonReply *reply = static_cast<JsonReply *>(sender()); JsonReply *reply = static_cast<JsonReply *>(sender());
QVariantMap response = reply->response(); QVariantMap response = reply->response();
qCDebug(dcRemoteProxyClientConnection()) << "Hello response ready" << response; qCDebug(dcRemoteProxyClientConnection()) << "Authentication response ready" << response;
} }
bool RemoteProxyConnection::connectServer(const QHostAddress &serverAddress, quint16 port) bool RemoteProxyConnection::connectServer(const QHostAddress &serverAddress, quint16 port)
@ -216,9 +271,9 @@ bool RemoteProxyConnection::connectServer(const QHostAddress &serverAddress, qui
connect(m_connection, &ProxyConnection::errorOccured, this, &RemoteProxyConnection::onConnectionSocketError); connect(m_connection, &ProxyConnection::errorOccured, this, &RemoteProxyConnection::onConnectionSocketError);
connect(m_connection, &ProxyConnection::sslErrorOccured, this, &RemoteProxyConnection::onConnectionSslError); connect(m_connection, &ProxyConnection::sslErrorOccured, this, &RemoteProxyConnection::onConnectionSslError);
m_jsonClient = new JsonRpcClient(this); m_jsonClient = new JsonRpcClient(m_connection, this);
qCDebug(dcRemoteProxyClientConnection()) << "Connecting to" << QString("%1:%2").arg(serverAddress.toString()).arg(port); qCDebug(dcRemoteProxyClientConnection()) << "Connecting to" << m_connection->serverUrl().toString();
m_connection->connectServer(serverAddress, port); m_connection->connectServer(serverAddress, port);
setState(StateConnecting); setState(StateConnecting);
@ -226,6 +281,19 @@ bool RemoteProxyConnection::connectServer(const QHostAddress &serverAddress, qui
return true; return true;
} }
bool RemoteProxyConnection::authenticate(const QString &token)
{
if (m_state != StateReady) {
qCWarning(dcRemoteProxyClientConnection()) << "Could not authenticate. The connection is not ready";
return false;
}
qCDebug(dcRemoteProxyClientConnection()) << "Start authentication using token" << token;
JsonReply *reply = m_jsonClient->callAuthenticate(m_clientUuid, m_clientName, token);
connect(reply, &JsonReply::finished, this, &RemoteProxyConnection::onAuthenticateFinished);
return true;
}
void RemoteProxyConnection::disconnectServer() void RemoteProxyConnection::disconnectServer()
{ {
if (m_connection) if (m_connection)

View File

@ -1,6 +1,7 @@
#ifndef REMOTEPROXYCONNECTOR_H #ifndef REMOTEPROXYCONNECTOR_H
#define REMOTEPROXYCONNECTOR_H #define REMOTEPROXYCONNECTOR_H
#include <QUuid>
#include <QDebug> #include <QDebug>
#include <QObject> #include <QObject>
#include <QWebSocket> #include <QWebSocket>
@ -27,6 +28,8 @@ public:
enum State { enum State {
StateConnecting, StateConnecting,
StateConnected, StateConnected,
StateInitializing,
StateReady,
StateAuthenticating, StateAuthenticating,
StateWaitTunnel, StateWaitTunnel,
StateRemoteConnected, StateRemoteConnected,
@ -43,11 +46,10 @@ public:
}; };
Q_ENUM(Error) Q_ENUM(Error)
explicit RemoteProxyConnection(ConnectionType connectionType = ConnectionTypeWebSocket, QObject *parent = nullptr); explicit RemoteProxyConnection(const QUuid &clientUuid, const QString &clientName, ConnectionType connectionType = ConnectionTypeWebSocket, QObject *parent = nullptr);
~RemoteProxyConnection(); ~RemoteProxyConnection();
RemoteProxyConnection::State state() const; RemoteProxyConnection::State state() const;
RemoteProxyConnection::Error error() const; RemoteProxyConnection::Error error() const;
QString errorString() const; QString errorString() const;
@ -58,6 +60,11 @@ public:
QHostAddress serverAddress() const; QHostAddress serverAddress() const;
quint16 serverPort() const; quint16 serverPort() const;
QString serverName() const;
QString proxyServerName() const;
QString proxyServerVersion() const;
QString proxyServerApiVersion() const;
bool insecureConnection() const; bool insecureConnection() const;
void setInsecureConnection(bool insecureConnection); void setInsecureConnection(bool insecureConnection);
@ -65,16 +72,27 @@ public:
private: private:
ConnectionType m_connectionType = ConnectionTypeWebSocket; ConnectionType m_connectionType = ConnectionTypeWebSocket;
QUuid m_clientUuid;
QString m_clientName;
QHostAddress m_serverAddress; QHostAddress m_serverAddress;
quint16 m_serverPort = 443; quint16 m_serverPort = 443;
State m_state = StateDisconnected; State m_state = StateDisconnected;
Error m_error = ErrorNoError; Error m_error = ErrorNoError;
bool m_insecureConnection = false; bool m_insecureConnection = false;
bool m_remoteConnected = false; bool m_remoteConnected = false;
JsonRpcClient *m_jsonClient = nullptr; JsonRpcClient *m_jsonClient = nullptr;
ProxyConnection *m_connection = nullptr; ProxyConnection *m_connection = nullptr;
// Server information
QString m_serverName;
QString m_proxyServerName;
QString m_proxyServerVersion;
QString m_proxyServerApiVersion;
void cleanUp(); void cleanUp();
void setState(State state); void setState(State state);
@ -83,6 +101,7 @@ private:
signals: signals:
void connected(); void connected();
void disconnected(); void disconnected();
void ready();
void remoteConnectedChanged(bool remoteConnected); void remoteConnectedChanged(bool remoteConnected);
void stateChanged(RemoteProxyConnection::State state); void stateChanged(RemoteProxyConnection::State state);
void errorOccured(RemoteProxyConnection::Error error); void errorOccured(RemoteProxyConnection::Error error);
@ -100,6 +119,7 @@ private slots:
public slots: public slots:
bool connectServer(const QHostAddress &serverAddress, quint16 port); bool connectServer(const QHostAddress &serverAddress, quint16 port);
bool authenticate(const QString &token);
void disconnectServer(); void disconnectServer();
}; };

View File

@ -7,10 +7,7 @@ namespace remoteproxyclient {
WebSocketConnection::WebSocketConnection(QObject *parent) : WebSocketConnection::WebSocketConnection(QObject *parent) :
ProxyConnection(parent) ProxyConnection(parent)
{ {
m_webSocket = new QWebSocket("libnymea-remoteproxyclient", QWebSocketProtocol::VersionLatest, this); m_webSocket = new QWebSocket("libnymea-remoteproxyclient", QWebSocketProtocol::Version13, this);
if (allowSslErrors())
m_webSocket->ignoreSslErrors();
connect(m_webSocket, &QWebSocket::connected, this, &WebSocketConnection::onConnected); connect(m_webSocket, &QWebSocket::connected, this, &WebSocketConnection::onConnected);
connect(m_webSocket, &QWebSocket::disconnected, this, &WebSocketConnection::onDisconnected); connect(m_webSocket, &QWebSocket::disconnected, this, &WebSocketConnection::onDisconnected);
@ -24,7 +21,7 @@ WebSocketConnection::WebSocketConnection(QObject *parent) :
WebSocketConnection::~WebSocketConnection() WebSocketConnection::~WebSocketConnection()
{ {
disconnectServer();
} }
QUrl WebSocketConnection::serverUrl() const QUrl WebSocketConnection::serverUrl() const
@ -69,6 +66,7 @@ void WebSocketConnection::onSslError(const QList<QSslError> &errors)
foreach (const QSslError sslError, errors) { foreach (const QSslError sslError, errors) {
qCWarning(dcRemoteProxyClientWebSocket()) << " -->" << static_cast<int>(sslError.error()) << sslError.errorString(); qCWarning(dcRemoteProxyClientWebSocket()) << " -->" << static_cast<int>(sslError.error()) << sslError.errorString();
} }
emit sslErrorOccured();
} }
} }
@ -89,10 +87,14 @@ void WebSocketConnection::onBinaryMessageReceived(const QByteArray &message)
void WebSocketConnection::connectServer(const QHostAddress &address, quint16 port) void WebSocketConnection::connectServer(const QHostAddress &address, quint16 port)
{ {
if (isConnected()) {
m_webSocket->close();
}
QUrl url; QUrl url;
url.setScheme("wss"); url.setScheme("wss");
url.setHost(address.toString()); url.setHost(address.toString());
url.port(port); url.setPort(port);
m_serverUrl = url; m_serverUrl = url;

View File

@ -1,10 +1,15 @@
QT *= network websockets QT *= network websockets
QT -= gui QT -= gui
# define protocol versions # Define versions
SERVER_NAME=nymea-remoteproxy
API_VERSION_MAJOR=0 API_VERSION_MAJOR=0
API_VERSION_MINOR=1 API_VERSION_MINOR=1
DEFINES += API_VERSION_STRING=\\\"$${API_VERSION_MAJOR}.$${API_VERSION_MINOR}\\\" SERVER_VERSION=0.0.1
DEFINES += SERVER_NAME_STRING=\\\"$${SERVER_NAME}\\\" \
SERVER_VERSION_STRING=\\\"$${SERVER_VERSION}\\\" \
API_VERSION_STRING=\\\"$${API_VERSION_MAJOR}.$${API_VERSION_MINOR}\\\"
CONFIG += c++11 console CONFIG += c++11 console
@ -15,8 +20,6 @@ top_srcdir=$$PWD
top_builddir=$$shadowed($$PWD) top_builddir=$$shadowed($$PWD)
coverage { coverage {
message("Building with coverage report")
# Note: this works only if you build in the source dir # Note: this works only if you build in the source dir
OBJECTS_DIR = OBJECTS_DIR =
MOC_DIR = MOC_DIR =

View File

@ -5,3 +5,11 @@ SUBDIRS += server libnymea-remoteproxy libnymea-remoteproxyclient tests
server.depends = libnymea-remoteproxy server.depends = libnymea-remoteproxy
tests.depends = libnymea-remoteproxy libnymea-remoteproxyclient tests.depends = libnymea-remoteproxy libnymea-remoteproxyclient
message("----------------------------------------------------------")
message("Building nymea-remoteproxy $${SERVER_VERSION}")
message("----------------------------------------------------------")
message("JSON-RPC API version $${API_VERSION_MAJOR}.$${API_VERSION_MINOR}")
message("Qt version:" $$[QT_VERSION])
coverage { message("Building with coverage report") }

View File

@ -85,9 +85,9 @@ int main(int argc, char *argv[])
qInstallMessageHandler(consoleLogHandler); qInstallMessageHandler(consoleLogHandler);
QCoreApplication application(argc, argv); QCoreApplication application(argc, argv);
application.setApplicationName("nymea-remoteproxy"); application.setApplicationName(SERVER_NAME_STRING);
application.setOrganizationName("guh"); application.setOrganizationName("guh");
application.setApplicationVersion("0.0.1"); application.setApplicationVersion(SERVER_VERSION_STRING);
s_loggingFilters.insert("Engine", true); s_loggingFilters.insert("Engine", true);
s_loggingFilters.insert("Application", true); s_loggingFilters.insert("Application", true);
@ -101,9 +101,15 @@ int main(int argc, char *argv[])
// command line parser // command line parser
QCommandLineParser parser; QCommandLineParser parser;
parser.addHelpOption(); parser.addHelpOption();
parser.addVersionOption();
parser.setApplicationDescription(QString("\nThe nymea remote proxy server. This server allowes nymea-cloud users and " parser.setApplicationDescription(QString("\nThe nymea remote proxy server. This server allowes nymea-cloud users and "
"registered nymea deamons to establish a tunnel connection.\n\n" "registered nymea deamons to establish a tunnel connection.\n\n"
"Copyright %1 2018 Simon Stürz <simon.stuerz@guh.io>").arg(QChar(0xA9))); "Server version: %1\n"
"API version: %2\n\n"
"Copyright %3 2018 Simon Stürz <simon.stuerz@guh.io>\n")
.arg(SERVER_VERSION_STRING)
.arg(API_VERSION_STRING)
.arg(QChar(0xA9)));
QCommandLineOption logfileOption(QStringList() << "l" << "logging", "Write log file to the given logfile.", QCommandLineOption logfileOption(QStringList() << "l" << "logging", "Write log file to the given logfile.",
"logfile", "/var/log/nymea-remoteproxy.log"); "logfile", "/var/log/nymea-remoteproxy.log");

View File

@ -112,21 +112,9 @@ QVariant RemoteProxyTests::invokeApiCall(const QString &method, const QVariantMa
return QVariant(); return QVariant();
} }
// QSignalSpy disconnectedSpy(socket, SIGNAL(disconnected()));
QSignalSpy dataSpy(socket, SIGNAL(textMessageReceived(QString))); QSignalSpy dataSpy(socket, SIGNAL(textMessageReceived(QString)));
socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact))); socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact)));
dataSpy.wait(); 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->close();
socket->deleteLater(); socket->deleteLater();
@ -365,14 +353,23 @@ void RemoteProxyTests::clientConnection()
startServer(); startServer();
// Connect to the server (insecure disabled) // Connect to the server (insecure disabled)
RemoteProxyConnection *connectorOne = new RemoteProxyConnection(RemoteProxyConnection::ConnectionTypeWebSocket, this); RemoteProxyConnection *connectorOne = new RemoteProxyConnection(QUuid::createUuid(), "Test client one", RemoteProxyConnection::ConnectionTypeWebSocket, this);
connectorOne->setInsecureConnection(true); connectorOne->setInsecureConnection(true);
// Connect to server (insecue enabled for testing) // Connect to server (insecue enabled for testing)
QSignalSpy spyConnected(connectorOne, &RemoteProxyConnection::connected); QSignalSpy readySpy(connectorOne, &RemoteProxyConnection::ready);
connectorOne->connectServer(QHostAddress::LocalHost, m_port); QVERIFY(connectorOne->connectServer(QHostAddress::LocalHost, m_port));
spyConnected.wait(); readySpy.wait();
//QVERIFY(connectorOne->isConnected()); QVERIFY(readySpy.count() == 1);
QVERIFY(connectorOne->isConnected());
QVERIFY(connectorOne->state() == RemoteProxyConnection::StateReady);
// Authenticate
m_authenticator->setTimeoutDuration(100);
m_authenticator->setExpectedAuthenticationError();
connectorOne->authenticate("foobar");
QTest::qWait(1000);
// Disconnect and clean up // Disconnect and clean up
QSignalSpy spyDisconnected(connectorOne, &RemoteProxyConnection::disconnected); QSignalSpy spyDisconnected(connectorOne, &RemoteProxyConnection::disconnected);

View File

@ -63,11 +63,12 @@ private slots:
void authenticate(); void authenticate();
// Client lib // Client lib
void clientConnection();
void sslConfigurations(); void sslConfigurations();
void timeout(); void timeout();
void clientConnection();
public slots: public slots:
void sslErrors(const QList<QSslError> &) { void sslErrors(const QList<QSslError> &) {
QWebSocket *socket = static_cast<QWebSocket*>(sender()); QWebSocket *socket = static_cast<QWebSocket*>(sender());