fix initialSetupRequired still being true, even if we already gave out tokens by pushbuttonAuth
This commit is contained in:
parent
6608748f83
commit
44dd09d227
@ -436,7 +436,7 @@ QVariantMap JsonRPCServer::createWelcomeMessage(TransportInterface *interface) c
|
||||
handshake.insert("uuid", NymeaCore::instance()->configuration()->serverUuid().toString());
|
||||
handshake.insert("language", NymeaCore::instance()->configuration()->locale().name());
|
||||
handshake.insert("protocol version", JSON_PROTOCOL_VERSION);
|
||||
handshake.insert("initialSetupRequired", (interface->configuration().authenticationEnabled ? NymeaCore::instance()->userManager()->users().isEmpty() : false));
|
||||
handshake.insert("initialSetupRequired", (interface->configuration().authenticationEnabled ? NymeaCore::instance()->userManager()->initRequired() : false));
|
||||
handshake.insert("authenticationRequired", interface->configuration().authenticationEnabled);
|
||||
handshake.insert("pushButtonAuthAvailable", NymeaCore::instance()->userManager()->pushButtonAuthAvailable());
|
||||
return handshake;
|
||||
@ -497,7 +497,7 @@ void JsonRPCServer::processData(const QUuid &clientId, const QByteArray &data)
|
||||
QStringList authExemptMethodsNoUser = {"Introspect", "Hello", "CreateUser", "RequestPushButtonAuth"};
|
||||
QStringList authExemptMethodsWithUser = {"Introspect", "Hello", "Authenticate", "RequestPushButtonAuth"};
|
||||
// if there is no user in the system yet, let's fail unless this is special method for authentication itself
|
||||
if (NymeaCore::instance()->userManager()->users().isEmpty()) {
|
||||
if (NymeaCore::instance()->userManager()->initRequired()) {
|
||||
if (!(targetNamespace == "JSONRPC" && authExemptMethodsNoUser.contains(method)) && (token.isEmpty() || !NymeaCore::instance()->userManager()->verifyToken(token))) {
|
||||
sendUnauthorizedResponse(interface, clientId, commandId, "Initial setup required. Call CreateUser first.");
|
||||
return;
|
||||
|
||||
@ -51,6 +51,23 @@ UserManager::UserManager(QObject *parent) : QObject(parent)
|
||||
m_pushButtonTransaction = qMakePair<int, QString>(-1, QString());
|
||||
}
|
||||
|
||||
/** Will return true if the database is working fine but doesn't have any information on users whatsoever.
|
||||
* That is, neither a user nor an anonimous token.
|
||||
* This may be used to determine whether a first-time setup is required.
|
||||
*/
|
||||
bool UserManager::initRequired() const
|
||||
{
|
||||
QString getTokensQuery = QString("SELECT id, username, creationdate, deviceName FROM tokens;");
|
||||
QSqlQuery result = m_db.exec(getTokensQuery);
|
||||
if (m_db.lastError().type() != QSqlError::NoError) {
|
||||
qCWarning(dcUserManager) << "Query for tokens failed:" << m_db.lastError().databaseText() << m_db.lastError().driverText() << getTokensQuery;
|
||||
// Note: do not return true in case the database access fails.
|
||||
return false;
|
||||
}
|
||||
|
||||
return users().isEmpty() && !result.first();
|
||||
}
|
||||
|
||||
QStringList UserManager::users() const
|
||||
{
|
||||
QString userQuery("SELECT username FROM users;");
|
||||
@ -96,16 +113,22 @@ UserManager::UserError UserManager::createUser(const QString &username, const QS
|
||||
return UserErrorNoError;
|
||||
}
|
||||
|
||||
/** Remove the given user and all of its tokens. If the username is empty, all anonymous tokens (e.g. issued by pushbutton auth) will be cleared. */
|
||||
UserManager::UserError UserManager::removeUser(const QString &username)
|
||||
{
|
||||
QString dropUserQuery = QString("DELETE FROM users WHERE lower(username) =\"%1\";").arg(username.toLower());
|
||||
QSqlQuery result = m_db.exec(dropUserQuery);
|
||||
if (result.numRowsAffected() == 0) {
|
||||
return UserErrorInvalidUserId;
|
||||
}
|
||||
if (!username.isEmpty()) {
|
||||
QString dropUserQuery = QString("DELETE FROM users WHERE lower(username) =\"%1\";").arg(username.toLower());
|
||||
QSqlQuery result = m_db.exec(dropUserQuery);
|
||||
if (result.numRowsAffected() == 0) {
|
||||
return UserErrorInvalidUserId;
|
||||
}
|
||||
|
||||
QString dropTokensQuery = QString("DELETE FROM tokens WHERE lower(username) = \"%1\";").arg(username.toLower());
|
||||
m_db.exec(dropTokensQuery);
|
||||
QString dropTokensQuery = QString("DELETE FROM tokens WHERE lower(username) = \"%1\";").arg(username.toLower());
|
||||
m_db.exec(dropTokensQuery);
|
||||
} else {
|
||||
QString dropTokensQuery = QString("DELETE FROM tokens WHERE username = \"\";").arg(username.toLower());
|
||||
m_db.exec(dropTokensQuery);
|
||||
}
|
||||
|
||||
return UserErrorNoError;
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@ public:
|
||||
|
||||
explicit UserManager(QObject *parent = 0);
|
||||
|
||||
bool initRequired() const;
|
||||
QStringList users() const;
|
||||
|
||||
UserError createUser(const QString &username, const QString &password);
|
||||
|
||||
@ -99,6 +99,7 @@ private slots:
|
||||
|
||||
void testPushButtonAuthConnectionDrop();
|
||||
|
||||
void testInitialSetupWithPushButtonAuth();
|
||||
private:
|
||||
QStringList extractRefs(const QVariant &variant);
|
||||
|
||||
@ -169,6 +170,9 @@ void TestJSONRPC::testInitialSetup()
|
||||
foreach (const QString &user, NymeaCore::instance()->userManager()->users()) {
|
||||
NymeaCore::instance()->userManager()->removeUser(user);
|
||||
}
|
||||
NymeaCore::instance()->userManager()->removeUser("");
|
||||
|
||||
QVERIFY(NymeaCore::instance()->userManager()->initRequired());
|
||||
QCOMPARE(NymeaCore::instance()->userManager()->users().count(), 0);
|
||||
|
||||
QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
|
||||
@ -197,6 +201,7 @@ void TestJSONRPC::testInitialSetup()
|
||||
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"));
|
||||
QCOMPARE(response.value("params").toMap().value("initialSetupRequired").toBool(), true);
|
||||
|
||||
// Any other call should fail with "unauthorized" even if we use a previously valid token
|
||||
spy.clear();
|
||||
@ -251,6 +256,19 @@ void TestJSONRPC::testInitialSetup()
|
||||
QCOMPARE(response.value("status").toString(), QStringLiteral("success"));
|
||||
QCOMPARE(NymeaCore::instance()->userManager()->users().count(), 1);
|
||||
|
||||
// Now that we have a user, initialSetup should be false in the Hello call
|
||||
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 initialized instance:" << response.value("status").toString() << response.value("error").toString();
|
||||
QCOMPARE(response.value("status").toString(), QStringLiteral("success"));
|
||||
QCOMPARE(response.value("params").toMap().value("initialSetupRequired").toBool(), false);
|
||||
|
||||
// Calls should still fail, given we didn't get a new token yet
|
||||
spy.clear();
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"token\": \"" + m_apiToken + "\", \"method\": \"JSONRPC.Version\"}");
|
||||
@ -1075,7 +1093,7 @@ void TestJSONRPC::testPushButtonAuthConnectionDrop()
|
||||
QUuid bobId = QUuid::createUuid();
|
||||
m_mockTcpServer->clientConnected(bobId);
|
||||
|
||||
// request push button auth for client 1 (alice) and check for OK reply
|
||||
// request push button auth for client 2 (bob) and check for OK reply
|
||||
params.clear();
|
||||
params.insert("deviceName", "bob");
|
||||
response = injectAndWait("JSONRPC.RequestPushButtonAuth", params, bobId);
|
||||
@ -1102,6 +1120,89 @@ void TestJSONRPC::testPushButtonAuthConnectionDrop()
|
||||
|
||||
}
|
||||
|
||||
void TestJSONRPC::testInitialSetupWithPushButtonAuth()
|
||||
{
|
||||
foreach (const QString &user, NymeaCore::instance()->userManager()->users()) {
|
||||
NymeaCore::instance()->userManager()->removeUser(user);
|
||||
}
|
||||
NymeaCore::instance()->userManager()->removeUser("");
|
||||
QVERIFY(NymeaCore::instance()->userManager()->initRequired());
|
||||
|
||||
QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
|
||||
QVERIFY(spy.isValid());
|
||||
|
||||
PushButtonAgent pushButtonAgent;
|
||||
pushButtonAgent.init();
|
||||
|
||||
// Hello call should work in any case, telling us initial setup is required
|
||||
spy.clear();
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.Hello\"}");
|
||||
if (spy.count() == 0) {
|
||||
spy.wait();
|
||||
}
|
||||
QVERIFY(spy.count() == 1);
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray());
|
||||
QVariant response = jsonDoc.toVariant();
|
||||
qWarning() << "Calling Hello on uninitialized instance:" << response.toMap().value("status").toString() << response.toMap().value("error").toString();
|
||||
QCOMPARE(response.toMap().value("status").toString(), QStringLiteral("success"));
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("initialSetupRequired").toBool(), true);
|
||||
|
||||
// request push button auth for alice and check for OK reply
|
||||
QUuid aliceId = QUuid::createUuid();
|
||||
m_mockTcpServer->clientConnected(aliceId);
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("deviceName", "alice");
|
||||
response = injectAndWait("JSONRPC.RequestPushButtonAuth", params, aliceId);
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("success").toBool(), true);
|
||||
int transactionId = response.toMap().value("params").toMap().value("transactionId").toInt();
|
||||
|
||||
spy.clear();
|
||||
pushButtonAgent.sendButtonPressed();
|
||||
|
||||
// Wait for things to happen
|
||||
if (spy.count() == 0) {
|
||||
spy.wait();
|
||||
}
|
||||
|
||||
// There should have been only exactly one message sent, the token for alice
|
||||
QCOMPARE(spy.count(), 1);
|
||||
QVariantMap notification = QJsonDocument::fromJson(spy.first().at(1).toByteArray()).toVariant().toMap();
|
||||
QCOMPARE(spy.first().first().toUuid(), aliceId);
|
||||
QCOMPARE(notification.value("notification").toString(), QLatin1String("JSONRPC.PushButtonAuthFinished"));
|
||||
QCOMPARE(notification.value("params").toMap().value("transactionId").toInt(), transactionId);
|
||||
QCOMPARE(notification.value("params").toMap().value("success").toBool(), true);
|
||||
QVERIFY2(!notification.value("params").toMap().value("token").toByteArray().isEmpty(), "Token is empty while it shouldn't be");
|
||||
|
||||
// initialSetupRequired should be false in Hello call now
|
||||
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();
|
||||
qWarning() << "Calling Hello on uninitialized instance:" << response.toMap().value("status").toString() << response.toMap().value("error").toString();
|
||||
QCOMPARE(response.toMap().value("status").toString(), QStringLiteral("success"));
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("initialSetupRequired").toBool(), false);
|
||||
|
||||
|
||||
// CreateUser without a token should fail now even though there are 0 users in the DB
|
||||
spy.clear();
|
||||
m_mockTcpServer->injectData(m_clientId, "{\"id\": 555, \"method\": \"JSONRPC.CreateUser\", \"params\": {\"username\": \"Dummy@guh.io\", \"password\": \"DummyPW1!\"}}");
|
||||
if (spy.count() == 0) {
|
||||
spy.wait();
|
||||
}
|
||||
QVERIFY(spy.count() == 1);
|
||||
jsonDoc = QJsonDocument::fromJson(spy.first().at(1).toByteArray());
|
||||
response = jsonDoc.toVariant();
|
||||
qWarning() << "Calling CreateUser on uninitialized instance:" << response.toMap().value("status").toString() << response.toMap().value("error").toString();
|
||||
QCOMPARE(response.toMap().value("status").toString(), QStringLiteral("unauthorized"));
|
||||
QCOMPARE(NymeaCore::instance()->userManager()->users().count(), 0);
|
||||
|
||||
}
|
||||
|
||||
#include "testjsonrpc.moc"
|
||||
|
||||
QTEST_MAIN(TestJSONRPC)
|
||||
|
||||
Reference in New Issue
Block a user