Add optional the nonce parameter for authentication and update docs

This commit is contained in:
Simon Stürz 2018-08-31 22:28:03 +02:00
parent 2dcf219c5a
commit eacffb695f
16 changed files with 321 additions and 114 deletions

146
README.md
View File

@ -38,7 +38,7 @@ If you want to start the proxy server from the build directory, you need to expo
## From repository ## From repository
There is a public version available in the nymea repository. There is a public version available in the nymea repository.
$ apt install nymea-remoteproxy nymea-remoteproxy-client $ apt install nymea-remoteproxy nymea-remoteproxy-client nymea-remoteproxy-monitor
This will install a systemd service called `nymea-remoteproxy.service` and the client application for testing. This will install a systemd service called `nymea-remoteproxy.service` and the client application for testing.
@ -62,6 +62,11 @@ The package will deliver a default configuration file with following content (`/
inactiveTimeout=8000 inactiveTimeout=8000
aloneTimeout=8000 aloneTimeout=8000
[AWS]
region=eu-west-1
authorizerLambdaFunction=system-services-authorizer-dev-checkToken
awsCredentialsUrl=http://169.254.169.254/latest/meta-data/iam/security-credentials/EC2-Remote-Connection-Proxy-Role
[SSL] [SSL]
certificate=/etc/ssl/certs/ssl-cert-snakeoil.pem certificate=/etc/ssl/certs/ssl-cert-snakeoil.pem
certificateKey=/etc/ssl/private/ssl-cert-snakeoil.key certificateKey=/etc/ssl/private/ssl-cert-snakeoil.key
@ -103,8 +108,8 @@ In order to get information about the server you can start the command with the
The nymea remote proxy server. This server allowes nymea-cloud users and registered nymea deamons to establish a tunnel connection. The nymea remote proxy server. This server allowes nymea-cloud users and registered nymea deamons to establish a tunnel connection.
Version: 0.1.2 Version: 0.1.5
API version: 0.2 API version: 0.3
Copyright © 2018 Simon Stürz <simon.stuerz@guh.io> Copyright © 2018 Simon Stürz <simon.stuerz@guh.io>
@ -204,7 +209,7 @@ If anything goes wrong, or the tunnel partner disconnects from the proxy, the se
## Authenticate the connection ## Authenticate the connection
The first data a client **must** send to the proxy server is the authentication request. This request contains the token which will be verified agains the nymea-cloud infrastructure. The first data a client **must** send to the proxy server is the authentication request. This request contains the `token` which will be verified agains the nymea-cloud infrastructure and a `nonce` which has to be uniq for each connection attempt and shared between the 2 clients. The `uuid` should be a persistant uuid for this client and the name should make clear which type of connection this is and which client is connecting. The name and uuid will be sent to the tunnel partner during the tunnel establishmend.
#### Request #### Request
@ -215,6 +220,7 @@ The first data a client **must** send to the proxy server is the authentication
"uuid": "string", "uuid": "string",
"name": "string", "name": "string",
"token": "tokenstring" "token": "tokenstring"
"nonce": "nonce"
} }
} }
@ -263,75 +269,75 @@ Once the other client is here and ready, the server will send a notification to
#### Response #### Response
{ "id": 1,
"id": 0, "params": {
"params": { "methods": {
"methods": { "Authentication.Authenticate": {
"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.",
"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": {
"params": { "name": "String",
"name": "String", "o:nonce": "String",
"token": "String", "token": "String",
"uuid": "String" "uuid": "String"
},
"returns": {
"authenticationError": "$ref:AuthenticationError"
}
}, },
"RemoteProxy.Hello": { "returns": {
"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.", "authenticationError": "$ref:AuthenticationError"
"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.Hello": {
"RemoteProxy.TunnelEstablished": { "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.",
"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": {
"params": { },
"name": "String", "returns": {
"uuid": "String" "apiVersion": "String",
} "name": "String",
"server": "String",
"version": "String"
} }
}, },
"types": { "RemoteProxy.Introspect": {
"AuthenticationError": [ "description": "Introspect this API.",
"AuthenticationErrorNoError", "params": {
"AuthenticationErrorUnknown", },
"AuthenticationErrorTimeout", "returns": {
"AuthenticationErrorAborted", "methods": "Object",
"AuthenticationErrorAuthenticationFailed", "notifications": "Object",
"AuthenticationErrorAuthenticationServerNotResponding" "types": "Object"
], }
"BasicType": [
"Uuid",
"String",
"Int",
"UInt",
"Double",
"Bool",
"Variant",
"Object"
]
} }
}, },
"status": "success" "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",
"AuthenticationErrorProxyError"
],
"BasicType": [
"Uuid",
"String",
"Int",
"UInt",
"Double",
"Bool",
"Variant",
"Object"
]
}
},
"status": "success"
}
# Server monitor # Server monitor
@ -348,8 +354,8 @@ There is also the package `nymea-remoteproxy-monitor` package and application wh
The nymea remote proxy monitor allowes to monitor the live server activity on the a local instance. The nymea remote proxy monitor allowes to monitor the live server activity on the a local instance.
Server version: 0.1.2 Server version: 0.1.5
API version: 0.2 API version: 0.3
Copyright © 2018 Simon Stürz <simon.stuerz@guh.io> Copyright © 2018 Simon Stürz <simon.stuerz@guh.io>
@ -374,8 +380,8 @@ The client allowes you to test the proxy server and create a dummy client for te
The nymea remote proxy client application. This client allowes to test a server application as client perspective. The nymea remote proxy client application. This client allowes to test a server application as client perspective.
Version: 0.1.2 Version: 0.1.5
API version: 0.2 API version: 0.3
Copyright © 2018 Simon Stürz <simon.stuerz@guh.io> Copyright © 2018 Simon Stürz <simon.stuerz@guh.io>

View File

@ -43,6 +43,7 @@ AuthenticationHandler::AuthenticationHandler(QObject *parent) :
params.insert("uuid", JsonTypes::basicTypeToString(JsonTypes::String)); 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));
params.insert("o:nonce", 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);
@ -58,14 +59,16 @@ JsonReply *AuthenticationHandler::Authenticate(const QVariantMap &params, ProxyC
QString uuid = params.value("uuid").toString(); QString uuid = params.value("uuid").toString();
QString name = params.value("name").toString(); QString name = params.value("name").toString();
QString token = params.value("token").toString(); QString token = params.value("token").toString();
QString nonce = params.value("nonce").toString();
qCDebug(dcJsonRpc()) << "Authenticate:" << name << uuid << token; qCDebug(dcJsonRpc()) << "Authenticate:" << name << uuid << token << nonce;
JsonReply *jsonReply = createAsyncReply("Authenticate"); JsonReply *jsonReply = createAsyncReply("Authenticate");
// Set the token for this proxy client // Set the token for this proxy client
proxyClient->setUuid(uuid); proxyClient->setUuid(uuid);
proxyClient->setName(name); proxyClient->setName(name);
proxyClient->setToken(token); proxyClient->setToken(token);
proxyClient->setNonce(nonce);
AuthenticationReply *authReply = Engine::instance()->authenticator()->authenticate(proxyClient); AuthenticationReply *authReply = Engine::instance()->authenticator()->authenticate(proxyClient);
connect(authReply, &AuthenticationReply::finished, this, &AuthenticationHandler::onAuthenticationFinished); connect(authReply, &AuthenticationReply::finished, this, &AuthenticationHandler::onAuthenticationFinished);

View File

@ -123,6 +123,16 @@ void ProxyClient::setToken(const QString &token)
m_token = token; m_token = token;
} }
QString ProxyClient::nonce() const
{
return m_nonce;
}
void ProxyClient::setNonce(const QString &nonce)
{
m_nonce = nonce;
}
void ProxyClient::sendData(const QByteArray &data) void ProxyClient::sendData(const QByteArray &data)
{ {
if (!m_interface) if (!m_interface)

View File

@ -63,6 +63,9 @@ public:
QString token() const; QString token() const;
void setToken(const QString &token); void setToken(const QString &token);
QString nonce() const;
void setNonce(const QString &nonce);
// Actions for this client // Actions for this client
void sendData(const QByteArray &data); void sendData(const QByteArray &data);
void killConnection(const QString &reason); void killConnection(const QString &reason);
@ -81,6 +84,7 @@ private:
QString m_uuid; QString m_uuid;
QString m_name; QString m_name;
QString m_token; QString m_token;
QString m_nonce;
signals: signals:
void authenticated(); void authenticated();

View File

@ -119,13 +119,6 @@ ProxyClient *ProxyServer::getRemoteClient(ProxyClient *proxyClient)
return nullptr; return nullptr;
} }
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::establishTunnel(ProxyClient *firstClient, ProxyClient *secondClient) void ProxyServer::establishTunnel(ProxyClient *firstClient, ProxyClient *secondClient)
{ {
qCDebug(dcProxyServer()) << "Create tunnel between authenticated clients " << firstClient << secondClient; qCDebug(dcProxyServer()) << "Create tunnel between authenticated clients " << firstClient << secondClient;
@ -196,11 +189,16 @@ void ProxyServer::onClientDisconnected(const QUuid &clientId)
m_authenticatedClients.remove(proxyClient->token()); m_authenticatedClients.remove(proxyClient->token());
} }
if (m_authenticatedClientsNonce.values().contains(proxyClient)) {
m_authenticatedClientsNonce.remove(proxyClient->nonce());
}
// Unregister from json rpc server // Unregister from json rpc server
m_jsonRpcServer->unregisterClient(proxyClient); m_jsonRpcServer->unregisterClient(proxyClient);
// Check if // Check if
if (m_tunnels.contains(proxyClient->token())) { if (m_tunnels.contains(proxyClient->token())) {
// There is a tunnel connection for this client, remove the tunnel and disconnect also the other client // There is a tunnel connection for this client, remove the tunnel and disconnect also the other client
ProxyClient *remoteClient = getRemoteClient(proxyClient); ProxyClient *remoteClient = getRemoteClient(proxyClient);
m_tunnels.remove(proxyClient->token()); m_tunnels.remove(proxyClient->token());
@ -259,8 +257,10 @@ void ProxyServer::onProxyClientAuthenticated()
qCDebug(dcProxyServer()) << "Client authenticated" << proxyClient; qCDebug(dcProxyServer()) << "Client authenticated" << proxyClient;
// Check if we already have a tunnel for this token
if (m_tunnels.contains(proxyClient->token())) { if (m_tunnels.contains(proxyClient->token())) {
// A new connection attempt with the same token, kill the old tunnel connection and allow the new connection to stablish the tunnel // A new connection attempt with the same token, kill the old tunnel
// connection and allow the new connection to stablish the tunnel
qCWarning(dcProxyServer()) << "New authenticated client which already has a tunnel connection. Closing and clean up the old tunnel."; qCWarning(dcProxyServer()) << "New authenticated client which already has a tunnel connection. Closing and clean up the old tunnel.";
TunnelConnection tunnel = m_tunnels.take(proxyClient->token()); TunnelConnection tunnel = m_tunnels.take(proxyClient->token());
@ -269,24 +269,47 @@ void ProxyServer::onProxyClientAuthenticated()
tunnel.clientTwo()->killConnection("Clean up for new connection."); tunnel.clientTwo()->killConnection("Clean up for new connection.");
} }
// Check if we have an other authenticated client with this token // FIXME: for backwards compatibility
if (m_authenticatedClients.keys().contains(proxyClient->token())) { if (proxyClient->nonce().isEmpty()) {
// Found a client with this token // Check if we have an other authenticated client with this token
ProxyClient *tunnelEnd = m_authenticatedClients.take(proxyClient->token()); if (m_authenticatedClients.keys().contains(proxyClient->token())) {
// Check if the two clients show up with the same uuid // Found a client with this token
if (tunnelEnd->uuid() == proxyClient->uuid()) { ProxyClient *tunnelPartner = m_authenticatedClients.take(proxyClient->token());
qCWarning(dcProxyServer()) << "The clients have the same uuid. This is not allowed.";
proxyClient->killConnection("Duplicated client UUID."); // Check if the two clients show up with the same uuid to prevent connection loops
tunnelEnd->killConnection("Duplicated client UUID."); if (tunnelPartner->uuid() == proxyClient->uuid()) {
return; qCWarning(dcProxyServer()) << "The clients have the same uuid. This is not allowed.";
proxyClient->killConnection("Duplicated client UUID.");
tunnelPartner->killConnection("Duplicated client UUID.");
return;
}
// All ok so far. Create the tunnel
establishTunnel(tunnelPartner, proxyClient);
} else {
// Append and wait for the other client
m_authenticatedClients.insert(proxyClient->token(), proxyClient);
} }
// All ok so far. Create the tunnel
establishTunnel(tunnelEnd, proxyClient);
} else { } else {
// Append and wait for the other client // The client passed a nonce, let's hash with that to prevent cross connections
m_authenticatedClients.insert(proxyClient->token(), proxyClient); if (m_authenticatedClientsNonce.keys().contains(proxyClient->nonce())) {
// Found a client with this nonce
ProxyClient *tunnelPartner = m_authenticatedClientsNonce.take(proxyClient->nonce());
// Check if the two clients show up with the same uuid to prevent connection loops
if (tunnelPartner->uuid() == proxyClient->uuid()) {
qCWarning(dcProxyServer()) << "The clients have the same uuid. This could cause a loop and is not allowed.";
proxyClient->killConnection("Client loop detected.");
tunnelPartner->killConnection("Client loop detected.");
return;
}
// All ok so far. Create the tunnel
establishTunnel(tunnelPartner, proxyClient);
} else {
m_authenticatedClientsNonce.insert(proxyClient->nonce(), proxyClient);
}
} }
} }

View File

@ -54,9 +54,12 @@ private:
// Transport ClientId, ProxyClient // Transport ClientId, ProxyClient
QHash<QUuid, ProxyClient *> m_proxyClients; QHash<QUuid, ProxyClient *> m_proxyClients;
// Token, ProxyClient // FIXME: Token, ProxyClient
QHash<QString, ProxyClient *> m_authenticatedClients; QHash<QString, ProxyClient *> m_authenticatedClients;
// Nonce, ProxyClient
QHash<QString, ProxyClient *> m_authenticatedClientsNonce;
// Token, Tunnel // Token, Tunnel
QHash<QString, TunnelConnection> m_tunnels; QHash<QString, TunnelConnection> m_tunnels;
@ -64,8 +67,6 @@ private:
ProxyClient *getRemoteClient(ProxyClient *proxyClient); ProxyClient *getRemoteClient(ProxyClient *proxyClient);
void sendResponse(TransportInterface *interface, const QUuid &clientId, const QVariantMap &response = QVariantMap());
void establishTunnel(ProxyClient *firstClient, ProxyClient *secondClient); void establishTunnel(ProxyClient *firstClient, ProxyClient *secondClient);
signals: signals:

View File

@ -60,6 +60,11 @@ ProxyClient *TunnelConnection::clientTwo() const
return m_clientTwo; return m_clientTwo;
} }
bool TunnelConnection::hasClient(ProxyClient *proxyClient) const
{
return m_clientOne == proxyClient || m_clientTwo == proxyClient;
}
bool TunnelConnection::isValid() const bool TunnelConnection::isValid() const
{ {
// Both clients have to be valid // Both clients have to be valid

View File

@ -39,6 +39,8 @@ public:
ProxyClient *clientOne() const; ProxyClient *clientOne() const;
ProxyClient *clientTwo() const; ProxyClient *clientTwo() const;
bool hasClient(ProxyClient *proxyClient) const;
bool isValid() const; bool isValid() const;
private: private:

View File

@ -45,12 +45,13 @@ JsonReply *JsonRpcClient::callHello()
return reply; return reply;
} }
JsonReply *JsonRpcClient::callAuthenticate(const QUuid &clientUuid, const QString &clientName, const QString &token) JsonReply *JsonRpcClient::callAuthenticate(const QUuid &clientUuid, const QString &clientName, const QString &token, const QString &nonce)
{ {
QVariantMap params; QVariantMap params;
params.insert("name", clientName); params.insert("name", clientName);
params.insert("uuid", clientUuid.toString()); params.insert("uuid", clientUuid.toString());
params.insert("token", token); params.insert("token", token);
if (!nonce.isEmpty()) params.insert("nonce", nonce);
JsonReply *reply = new JsonReply(m_commandId, "Authentication", "Authenticate", params, this); JsonReply *reply = new JsonReply(m_commandId, "Authentication", "Authenticate", params, this);
qCDebug(dcRemoteProxyClientJsonRpc()) << "Calling" << QString("%1.%2").arg(reply->nameSpace()).arg(reply->method()); qCDebug(dcRemoteProxyClientJsonRpc()) << "Calling" << QString("%1.%2").arg(reply->nameSpace()).arg(reply->method());

View File

@ -42,7 +42,7 @@ public:
explicit JsonRpcClient(ProxyConnection *connection, 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); JsonReply *callAuthenticate(const QUuid &clientUuid, const QString &clientName, const QString &token, const QString &nonce);
private: private:
ProxyConnection *m_connection = nullptr; ProxyConnection *m_connection = nullptr;

View File

@ -340,7 +340,7 @@ bool RemoteProxyConnection::connectServer(const QUrl &url)
return true; return true;
} }
bool RemoteProxyConnection::authenticate(const QString &token) bool RemoteProxyConnection::authenticate(const QString &token, const QString &nonce)
{ {
if (m_state != StateReady) { if (m_state != StateReady) {
qCWarning(dcRemoteProxyClientConnection()) << "Could not authenticate. The connection is not ready"; qCWarning(dcRemoteProxyClientConnection()) << "Could not authenticate. The connection is not ready";
@ -349,8 +349,8 @@ bool RemoteProxyConnection::authenticate(const QString &token)
setState(StateAuthenticating); setState(StateAuthenticating);
qCDebug(dcRemoteProxyClientConnection()) << "Start authentication using token" << token; qCDebug(dcRemoteProxyClientConnection()) << "Start authentication using token" << token << nonce;
JsonReply *reply = m_jsonClient->callAuthenticate(m_clientUuid, m_clientName, token); JsonReply *reply = m_jsonClient->callAuthenticate(m_clientUuid, m_clientName, token, nonce);
connect(reply, &JsonReply::finished, this, &RemoteProxyConnection::onAuthenticateFinished); connect(reply, &JsonReply::finished, this, &RemoteProxyConnection::onAuthenticateFinished);
return true; return true;
} }

View File

@ -143,7 +143,7 @@ private slots:
public slots: public slots:
bool connectServer(const QUrl &url); bool connectServer(const QUrl &url);
bool authenticate(const QString &token); bool authenticate(const QString &token, const QString &nonce = QString());
void disconnectServer(); void disconnectServer();
bool sendData(const QByteArray &data); bool sendData(const QByteArray &data);
}; };

View File

@ -4,8 +4,8 @@ QT -= gui
# Define versions # Define versions
SERVER_NAME=nymea-remoteproxy SERVER_NAME=nymea-remoteproxy
API_VERSION_MAJOR=0 API_VERSION_MAJOR=0
API_VERSION_MINOR=2 API_VERSION_MINOR=3
SERVER_VERSION=0.1.4 SERVER_VERSION=0.1.5
DEFINES += SERVER_NAME_STRING=\\\"$${SERVER_NAME}\\\" \ DEFINES += SERVER_NAME_STRING=\\\"$${SERVER_NAME}\\\" \
SERVER_VERSION_STRING=\\\"$${SERVER_VERSION}\\\" \ SERVER_VERSION_STRING=\\\"$${SERVER_VERSION}\\\" \

View File

@ -279,23 +279,31 @@ void RemoteProxyOfflineTests::authenticate_data()
QTest::addColumn<QString>("uuid"); QTest::addColumn<QString>("uuid");
QTest::addColumn<QString>("name"); QTest::addColumn<QString>("name");
QTest::addColumn<QString>("token"); QTest::addColumn<QString>("token");
QTest::addColumn<QString>("nonce");
QTest::addColumn<int>("timeout"); QTest::addColumn<int>("timeout");
QTest::addColumn<Authenticator::AuthenticationError>("expectedError"); QTest::addColumn<Authenticator::AuthenticationError>("expectedError");
QTest::newRow("success") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken QTest::newRow("success") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken << ""
<< 100 << Authenticator::AuthenticationErrorNoError; << 100 << Authenticator::AuthenticationErrorNoError;
QTest::newRow("failed") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken QTest::newRow("success") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken << "nonce"
<< 100 << Authenticator::AuthenticationErrorNoError;
QTest::newRow("success") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken << "nonce"
<< 100 << Authenticator::AuthenticationErrorAuthenticationFailed;
QTest::newRow("failed") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken << ""
<< 100 << Authenticator::AuthenticationErrorAuthenticationFailed; << 100 << Authenticator::AuthenticationErrorAuthenticationFailed;
QTest::newRow("not responding") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken QTest::newRow("not responding") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken << ""
<< 200 << Authenticator::AuthenticationErrorProxyError; << 200 << Authenticator::AuthenticationErrorProxyError;
QTest::newRow("aborted") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken QTest::newRow("aborted") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken << ""
<< 100 << Authenticator::AuthenticationErrorAborted; << 100 << Authenticator::AuthenticationErrorAborted;
QTest::newRow("unknown") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken QTest::newRow("unknown") << QUuid::createUuid().toString() << "Testclient, hello form the test!" << m_testToken << ""
<< 100 << Authenticator::AuthenticationErrorUnknown; << 100 << Authenticator::AuthenticationErrorUnknown;
} }
@ -305,6 +313,7 @@ void RemoteProxyOfflineTests::authenticate()
QFETCH(QString, uuid); QFETCH(QString, uuid);
QFETCH(QString, name); QFETCH(QString, name);
QFETCH(QString, token); QFETCH(QString, token);
QFETCH(QString, nonce);
QFETCH(int, timeout); QFETCH(int, timeout);
QFETCH(Authenticator::AuthenticationError, expectedError); QFETCH(Authenticator::AuthenticationError, expectedError);
@ -320,6 +329,7 @@ void RemoteProxyOfflineTests::authenticate()
params.insert("uuid", uuid); params.insert("uuid", uuid);
params.insert("name", name); params.insert("name", name);
params.insert("token", token); params.insert("token", token);
if (!nonce.isEmpty()) params.insert("nonce", nonce);
QVariant response = invokeApiCall("Authentication.Authenticate", params); QVariant response = invokeApiCall("Authentication.Authenticate", params);
qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented));
@ -329,6 +339,145 @@ void RemoteProxyOfflineTests::authenticate()
stopServer(); stopServer();
} }
void RemoteProxyOfflineTests::authenticateNonce()
{
// Start the server
startServer();
QString nonce = "67af856c4e4071833ed01128e50b3ea5";
QString token = "C001L0ckingT0ken";
// Configure moch authenticator
m_mockAuthenticator->setTimeoutDuration(100);
m_mockAuthenticator->setExpectedAuthenticationError();
QString nameConnectionOne = "Test client one";
QUuid uuidConnectionOne = QUuid::createUuid();
QString nameConnectionTwo = "Test client two";
QUuid uuidConnectionTwo = QUuid::createUuid();
QByteArray dataOne = "Hello from client one :-)";
QByteArray dataTwo = "Hello from client two :-)";
// Create two connection
RemoteProxyConnection *connectionOne = new RemoteProxyConnection(uuidConnectionOne, nameConnectionOne, this);
connect(connectionOne, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
RemoteProxyConnection *connectionTwo = new RemoteProxyConnection(uuidConnectionTwo, nameConnectionTwo, this);
connect(connectionTwo, &RemoteProxyConnection::sslErrors, this, &BaseTest::ignoreConnectionSslError);
// Connect one
QSignalSpy connectionOneReadySpy(connectionOne, &RemoteProxyConnection::ready);
QVERIFY(connectionOne->connectServer(m_serverUrl));
connectionOneReadySpy.wait();
QVERIFY(connectionOneReadySpy.count() == 1);
QVERIFY(connectionOne->isConnected());
// Connect two
QSignalSpy connectionTwoReadySpy(connectionTwo, &RemoteProxyConnection::ready);
QVERIFY(connectionTwo->connectServer(m_serverUrl));
connectionTwoReadySpy.wait();
QVERIFY(connectionTwoReadySpy.count() == 1);
QVERIFY(connectionTwo->isConnected());
// Authenticate one
QSignalSpy remoteConnectionEstablishedOne(connectionOne, &RemoteProxyConnection::remoteConnectionEstablished);
QSignalSpy connectionOneAuthenticatedSpy(connectionOne, &RemoteProxyConnection::authenticated);
QVERIFY(connectionOne->authenticate(m_testToken, nonce));
connectionOneAuthenticatedSpy.wait();
QVERIFY(connectionOneAuthenticatedSpy.count() == 1);
QVERIFY(connectionOne->isConnected());
QVERIFY(connectionOne->isAuthenticated());
QVERIFY(connectionOne->state() == RemoteProxyConnection::StateAuthenticated);
// Authenticate two
QSignalSpy remoteConnectionEstablishedTwo(connectionTwo, &RemoteProxyConnection::remoteConnectionEstablished);
QSignalSpy connectionTwoAuthenticatedSpy(connectionTwo, &RemoteProxyConnection::authenticated);
QVERIFY(connectionTwo->authenticate(m_testToken, nonce));
connectionTwoAuthenticatedSpy.wait();
QVERIFY(connectionTwoAuthenticatedSpy.count() == 1);
QVERIFY(connectionTwo->isConnected());
QVERIFY(connectionTwo->isAuthenticated());
// Wait for both to be connected
remoteConnectionEstablishedOne.wait(500);
remoteConnectionEstablishedTwo.wait(500);
QVERIFY(remoteConnectionEstablishedOne.count() == 1);
QVERIFY(remoteConnectionEstablishedTwo.count() == 1);
QVERIFY(connectionOne->state() == RemoteProxyConnection::StateRemoteConnected);
QVERIFY(connectionTwo->state() == RemoteProxyConnection::StateRemoteConnected);
QCOMPARE(connectionOne->tunnelPartnerName(), nameConnectionTwo);
QCOMPARE(connectionOne->tunnelPartnerUuid(), uuidConnectionTwo.toString());
QCOMPARE(connectionTwo->tunnelPartnerName(), nameConnectionOne);
QCOMPARE(connectionTwo->tunnelPartnerUuid(), uuidConnectionOne.toString());
// Pipe data trought the tunnel
QSignalSpy remoteConnectionDataOne(connectionOne, &RemoteProxyConnection::dataReady);
QSignalSpy remoteConnectionDataTwo(connectionTwo, &RemoteProxyConnection::dataReady);
connectionOne->sendData(dataOne);
remoteConnectionDataTwo.wait(500);
QVERIFY(remoteConnectionDataTwo.count() == 1);
QCOMPARE(remoteConnectionDataTwo.at(0).at(0).toByteArray().trimmed(), dataOne);
connectionTwo->sendData(dataTwo);
remoteConnectionDataOne.wait(500);
QVERIFY(remoteConnectionDataOne.count() == 1);
QCOMPARE(remoteConnectionDataOne.at(0).at(0).toByteArray().trimmed(), dataTwo);
connectionOne->deleteLater();
connectionTwo->deleteLater();
// Clean up
stopServer();
}
void RemoteProxyOfflineTests::authenticateSendData()
{
// Start the server
startServer();
QVariantMap params;
params.insert("uuid", "uuid");
params.insert("name", "name");
params.insert("token", "token");
params.insert("nonce", "nonce");
QVariantMap request;
request.insert("id", m_commandCounter);
request.insert("method", "Authentication.Authenticate");
request.insert("params", params);
QJsonDocument jsonDoc = QJsonDocument::fromVariant(request);
// Connect socket
QWebSocket *socket = new QWebSocket("proxy-testclient", QWebSocketProtocol::Version13);
connect(socket, &QWebSocket::sslErrors, this, &BaseTest::sslErrors);
QSignalSpy spyConnection(socket, SIGNAL(connected()));
socket->open(Engine::instance()->webSocketServer()->serverUrl());
spyConnection.wait();
QVERIFY(spyConnection.count() == 1);
// Authenticate
QSignalSpy dataSpy(socket, SIGNAL(textMessageReceived(QString)));
socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact)));
dataSpy.wait();
QVERIFY(dataSpy.count() == 1);
// Send data and make sure we get disconnected
QSignalSpy disconnectedSpy(socket, SIGNAL(disconnected()));
socket->sendTextMessage(QString(jsonDoc.toJson(QJsonDocument::Compact)));
disconnectedSpy.wait();
QVERIFY(disconnectedSpy.count() == 1);
socket->deleteLater();
// Clean up
stopServer();
}
void RemoteProxyOfflineTests::clientConnection() void RemoteProxyOfflineTests::clientConnection()
{ {
// Start the server // Start the server
@ -384,7 +533,7 @@ void RemoteProxyOfflineTests::remoteConnection()
// Start the server // Start the server
startServer(); startServer();
// Configure moch authenticator // Configure mock authenticator
m_mockAuthenticator->setTimeoutDuration(100); m_mockAuthenticator->setTimeoutDuration(100);
m_mockAuthenticator->setExpectedAuthenticationError(); m_mockAuthenticator->setExpectedAuthenticationError();

View File

@ -58,6 +58,9 @@ private slots:
void authenticate_data(); void authenticate_data();
void authenticate(); void authenticate();
void authenticateNonce();
void authenticateSendData();
// Client lib // Client lib
void clientConnection(); void clientConnection();
void remoteConnection(); void remoteConnection();

View File

@ -53,7 +53,7 @@ void MockAuthenticator::replyFinished()
setReplyError(reply->authenticationReply(), reply->error()); setReplyError(reply->authenticationReply(), reply->error());
setReplyFinished(reply->authenticationReply()); setReplyFinished(reply->authenticationReply());
delete reply; reply->deleteLater();
} }
AuthenticationReply *MockAuthenticator::authenticate(ProxyClient *proxyClient) AuthenticationReply *MockAuthenticator::authenticate(ProxyClient *proxyClient)