add JSONRPC.Hello method
it might happen that the auto-sent welcome message is lost. this is a way for the client to retrieve it again.
This commit is contained in:
parent
e468fc13d8
commit
0c4289b92c
@ -74,6 +74,20 @@ JsonRPCServer::JsonRPCServer(const QSslConfiguration &sslConfiguration, QObject
|
|||||||
QVariantMap returns;
|
QVariantMap returns;
|
||||||
QVariantMap params;
|
QVariantMap params;
|
||||||
|
|
||||||
|
params.clear(); returns.clear();
|
||||||
|
setDescription("Hello", "Upon first connection, guh will automatically send a welcome message containing information about the setup. If this message is lost for whatever reason (connections with multiple hops might drop this if guh sends it too early), the exact same message can be retrieved multiple times by calling this Hello method. Note that the contents might change if the system changed its state in the meantime, e.g. initialSetupRequired might turn false if the initial setup has been performed in the meantime.");
|
||||||
|
setParams("Hello", params);
|
||||||
|
returns.insert("id", JsonTypes::basicTypeToString(JsonTypes::Int));
|
||||||
|
returns.insert("server", JsonTypes::basicTypeToString(JsonTypes::String));
|
||||||
|
returns.insert("name", JsonTypes::basicTypeToString(JsonTypes::String));
|
||||||
|
returns.insert("version", JsonTypes::basicTypeToString(JsonTypes::String));
|
||||||
|
returns.insert("uuid", JsonTypes::basicTypeToString(JsonTypes::Uuid));
|
||||||
|
returns.insert("language", JsonTypes::basicTypeToString(JsonTypes::String));
|
||||||
|
returns.insert("protocol version", JsonTypes::basicTypeToString(JsonTypes::String));
|
||||||
|
returns.insert("initialSetupRequired", JsonTypes::basicTypeToString(JsonTypes::Bool));
|
||||||
|
returns.insert("authenticationRequired", JsonTypes::basicTypeToString(JsonTypes::Bool));
|
||||||
|
setReturns("Hello", returns);
|
||||||
|
|
||||||
params.clear(); returns.clear();
|
params.clear(); returns.clear();
|
||||||
setDescription("Introspect", "Introspect this API.");
|
setDescription("Introspect", "Introspect this API.");
|
||||||
setParams("Introspect", params);
|
setParams("Introspect", params);
|
||||||
@ -135,6 +149,13 @@ QString JsonRPCServer::name() const
|
|||||||
return QStringLiteral("JSONRPC");
|
return QStringLiteral("JSONRPC");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JsonReply *JsonRPCServer::Hello(const QVariantMap ¶ms) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(params);
|
||||||
|
TransportInterface *interface = reinterpret_cast<TransportInterface*>(property("transportInterface").toLongLong());
|
||||||
|
return createReply(createWelcomeMessage(interface));
|
||||||
|
}
|
||||||
|
|
||||||
JsonReply* JsonRPCServer::Introspect(const QVariantMap ¶ms) const
|
JsonReply* JsonRPCServer::Introspect(const QVariantMap ¶ms) const
|
||||||
{
|
{
|
||||||
Q_UNUSED(params)
|
Q_UNUSED(params)
|
||||||
@ -292,6 +313,21 @@ void JsonRPCServer::sendUnauthorizedResponse(TransportInterface *interface, cons
|
|||||||
interface->sendData(clientId, QJsonDocument::fromVariant(errorResponse).toJson(QJsonDocument::Compact));
|
interface->sendData(clientId, QJsonDocument::fromVariant(errorResponse).toJson(QJsonDocument::Compact));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariantMap JsonRPCServer::createWelcomeMessage(TransportInterface *interface) const
|
||||||
|
{
|
||||||
|
QVariantMap handshake;
|
||||||
|
handshake.insert("id", 0);
|
||||||
|
handshake.insert("server", "guhIO");
|
||||||
|
handshake.insert("name", GuhCore::instance()->configuration()->serverName());
|
||||||
|
handshake.insert("version", GUH_VERSION_STRING);
|
||||||
|
handshake.insert("uuid", GuhCore::instance()->configuration()->serverUuid().toString());
|
||||||
|
handshake.insert("language", GuhCore::instance()->configuration()->locale().name());
|
||||||
|
handshake.insert("protocol version", JSON_PROTOCOL_VERSION);
|
||||||
|
handshake.insert("initialSetupRequired", (interface->configuration().authenticationEnabled ? GuhCore::instance()->userManager()->users().isEmpty() : false));
|
||||||
|
handshake.insert("authenticationRequired", interface->configuration().authenticationEnabled);
|
||||||
|
return handshake;
|
||||||
|
}
|
||||||
|
|
||||||
void JsonRPCServer::setup()
|
void JsonRPCServer::setup()
|
||||||
{
|
{
|
||||||
registerHandler(this);
|
registerHandler(this);
|
||||||
@ -338,16 +374,16 @@ void JsonRPCServer::processData(const QUuid &clientId, const QByteArray &data)
|
|||||||
|
|
||||||
// check if authentication is required for this transport
|
// check if authentication is required for this transport
|
||||||
if (m_interfaces.value(interface)) {
|
if (m_interfaces.value(interface)) {
|
||||||
// if there is no user in the system yet, let's fail unless this is a CreateUser or Introspect call
|
// if there is no user in the system yet, let's fail unless this is a CreateUser, Introspect or Hello call
|
||||||
if (GuhCore::instance()->userManager()->users().isEmpty()) {
|
if (GuhCore::instance()->userManager()->users().isEmpty()) {
|
||||||
if (!(targetNamespace == "JSONRPC" && (method == "CreateUser" || method == "Introspect"))) {
|
if (!(targetNamespace == "JSONRPC" && (method == "CreateUser" || method == "Introspect" || method == "Hello"))) {
|
||||||
sendUnauthorizedResponse(interface, clientId, commandId, "Initial setup required. Call CreateUser first.");
|
sendUnauthorizedResponse(interface, clientId, commandId, "Initial setup required. Call CreateUser first.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// ok, we have a user. if there isn't a valid token, let's fail unless this is a Authenticate or Introspect call
|
// ok, we have a user. if there isn't a valid token, let's fail unless this is a Authenticate, Introspect or Hello call
|
||||||
QByteArray token = message.value("token").toByteArray();
|
QByteArray token = message.value("token").toByteArray();
|
||||||
if (!(targetNamespace == "JSONRPC" && (method == "Authenticate" || method == "Introspect")) && !GuhCore::instance()->userManager()->verifyToken(token)) {
|
if (!(targetNamespace == "JSONRPC" && (method == "Authenticate" || method == "Introspect" || method == "Hello")) && !GuhCore::instance()->userManager()->verifyToken(token)) {
|
||||||
sendUnauthorizedResponse(interface, clientId, commandId, "Forbidden: Invalid token.");
|
sendUnauthorizedResponse(interface, clientId, commandId, "Forbidden: Invalid token.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -373,9 +409,10 @@ void JsonRPCServer::processData(const QUuid &clientId, const QByteArray &data)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hack: attach clientId to handler to be able to handle the JSONRPC methods. Do not use this outside of jsonrpcserver
|
// Hack: attach some properties to the handler to be able to handle the JSONRPC methods. Do not use this outside of jsonrpcserver
|
||||||
handler->setProperty("clientId", clientId);
|
handler->setProperty("clientId", clientId);
|
||||||
handler->setProperty("token", message.value("token").toByteArray());
|
handler->setProperty("token", message.value("token").toByteArray());
|
||||||
|
handler->setProperty("transportInterface", reinterpret_cast<qint64>(interface));
|
||||||
|
|
||||||
qCDebug(dcJsonRpc()) << "Got method" << method.toLatin1().data();
|
qCDebug(dcJsonRpc()) << "Got method" << method.toLatin1().data();
|
||||||
|
|
||||||
@ -453,17 +490,7 @@ void JsonRPCServer::clientConnected(const QUuid &clientId)
|
|||||||
// If authentication is required, notifications are disabled by default. Clients must enable them with a valid token
|
// If authentication is required, notifications are disabled by default. Clients must enable them with a valid token
|
||||||
m_clients.insert(clientId, !interface->configuration().authenticationEnabled);
|
m_clients.insert(clientId, !interface->configuration().authenticationEnabled);
|
||||||
|
|
||||||
QVariantMap handshake;
|
interface->sendData(clientId, QJsonDocument::fromVariant(createWelcomeMessage(interface)).toJson(QJsonDocument::Compact));
|
||||||
handshake.insert("id", 0);
|
|
||||||
handshake.insert("server", "guhIO");
|
|
||||||
handshake.insert("name", GuhCore::instance()->configuration()->serverName());
|
|
||||||
handshake.insert("version", GUH_VERSION_STRING);
|
|
||||||
handshake.insert("uuid", GuhCore::instance()->configuration()->serverUuid().toString());
|
|
||||||
handshake.insert("language", GuhCore::instance()->configuration()->locale().name());
|
|
||||||
handshake.insert("protocol version", JSON_PROTOCOL_VERSION);
|
|
||||||
handshake.insert("initialSetupRequired", (interface->configuration().authenticationEnabled ? GuhCore::instance()->userManager()->users().isEmpty() : false));
|
|
||||||
handshake.insert("authenticationRequired", interface->configuration().authenticationEnabled);
|
|
||||||
interface->sendData(clientId, QJsonDocument::fromVariant(handshake).toJson(QJsonDocument::Compact));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void JsonRPCServer::clientDisconnected(const QUuid &clientId)
|
void JsonRPCServer::clientDisconnected(const QUuid &clientId)
|
||||||
|
|||||||
@ -47,6 +47,7 @@ public:
|
|||||||
|
|
||||||
// JsonHandler API implementation
|
// JsonHandler API implementation
|
||||||
QString name() const;
|
QString name() const;
|
||||||
|
Q_INVOKABLE JsonReply *Hello(const QVariantMap ¶ms) const;
|
||||||
Q_INVOKABLE JsonReply *Introspect(const QVariantMap ¶ms) const;
|
Q_INVOKABLE JsonReply *Introspect(const QVariantMap ¶ms) const;
|
||||||
Q_INVOKABLE JsonReply *Version(const QVariantMap ¶ms) const;
|
Q_INVOKABLE JsonReply *Version(const QVariantMap ¶ms) const;
|
||||||
Q_INVOKABLE JsonReply *SetNotificationStatus(const QVariantMap ¶ms);
|
Q_INVOKABLE JsonReply *SetNotificationStatus(const QVariantMap ¶ms);
|
||||||
@ -65,6 +66,7 @@ private:
|
|||||||
void sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap ¶ms = QVariantMap());
|
void sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap ¶ms = QVariantMap());
|
||||||
void sendErrorResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error);
|
void sendErrorResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error);
|
||||||
void sendUnauthorizedResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error);
|
void sendUnauthorizedResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error);
|
||||||
|
QVariantMap createWelcomeMessage(TransportInterface *interface) const;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void setup();
|
void setup();
|
||||||
|
|||||||
@ -96,6 +96,7 @@ QStringList TestJSONRPC::extractRefs(const QVariant &variant)
|
|||||||
|
|
||||||
void TestJSONRPC::testHandshake()
|
void TestJSONRPC::testHandshake()
|
||||||
{
|
{
|
||||||
|
// first test if the handshake message is auto-sent upon connecting
|
||||||
QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
|
QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
|
||||||
|
|
||||||
QUuid newClientId = QUuid::createUuid();
|
QUuid newClientId = QUuid::createUuid();
|
||||||
@ -108,6 +109,10 @@ void TestJSONRPC::testHandshake()
|
|||||||
QString guhVersionString(GUH_VERSION_STRING);
|
QString guhVersionString(GUH_VERSION_STRING);
|
||||||
QVERIFY2(handShake.value("version").toString() == guhVersionString, "Handshake version doesn't match Guh version.");
|
QVERIFY2(handShake.value("version").toString() == guhVersionString, "Handshake version doesn't match Guh version.");
|
||||||
|
|
||||||
|
// And now check if it is sent again when calling JSONRPC.Hello
|
||||||
|
handShake = injectAndWait("JSONRPC.Hello").toMap();
|
||||||
|
QCOMPARE(handShake.value("params").toMap().value("version").toString(), guhVersionString);
|
||||||
|
|
||||||
m_mockTcpServer->clientDisconnected(newClientId);
|
m_mockTcpServer->clientDisconnected(newClientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +138,18 @@ void TestJSONRPC::testInitialSetup()
|
|||||||
QCOMPARE(response.value("status").toString(), QStringLiteral("success"));
|
QCOMPARE(response.value("status").toString(), QStringLiteral("success"));
|
||||||
|
|
||||||
|
|
||||||
|
// Hello call should work in any case too
|
||||||
|
spy.clear();
|
||||||
|
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.Hello\"}");
|
||||||
|
if (spy.count() == 0) {
|
||||||
|
spy.wait();
|
||||||
|
}
|
||||||
|
QVERIFY(spy.count() == 1);
|
||||||
|
jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray());
|
||||||
|
response = jsonDoc.toVariant().toMap();
|
||||||
|
qWarning() << "Calling Hello on uninitialized instance:" << response.value("status").toString() << response.value("error").toString();
|
||||||
|
QCOMPARE(response.value("status").toString(), QStringLiteral("success"));
|
||||||
|
|
||||||
// Any other call should fail with "unauthorized" even if we use a previously valid token
|
// Any other call should fail with "unauthorized" even if we use a previously valid token
|
||||||
spy.clear();
|
spy.clear();
|
||||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"token\": \"" + m_apiToken + "\", \"method\": \"JSONRPC.Version\"}");
|
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"token\": \"" + m_apiToken + "\", \"method\": \"JSONRPC.Version\"}");
|
||||||
|
|||||||
Reference in New Issue
Block a user