From eacffb695f8870c75b027a701277f6ec4455d327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 31 Aug 2018 22:28:03 +0200 Subject: [PATCH] Add optional the nonce parameter for authentication and update docs --- README.md | 150 ++++++++-------- .../jsonrpc/authenticationhandler.cpp | 5 +- libnymea-remoteproxy/proxyclient.cpp | 10 ++ libnymea-remoteproxy/proxyclient.h | 4 + libnymea-remoteproxy/proxyserver.cpp | 69 +++++--- libnymea-remoteproxy/proxyserver.h | 7 +- libnymea-remoteproxy/tunnelconnection.cpp | 5 + libnymea-remoteproxy/tunnelconnection.h | 2 + .../proxyjsonrpcclient.cpp | 3 +- .../proxyjsonrpcclient.h | 2 +- .../remoteproxyconnection.cpp | 6 +- .../remoteproxyconnection.h | 2 +- nymea-remoteproxy.pri | 4 +- .../nymea-remoteproxy-tests-offline.cpp | 161 +++++++++++++++++- .../nymea-remoteproxy-tests-offline.h | 3 + tests/testbase/mockauthenticator.cpp | 2 +- 16 files changed, 321 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 21e0c0f..98fb2ed 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ If you want to start the proxy server from the build directory, you need to expo ## From 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. @@ -62,6 +62,11 @@ The package will deliver a default configuration file with following content (`/ inactiveTimeout=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] certificate=/etc/ssl/certs/ssl-cert-snakeoil.pem certificateKey=/etc/ssl/private/ssl-cert-snakeoil.key @@ -74,7 +79,7 @@ The package will deliver a default configuration file with following content (`/ [TcpServer] host=127.0.0.1 port=80 - + # Test @@ -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. - Version: 0.1.2 - API version: 0.2 + Version: 0.1.5 + API version: 0.3 Copyright © 2018 Simon Stürz @@ -124,7 +129,7 @@ In order to get information about the server you can start the command with the ~/.config/nymea/nymea-remoteproxy.conf --verbose Print more verbose. - + # Server API Once a client connects to the proxy server, he must authenticate him self by passing the token received from the nymea-cloud mqtt connection request. @@ -204,7 +209,7 @@ If anything goes wrong, or the tunnel partner disconnects from the proxy, the se ## 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 @@ -215,6 +220,7 @@ The first data a client **must** send to the proxy server is the authentication "uuid": "string", "name": "string", "token": "tokenstring" + "nonce": "nonce" } } @@ -263,75 +269,75 @@ Once the other client is here and ready, the server will send a notification to #### Response - { - "id": 0, - "params": { - "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" - } + "id": 1, + "params": { + "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", + "o:nonce": "String", + "token": "String", + "uuid": "String" }, - "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" - } + "returns": { + "authenticationError": "$ref:AuthenticationError" } }, - "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" - } + "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" } }, - "types": { - "AuthenticationError": [ - "AuthenticationErrorNoError", - "AuthenticationErrorUnknown", - "AuthenticationErrorTimeout", - "AuthenticationErrorAborted", - "AuthenticationErrorAuthenticationFailed", - "AuthenticationErrorAuthenticationServerNotResponding" - ], - "BasicType": [ - "Uuid", - "String", - "Int", - "UInt", - "Double", - "Bool", - "Variant", - "Object" - ] + "RemoteProxy.Introspect": { + "description": "Introspect this API.", + "params": { + }, + "returns": { + "methods": "Object", + "notifications": "Object", + "types": "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 @@ -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. - Server version: 0.1.2 - API version: 0.2 + Server version: 0.1.5 + API version: 0.3 Copyright © 2018 Simon Stürz @@ -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. - Version: 0.1.2 - API version: 0.2 + Version: 0.1.5 + API version: 0.3 Copyright © 2018 Simon Stürz diff --git a/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp b/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp index 3fc1d85..75ab875 100644 --- a/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp +++ b/libnymea-remoteproxy/jsonrpc/authenticationhandler.cpp @@ -43,6 +43,7 @@ AuthenticationHandler::AuthenticationHandler(QObject *parent) : params.insert("uuid", JsonTypes::basicTypeToString(JsonTypes::String)); params.insert("name", JsonTypes::basicTypeToString(JsonTypes::String)); params.insert("token", JsonTypes::basicTypeToString(JsonTypes::String)); + params.insert("o:nonce", JsonTypes::basicTypeToString(JsonTypes::String)); setParams("Authenticate", params); returns.insert("authenticationError", JsonTypes::authenticationErrorRef()); setReturns("Authenticate", returns); @@ -58,14 +59,16 @@ JsonReply *AuthenticationHandler::Authenticate(const QVariantMap ¶ms, ProxyC QString uuid = params.value("uuid").toString(); QString name = params.value("name").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"); // Set the token for this proxy client proxyClient->setUuid(uuid); proxyClient->setName(name); proxyClient->setToken(token); + proxyClient->setNonce(nonce); AuthenticationReply *authReply = Engine::instance()->authenticator()->authenticate(proxyClient); connect(authReply, &AuthenticationReply::finished, this, &AuthenticationHandler::onAuthenticationFinished); diff --git a/libnymea-remoteproxy/proxyclient.cpp b/libnymea-remoteproxy/proxyclient.cpp index 404b1f6..81bbf08 100644 --- a/libnymea-remoteproxy/proxyclient.cpp +++ b/libnymea-remoteproxy/proxyclient.cpp @@ -123,6 +123,16 @@ void ProxyClient::setToken(const QString &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) { if (!m_interface) diff --git a/libnymea-remoteproxy/proxyclient.h b/libnymea-remoteproxy/proxyclient.h index 9042127..665562f 100644 --- a/libnymea-remoteproxy/proxyclient.h +++ b/libnymea-remoteproxy/proxyclient.h @@ -63,6 +63,9 @@ public: QString token() const; void setToken(const QString &token); + QString nonce() const; + void setNonce(const QString &nonce); + // Actions for this client void sendData(const QByteArray &data); void killConnection(const QString &reason); @@ -81,6 +84,7 @@ private: QString m_uuid; QString m_name; QString m_token; + QString m_nonce; signals: void authenticated(); diff --git a/libnymea-remoteproxy/proxyserver.cpp b/libnymea-remoteproxy/proxyserver.cpp index 02a2157..f839d9b 100644 --- a/libnymea-remoteproxy/proxyserver.cpp +++ b/libnymea-remoteproxy/proxyserver.cpp @@ -119,13 +119,6 @@ ProxyClient *ProxyServer::getRemoteClient(ProxyClient *proxyClient) 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) { qCDebug(dcProxyServer()) << "Create tunnel between authenticated clients " << firstClient << secondClient; @@ -196,11 +189,16 @@ void ProxyServer::onClientDisconnected(const QUuid &clientId) m_authenticatedClients.remove(proxyClient->token()); } + if (m_authenticatedClientsNonce.values().contains(proxyClient)) { + m_authenticatedClientsNonce.remove(proxyClient->nonce()); + } + // Unregister from json rpc server m_jsonRpcServer->unregisterClient(proxyClient); // Check if if (m_tunnels.contains(proxyClient->token())) { + // There is a tunnel connection for this client, remove the tunnel and disconnect also the other client ProxyClient *remoteClient = getRemoteClient(proxyClient); m_tunnels.remove(proxyClient->token()); @@ -259,8 +257,10 @@ void ProxyServer::onProxyClientAuthenticated() qCDebug(dcProxyServer()) << "Client authenticated" << proxyClient; + // Check if we already have a tunnel for this 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."; TunnelConnection tunnel = m_tunnels.take(proxyClient->token()); @@ -269,24 +269,47 @@ void ProxyServer::onProxyClientAuthenticated() tunnel.clientTwo()->killConnection("Clean up for new connection."); } - // Check if we have an other authenticated client with this token - if (m_authenticatedClients.keys().contains(proxyClient->token())) { - // Found a client with this token - ProxyClient *tunnelEnd = m_authenticatedClients.take(proxyClient->token()); + // FIXME: for backwards compatibility + if (proxyClient->nonce().isEmpty()) { + // Check if we have an other authenticated client with this token + if (m_authenticatedClients.keys().contains(proxyClient->token())) { - // Check if the two clients show up with the same uuid - if (tunnelEnd->uuid() == proxyClient->uuid()) { - qCWarning(dcProxyServer()) << "The clients have the same uuid. This is not allowed."; - proxyClient->killConnection("Duplicated client UUID."); - tunnelEnd->killConnection("Duplicated client UUID."); - return; + // Found a client with this token + ProxyClient *tunnelPartner = m_authenticatedClients.take(proxyClient->token()); + + // 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 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 { - // Append and wait for the other client - m_authenticatedClients.insert(proxyClient->token(), proxyClient); + // The client passed a nonce, let's hash with that to prevent cross connections + 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); + } } } diff --git a/libnymea-remoteproxy/proxyserver.h b/libnymea-remoteproxy/proxyserver.h index 37f4d3f..9fb83aa 100644 --- a/libnymea-remoteproxy/proxyserver.h +++ b/libnymea-remoteproxy/proxyserver.h @@ -54,9 +54,12 @@ private: // Transport ClientId, ProxyClient QHash m_proxyClients; - // Token, ProxyClient + // FIXME: Token, ProxyClient QHash m_authenticatedClients; + // Nonce, ProxyClient + QHash m_authenticatedClientsNonce; + // Token, Tunnel QHash m_tunnels; @@ -64,8 +67,6 @@ private: ProxyClient *getRemoteClient(ProxyClient *proxyClient); - void sendResponse(TransportInterface *interface, const QUuid &clientId, const QVariantMap &response = QVariantMap()); - void establishTunnel(ProxyClient *firstClient, ProxyClient *secondClient); signals: diff --git a/libnymea-remoteproxy/tunnelconnection.cpp b/libnymea-remoteproxy/tunnelconnection.cpp index 8a76986..6269f09 100644 --- a/libnymea-remoteproxy/tunnelconnection.cpp +++ b/libnymea-remoteproxy/tunnelconnection.cpp @@ -60,6 +60,11 @@ ProxyClient *TunnelConnection::clientTwo() const return m_clientTwo; } +bool TunnelConnection::hasClient(ProxyClient *proxyClient) const +{ + return m_clientOne == proxyClient || m_clientTwo == proxyClient; +} + bool TunnelConnection::isValid() const { // Both clients have to be valid diff --git a/libnymea-remoteproxy/tunnelconnection.h b/libnymea-remoteproxy/tunnelconnection.h index f8c376a..8180d2e 100644 --- a/libnymea-remoteproxy/tunnelconnection.h +++ b/libnymea-remoteproxy/tunnelconnection.h @@ -39,6 +39,8 @@ public: ProxyClient *clientOne() const; ProxyClient *clientTwo() const; + bool hasClient(ProxyClient *proxyClient) const; + bool isValid() const; private: diff --git a/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp b/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp index dacacaf..f28a3ef 100644 --- a/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp +++ b/libnymea-remoteproxyclient/proxyjsonrpcclient.cpp @@ -45,12 +45,13 @@ JsonReply *JsonRpcClient::callHello() 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; params.insert("name", clientName); params.insert("uuid", clientUuid.toString()); params.insert("token", token); + if (!nonce.isEmpty()) params.insert("nonce", nonce); JsonReply *reply = new JsonReply(m_commandId, "Authentication", "Authenticate", params, this); qCDebug(dcRemoteProxyClientJsonRpc()) << "Calling" << QString("%1.%2").arg(reply->nameSpace()).arg(reply->method()); diff --git a/libnymea-remoteproxyclient/proxyjsonrpcclient.h b/libnymea-remoteproxyclient/proxyjsonrpcclient.h index 148f788..89b99da 100644 --- a/libnymea-remoteproxyclient/proxyjsonrpcclient.h +++ b/libnymea-remoteproxyclient/proxyjsonrpcclient.h @@ -42,7 +42,7 @@ public: explicit JsonRpcClient(ProxyConnection *connection, QObject *parent = nullptr); 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: ProxyConnection *m_connection = nullptr; diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.cpp b/libnymea-remoteproxyclient/remoteproxyconnection.cpp index 3dbe25e..0d3ba40 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.cpp +++ b/libnymea-remoteproxyclient/remoteproxyconnection.cpp @@ -340,7 +340,7 @@ bool RemoteProxyConnection::connectServer(const QUrl &url) return true; } -bool RemoteProxyConnection::authenticate(const QString &token) +bool RemoteProxyConnection::authenticate(const QString &token, const QString &nonce) { if (m_state != StateReady) { qCWarning(dcRemoteProxyClientConnection()) << "Could not authenticate. The connection is not ready"; @@ -349,8 +349,8 @@ bool RemoteProxyConnection::authenticate(const QString &token) setState(StateAuthenticating); - qCDebug(dcRemoteProxyClientConnection()) << "Start authentication using token" << token; - JsonReply *reply = m_jsonClient->callAuthenticate(m_clientUuid, m_clientName, token); + qCDebug(dcRemoteProxyClientConnection()) << "Start authentication using token" << token << nonce; + JsonReply *reply = m_jsonClient->callAuthenticate(m_clientUuid, m_clientName, token, nonce); connect(reply, &JsonReply::finished, this, &RemoteProxyConnection::onAuthenticateFinished); return true; } diff --git a/libnymea-remoteproxyclient/remoteproxyconnection.h b/libnymea-remoteproxyclient/remoteproxyconnection.h index f47e966..2de8f1c 100644 --- a/libnymea-remoteproxyclient/remoteproxyconnection.h +++ b/libnymea-remoteproxyclient/remoteproxyconnection.h @@ -143,7 +143,7 @@ private slots: public slots: bool connectServer(const QUrl &url); - bool authenticate(const QString &token); + bool authenticate(const QString &token, const QString &nonce = QString()); void disconnectServer(); bool sendData(const QByteArray &data); }; diff --git a/nymea-remoteproxy.pri b/nymea-remoteproxy.pri index 255f287..f976111 100644 --- a/nymea-remoteproxy.pri +++ b/nymea-remoteproxy.pri @@ -4,8 +4,8 @@ QT -= gui # Define versions SERVER_NAME=nymea-remoteproxy API_VERSION_MAJOR=0 -API_VERSION_MINOR=2 -SERVER_VERSION=0.1.4 +API_VERSION_MINOR=3 +SERVER_VERSION=0.1.5 DEFINES += SERVER_NAME_STRING=\\\"$${SERVER_NAME}\\\" \ SERVER_VERSION_STRING=\\\"$${SERVER_VERSION}\\\" \ diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp index cbb0636..eef6296 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.cpp +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.cpp @@ -279,23 +279,31 @@ void RemoteProxyOfflineTests::authenticate_data() QTest::addColumn("uuid"); QTest::addColumn("name"); QTest::addColumn("token"); + QTest::addColumn("nonce"); QTest::addColumn("timeout"); QTest::addColumn("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; - 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; - 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; - 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; - 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; } @@ -305,6 +313,7 @@ void RemoteProxyOfflineTests::authenticate() QFETCH(QString, uuid); QFETCH(QString, name); QFETCH(QString, token); + QFETCH(QString, nonce); QFETCH(int, timeout); QFETCH(Authenticator::AuthenticationError, expectedError); @@ -320,6 +329,7 @@ void RemoteProxyOfflineTests::authenticate() params.insert("uuid", uuid); params.insert("name", name); params.insert("token", token); + if (!nonce.isEmpty()) params.insert("nonce", nonce); QVariant response = invokeApiCall("Authentication.Authenticate", params); qDebug() << qUtf8Printable(QJsonDocument::fromVariant(response).toJson(QJsonDocument::Indented)); @@ -329,6 +339,145 @@ void RemoteProxyOfflineTests::authenticate() 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() { // Start the server @@ -384,7 +533,7 @@ void RemoteProxyOfflineTests::remoteConnection() // Start the server startServer(); - // Configure moch authenticator + // Configure mock authenticator m_mockAuthenticator->setTimeoutDuration(100); m_mockAuthenticator->setExpectedAuthenticationError(); diff --git a/tests/test-offline/nymea-remoteproxy-tests-offline.h b/tests/test-offline/nymea-remoteproxy-tests-offline.h index bbf5384..28b0d2a 100644 --- a/tests/test-offline/nymea-remoteproxy-tests-offline.h +++ b/tests/test-offline/nymea-remoteproxy-tests-offline.h @@ -58,6 +58,9 @@ private slots: void authenticate_data(); void authenticate(); + void authenticateNonce(); + void authenticateSendData(); + // Client lib void clientConnection(); void remoteConnection(); diff --git a/tests/testbase/mockauthenticator.cpp b/tests/testbase/mockauthenticator.cpp index 95dd8bf..cea9385 100644 --- a/tests/testbase/mockauthenticator.cpp +++ b/tests/testbase/mockauthenticator.cpp @@ -53,7 +53,7 @@ void MockAuthenticator::replyFinished() setReplyError(reply->authenticationReply(), reply->error()); setReplyFinished(reply->authenticationReply()); - delete reply; + reply->deleteLater(); } AuthenticationReply *MockAuthenticator::authenticate(ProxyClient *proxyClient)