From 543d2089b3d6d883f147eb7ba153bf6fe1eeb7af Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 18 Sep 2024 11:59:53 +0200 Subject: [PATCH 01/11] Room::updateData(): fix a dangling pointer This was introduced in #799, only accidentally passing through tests. --- Quotient/room.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Quotient/room.cpp b/Quotient/room.cpp index 20772c9d7..1a63da7c6 100644 --- a/Quotient/room.cpp +++ b/Quotient/room.cpp @@ -1874,9 +1874,9 @@ void Room::updateData(SyncRoomData&& data, bool fromCache) // allowed to do everything. // The entire defaultPowerLevels event gets replaced in order to maintain its constness // everywhere else. - std::exchange(d->defaultPowerLevels, - std::make_unique(PowerLevelsEventContent{ - .users = { { creation()->senderId(), 100 } } })); + d->defaultPowerLevels = std::make_unique( + PowerLevelsEventContent{ .users = { { creation()->senderId(), 100 } } }); + d->currentState[{ RoomPowerLevelsEvent::TypeId, {} }] = d->defaultPowerLevels.get(); } // First test for changes that can only come from /sync calls and not From c2974ad7aadbf59498d34a920320e0ce45b1a0fe Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 18 Sep 2024 21:09:31 +0200 Subject: [PATCH 02/11] Fix incorrect location reported by QUO_ALARM[_X] --- Quotient/util.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quotient/util.h b/Quotient/util.h index 5f2177f94..901bb263e 100644 --- a/Quotient/util.h +++ b/Quotient/util.h @@ -64,7 +64,7 @@ inline bool alarmX(bool alarmCondition, const auto& msg, [[maybe_unused]] std::source_location loc = std::source_location::current()) { if (alarmCondition) [[unlikely]] { - Q_ASSERT_X(false, loc.function_name(), QUO_CSTR(msg)); + qt_assert_x(loc.function_name(), QUO_CSTR(msg), loc.file_name(), loc.line()); qCritical() << msg; } return alarmCondition; From bd744de9ec3251cbc617c019005b353500f2ac92 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Wed, 18 Sep 2024 21:24:32 +0200 Subject: [PATCH 03/11] Deprecate Connection::isUsable() The name is bad: a connection logged in via assumeIdentity() is perfectly usable yet doesn't necessarily have login flows loaded as the funciton implementation assumes. --- Quotient/connection.h | 1 + quotest/quotest.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Quotient/connection.h b/Quotient/connection.h index 91ee80778..5713c8040 100644 --- a/Quotient/connection.h +++ b/Quotient/connection.h @@ -293,6 +293,7 @@ class QUOTIENT_API Connection : public QObject { //! Get the domain name used for ids/aliases on the server QString domain() const; //! Check if the homeserver is known to be reachable and working + [[deprecated("Check the result returned by Connection::loginFlows() instead")]] bool isUsable() const; //! Get the list of supported login flows QVector loginFlows() const; diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index c8cc4e49d..fc3b0407a 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -913,7 +913,7 @@ void TestManager::conclude() void TestManager::finalize(const QString& lastWords) { - if (!c->isUsable() || !c->isLoggedIn()) { + if (!c->isLoggedIn()) { clog << "No usable connection reached" << endl; QCoreApplication::exit(-2); return; // NB: QCoreApplication::exit() does return to the caller From ff23038484c0a004805e3bad0e68a6fdc01de3bb Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 19 Sep 2024 08:43:04 +0200 Subject: [PATCH 04/11] Templated BaseJob::run() Will be used in the next commit to rewrite TestOlmAccount. --- Quotient/connection.h | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Quotient/connection.h b/Quotient/connection.h index 5713c8040..b0324b686 100644 --- a/Quotient/connection.h +++ b/Quotient/connection.h @@ -496,8 +496,21 @@ class QUOTIENT_API Connection : public QObject { void setLazyLoading(bool newValue); //! Start a pre-created job object on this connection - Q_INVOKABLE BaseJob* run(BaseJob* job, - RunningPolicy runningPolicy = ForegroundRequest); + Q_INVOKABLE BaseJob* run(BaseJob* job, RunningPolicy runningPolicy = ForegroundRequest); + + //! \brief Start a pre-created job on this connection and get a job handler to it + //! + //! This is a template overload for run(BaseJob*, RunningPolicy) - if you call run() on any + //! derived job (99% of the cases when you're going to call it), this overload will be chosen + //! as a more type-safe and feature-rich version. It's not Q_INVOKABLE though. + template JobT> + requires (!std::same_as) + JobHandle run(JobT* job, RunningPolicy runningPolicy = ForegroundRequest) + { + JobHandle jh { job }; + run(static_cast(job), runningPolicy); + return jh; + } //! \brief Start a job of a given type with specified arguments and policy //! @@ -513,9 +526,7 @@ class QUOTIENT_API Connection : public QObject { template JobHandle callApi(RunningPolicy runningPolicy, JobArgTs&&... jobArgs) { - auto job = new JobT(std::forward(jobArgs)...); - run(job, runningPolicy); - return job; + return run(new JobT(std::forward(jobArgs)...), runningPolicy); } //! \brief Start a job of a specified type with specified arguments From f737df741e21a742b5fa506df95dabf832452fad Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 19 Sep 2024 14:25:51 +0200 Subject: [PATCH 05/11] TestOlmAccount: rewrite with futures; ease timeouts In some situations Synapse takes a lot of time and sometimes even fails to respond to a request at once; to alleviate that, most timeouts are increased from 10 to 40 seconds, to give time for a retry. And to unify the waiting code, Quotient::waitForFuture() is introduced that internally uses QTest::qWaitFor() that is more lightweight than QSignalSpy. --- autotests/testolmaccount.cpp | 308 ++++++++++++++++------------------- autotests/testutils.h | 6 + 2 files changed, 147 insertions(+), 167 deletions(-) diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index 762038015..9c79cc127 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -210,15 +210,13 @@ void TestOlmAccount::uploadOneTimeKeys() for (const auto& [keyId, key] : oneTimeKeys.curve25519().asKeyValueRange()) oneTimeKeysHash["curve25519:"_L1 + keyId] = key; - auto request = new UploadKeysJob({}, oneTimeKeysHash); - connect(request, &BaseJob::result, this, [request] { - if (!request->status().good()) - QFAIL("upload failed"); - QCOMPARE(request->oneTimeKeyCounts().value(Curve25519Key), 5); - }); - conn->run(request); - QSignalSpy spy3(request, &BaseJob::result); - QVERIFY(spy3.wait(10000)); + const auto uploadFuture = conn->callApi(std::nullopt, oneTimeKeysHash) + .then( + [](const QHash& oneTimeKeyCounts) { + QCOMPARE(oneTimeKeyCounts.value(Curve25519Key), 5); + }, + [] { QFAIL("upload failed"); }); + QVERIFY(waitForFuture(uploadFuture)); } void TestOlmAccount::uploadSignedOneTimeKeys() @@ -230,15 +228,14 @@ void TestOlmAccount::uploadSignedOneTimeKeys() auto oneTimeKeys = olmAccount->oneTimeKeys(); auto oneTimeKeysHash = olmAccount->signOneTimeKeys(oneTimeKeys); - auto request = new UploadKeysJob({}, oneTimeKeysHash); - connect(request, &BaseJob::result, this, [request, nKeys] { - if (!request->status().good()) - QFAIL("upload failed"); - QCOMPARE(request->oneTimeKeyCounts().value(SignedCurve25519Key), nKeys); - }); - conn->run(request); - QSignalSpy spy3(request, &BaseJob::result); - QVERIFY(spy3.wait(10000)); + const auto uploadFuture = + conn->callApi(std::nullopt, oneTimeKeysHash) + .then( + [nKeys](const QHash& oneTimeKeyCounts) { + QCOMPARE(oneTimeKeyCounts.value(SignedCurve25519Key), nKeys); + }, + [] { QFAIL("upload failed"); }); + QVERIFY(waitForFuture(uploadFuture)); } void TestOlmAccount::uploadKeys() @@ -248,15 +245,13 @@ void TestOlmAccount::uploadKeys() auto idks = olmAccount->identityKeys(); olmAccount->generateOneTimeKeys(1); auto otks = olmAccount->oneTimeKeys(); - auto request = olmAccount->createUploadKeyRequest(otks); - connect(request, &BaseJob::result, this, [request] { - if (!request->status().good()) - QFAIL("upload failed"); - QCOMPARE(request->oneTimeKeyCounts().value(SignedCurve25519Key), 1); - }); - conn->run(request); - QSignalSpy spy3(request, &BaseJob::result); - QVERIFY(spy3.wait(10000)); + auto request = conn->run(olmAccount->createUploadKeyRequest(otks)) + .then( + [](const QHash& oneTimeKeyCounts) { + QCOMPARE(oneTimeKeyCounts.value(SignedCurve25519Key), 1); + }, + [] { QFAIL("upload failed"); }); + QVERIFY(waitForFuture(request)); } void TestOlmAccount::queryTest() @@ -267,64 +262,56 @@ void TestOlmAccount::queryTest() // Create and upload keys for both users. auto aliceOlm = alice->olmAccount(); aliceOlm->generateOneTimeKeys(1); - auto aliceRes = aliceOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys()); - connect(aliceRes, &BaseJob::result, this, [aliceRes] { - QCOMPARE(aliceRes->oneTimeKeyCounts().value(SignedCurve25519Key), 1); - }); - QSignalSpy spy(aliceRes, &BaseJob::result); - alice->run(aliceRes); - QVERIFY(spy.wait(10000)); + const auto aliceUploadKeysRequest = + alice->run(aliceOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys())) + .then([](const QHash& aliceOneTimeKeyCounts) { + QCOMPARE(aliceOneTimeKeyCounts.value(SignedCurve25519Key), 1); + }); + QVERIFY(waitForFuture(aliceUploadKeysRequest)); auto bobOlm = bob->olmAccount(); bobOlm->generateOneTimeKeys(1); - auto bobRes = bobOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys()); - connect(bobRes, &BaseJob::result, this, [bobRes] { - QCOMPARE(bobRes->oneTimeKeyCounts().value(SignedCurve25519Key), 1); - }); - QSignalSpy spy1(bobRes, &BaseJob::result); - bob->run(bobRes); - QVERIFY(spy1.wait(10000)); - - { - // Each user is requests each other's keys. - QHash deviceKeys; - deviceKeys[bob->userId()] = QStringList(); - auto job = alice->callApi(deviceKeys); - QSignalSpy spy(job, &BaseJob::result); - connect(job, &BaseJob::result, this, [job, bob, bobOlm] { - QCOMPARE(job->failures().size(), 0); - - const auto& aliceDevices = job->deviceKeys().value(bob->userId()); - QVERIFY(!aliceDevices.empty()); - - const auto& devKeys = aliceDevices.value(bob->deviceId()); - QCOMPARE(devKeys.userId, bob->userId()); - QCOMPARE(devKeys.deviceId, bob->deviceId()); - QCOMPARE(devKeys.keys, bobOlm->deviceKeys().keys); - QCOMPARE(devKeys.signatures, bobOlm->deviceKeys().signatures); - }); - QVERIFY(spy.wait(10000)); - } - - { - QHash deviceKeys; - deviceKeys[alice->userId()] = QStringList(); - auto job = bob->callApi(deviceKeys); - QSignalSpy spy(job, &BaseJob::result); - connect(job, &BaseJob::result, this, [job, alice, aliceOlm] { - QCOMPARE(job->failures().size(), 0); - - const auto& bobDevices = job->deviceKeys().value(alice->userId()); - QVERIFY(!bobDevices.empty()); - - auto devKeys = bobDevices[alice->deviceId()]; - QCOMPARE(devKeys.userId, alice->userId()); - QCOMPARE(devKeys.deviceId, alice->deviceId()); - QCOMPARE(devKeys.keys, aliceOlm->deviceKeys().keys); - QCOMPARE(devKeys.signatures, aliceOlm->deviceKeys().signatures); - }); - QVERIFY(spy.wait(10000)); - } + const auto bobUploadKeysRequest = + bob->run(bobOlm->createUploadKeyRequest(aliceOlm->oneTimeKeys())) + .then([](const QHash& bobOneTimeKeyCounts) { + QCOMPARE(bobOneTimeKeyCounts.value(SignedCurve25519Key), 1); + }); + QVERIFY(waitForFuture(bobUploadKeysRequest)); + + // Each user is requests each other's keys. + const QHash deviceKeysForBob{ { bob->userId(), {} } }; + const auto queryBobKeysResult = + alice->callApi(deviceKeysForBob) + .then([bob, bobOlm](const QueryKeysJob::Response& r) { + QCOMPARE(r.failures.size(), 0); + + const auto& aliceDevices = r.deviceKeys.value(bob->userId()); + QVERIFY(!aliceDevices.empty()); + + const auto& aliceDevKeys = aliceDevices.value(bob->deviceId()); + QCOMPARE(aliceDevKeys.userId, bob->userId()); + QCOMPARE(aliceDevKeys.deviceId, bob->deviceId()); + QCOMPARE(aliceDevKeys.keys, bobOlm->deviceKeys().keys); + QCOMPARE(aliceDevKeys.signatures, bobOlm->deviceKeys().signatures); + }); + QVERIFY(waitForFuture(queryBobKeysResult)); + + const QHash deviceKeysForAlice{ { alice->userId(), {} } }; + const auto queryAliceKeysResult = + bob->callApi(deviceKeysForAlice) + .then([alice, aliceOlm](const QueryKeysJob::Response& r) { + QCOMPARE(r.failures.size(), 0); + + const auto& bobDevices = r.deviceKeys.value(alice->userId()); + QVERIFY(!bobDevices.empty()); + + auto devKeys = bobDevices[alice->deviceId()]; + QCOMPARE(devKeys.userId, alice->userId()); + QCOMPARE(devKeys.deviceId, alice->deviceId()); + QCOMPARE(devKeys.keys, aliceOlm->deviceKeys().keys); + QCOMPARE(devKeys.signatures, aliceOlm->deviceKeys().signatures); + }); + QVERIFY(waitForFuture(queryAliceKeysResult)); } void TestOlmAccount::claimKeys() @@ -335,53 +322,50 @@ void TestOlmAccount::claimKeys() // Bob uploads his keys. auto *bobOlm = bob->olmAccount(); bobOlm->generateOneTimeKeys(1); - auto request = bobOlm->createUploadKeyRequest(bobOlm->oneTimeKeys()); - - connect(request, &BaseJob::result, this, [request, bob] { - QCOMPARE(request->oneTimeKeyCounts().value(SignedCurve25519Key), 1); - }); - bob->run(request); - - QSignalSpy requestSpy(request, &BaseJob::result); - QVERIFY(requestSpy.wait(10000)); + auto request = bob->run(bobOlm->createUploadKeyRequest(bobOlm->oneTimeKeys())) + .then([bob](const QHash& oneTimeKeyCounts) { + QCOMPARE(oneTimeKeyCounts.value(SignedCurve25519Key), 1); + }); + QVERIFY(waitForFuture(request)); // Alice retrieves bob's keys & claims one signed one-time key. const QHash deviceKeysToQuery{ { bob->userId(), {} } }; - auto queryKeysJob = alice->callApi(deviceKeysToQuery); - QSignalSpy requestSpy2(queryKeysJob, &BaseJob::result); - QVERIFY(requestSpy2.wait(10000)); - - const auto& bobDevices = queryKeysJob->deviceKeys().value(bob->userId()); - QVERIFY(!bobDevices.empty()); + const auto queryKeysJob = + alice->callApi(deviceKeysToQuery).then([bob](const QueryKeysJob::Response& r) { + const auto& bobDevices = r.deviceKeys.value(bob->userId()); + QVERIFY(!bobDevices.empty()); - QVERIFY(verifyIdentitySignature(bobDevices.value(bob->deviceId()), - bob->deviceId(), bob->userId())); + QVERIFY(verifyIdentitySignature(bobDevices.value(bob->deviceId()), bob->deviceId(), + bob->userId())); + }); + QVERIFY(waitForFuture(queryKeysJob)); // Retrieve the identity key for the current device to check after claiming - const auto& bobEd25519 = - bobDevices.value(bob->deviceId()).keys.value("ed25519:"_L1 + bob->deviceId()); + // const auto& bobEd25519 = + // bobDevices.value(bob->deviceId()).keys.value("ed25519:"_L1 + bob->deviceId()); const QHash> oneTimeKeys{ { bob->userId(), { { bob->deviceId(), SignedCurve25519Key } } } }; - - auto claimKeysJob = alice->callApi(oneTimeKeys); - QVERIFY(QSignalSpy(claimKeysJob, &BaseJob::result).wait(10000)); - QCOMPARE(claimKeysJob->status().code, BaseJob::StatusCode::NoError); - - // The device exists. - QCOMPARE(claimKeysJob->oneTimeKeys().size(), 1); - const auto& allClaimedKeys = - claimKeysJob->oneTimeKeys().value(bob->userId()); - QCOMPARE(allClaimedKeys.size(), 1); - - // The key is the one bob sent. - const auto& claimedDeviceKeys = allClaimedKeys.value(bob->deviceId()); - for (const auto& claimedKey : claimedDeviceKeys.asKeyValueRange()) { - if (!claimedKey.first.startsWith(SignedCurve25519Key)) - continue; - QVERIFY(std::holds_alternative(claimedKey.second)); - } + const auto claimKeysJob = + alice->callApi(oneTimeKeys) + .then( + [bob](const ClaimKeysJob::Response& r) { + // The device exists. + QCOMPARE(r.oneTimeKeys.size(), 1); + const auto& allClaimedKeys = r.oneTimeKeys.value(bob->userId()); + QCOMPARE(allClaimedKeys.size(), 1); + + // The key is the one bob sent. + const auto& claimedDeviceKeys = allClaimedKeys.value(bob->deviceId()); + for (const auto& claimedKey : claimedDeviceKeys.asKeyValueRange()) { + if (!claimedKey.first.startsWith(SignedCurve25519Key)) + continue; + QVERIFY(std::holds_alternative(claimedKey.second)); + } + }, + [] { QFAIL("Claim job failed"); }); + QVERIFY(waitForFuture(claimKeysJob)); } void TestOlmAccount::claimMultipleKeys() @@ -393,69 +377,59 @@ void TestOlmAccount::claimMultipleKeys() auto olm = alice->olmAccount(); olm->generateOneTimeKeys(10); - auto res = olm->createUploadKeyRequest(olm->oneTimeKeys()); - QSignalSpy spy(res, &BaseJob::result); - connect(res, &BaseJob::result, this, [res] { - QCOMPARE(res->oneTimeKeyCounts().value(SignedCurve25519Key), 10); - }); - alice->run(res); - QVERIFY(spy.wait(10000)); + const auto aliceUploadKeyRequest = + alice->run(olm->createUploadKeyRequest(olm->oneTimeKeys())) + .then([](const QHash& oneTimeKeyCounts) { + QCOMPARE(oneTimeKeyCounts.value(SignedCurve25519Key), 10); + }); + QVERIFY(waitForFuture(aliceUploadKeyRequest)); auto olm1 = alice1->olmAccount(); olm1->generateOneTimeKeys(10); - auto res1 = olm1->createUploadKeyRequest(olm1->oneTimeKeys()); - QSignalSpy spy1(res1, &BaseJob::result); - connect(res1, &BaseJob::result, this, [res1] { - QCOMPARE(res1->oneTimeKeyCounts().value(SignedCurve25519Key), 10); - }); - alice1->run(res1); - QVERIFY(spy1.wait(10000)); + const auto alice1UploadKeyRequest = + alice1->run(olm1->createUploadKeyRequest(olm1->oneTimeKeys())) + .then([](const QHash& oneTimeKeyCounts) { + QCOMPARE(oneTimeKeyCounts.value(SignedCurve25519Key), 10); + }); + QVERIFY(waitForFuture(alice1UploadKeyRequest)); auto olm2 = alice2->olmAccount(); olm2->generateOneTimeKeys(10); - auto res2 = olm2->createUploadKeyRequest(olm2->oneTimeKeys()); - QSignalSpy spy2(res2, &BaseJob::result); - connect(res2, &BaseJob::result, this, [res2] { - QCOMPARE(res2->oneTimeKeyCounts().value(SignedCurve25519Key), 10); - }); - alice2->run(res2); - QVERIFY(spy2.wait(10000)); - - // Bob will claim all keys from alice + const auto alice2UploadKeyRequest = + alice2->run(olm2->createUploadKeyRequest(olm2->oneTimeKeys())) + .then([](const QHash& oneTimeKeyCounts) { + QCOMPARE(oneTimeKeyCounts.value(SignedCurve25519Key), 10); + }); + QVERIFY(waitForFuture(alice2UploadKeyRequest)); + + // Bob will claim keys from all Alice's devices CREATE_CONNECTION(bob, "bob3"_L1, "secret"_L1, "BobPhone"_L1) - QStringList devices_; - devices_ << alice->deviceId() - << alice1->deviceId() - << alice2->deviceId(); - QHash> oneTimeKeys; - oneTimeKeys[alice->userId()] = QHash(); - for (const auto &d : devices_) { - oneTimeKeys[alice->userId()][d] = SignedCurve25519Key; - } - auto job = bob->callApi(oneTimeKeys); - QSignalSpy jobSpy(job, &BaseJob::finished); - QVERIFY(jobSpy.wait(10000)); - const auto userId = alice->userId(); - - QCOMPARE(job->oneTimeKeys().value(userId).size(), 3); + auto keyRequests = oneTimeKeys.insert(alice->userId(), {}); + for (const auto& c : { alice, alice1, alice2 }) + keyRequests->insert(c->deviceId(), SignedCurve25519Key); + + const auto claimResult = + bob->callApi(oneTimeKeys).then([alice](const ClaimKeysJob::Response& r) { + QCOMPARE(r.oneTimeKeys.value(alice->userId()).size(), 3); + }); + QVERIFY(waitForFuture(claimResult)); } void TestOlmAccount::enableEncryption() { CREATE_CONNECTION(alice, "alice9"_L1, "secret"_L1, "AlicePhone"_L1) - alice->syncLoop(); - QSignalSpy createRoomSpy(alice.get(), &Connection::loadedRoomState); - auto job = alice->createRoom(Connection::PublishRoom, {}, {}, {}, {}); - QVERIFY(createRoomSpy.wait(10000)); - - auto room = alice->rooms(JoinState::Join)[0]; - QSignalSpy encryptionSpy(room, &Room::encryption); - room->activateEncryption(); - QVERIFY(encryptionSpy.wait(10000)); - QVERIFY(room->usesEncryption()); + const auto futureRoom = alice->createRoom(Connection::PublishRoom, {}, {}, {}, {}) + .then([alice](const QString& roomId) { + auto room = alice->room(roomId); + room->activateEncryption(); + return room; + }); + QVERIFY(waitForFuture(futureRoom)); + alice->syncLoop(); + QVERIFY(QTest::qWaitFor([room = futureRoom.result()] { return room->usesEncryption(); }, 20000)); } QTEST_GUILESS_MAIN(TestOlmAccount) diff --git a/autotests/testutils.h b/autotests/testutils.h index 00a724694..77291c1b7 100644 --- a/autotests/testutils.h +++ b/autotests/testutils.h @@ -21,3 +21,9 @@ std::shared_ptr createTestConnection(QLatin1StringView localUserName const auto VAR = createTestConnection(USERNAME, SECRET, DEVICE_NAME); \ if (!VAR) \ QFAIL("Could not set up test connection"); + +inline bool waitForFuture(const auto& ft) + requires requires { ft.isFinished(); } +{ + return QTest::qWaitFor([ft] { return ft.isFinished(); }, 40000); +} From 57e58c967cce07ff86c170e562d236b1f81810b9 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 19 Sep 2024 18:16:19 +0200 Subject: [PATCH 06/11] Tweaks to ConnectionData and NAM APIs These APIs are practically internal; the change is meant to streamline adding an account, assuming that both the identity and the access token arrive to these classes at the same time (e.g. see Connection::completeSetup()). --- Quotient/connectiondata.cpp | 13 +++++++----- Quotient/connectiondata.h | 4 +++- Quotient/networkaccessmanager.cpp | 34 ++++++++++++++++--------------- Quotient/networkaccessmanager.h | 5 +++-- Quotient/util.h | 4 ++-- 5 files changed, 34 insertions(+), 26 deletions(-) diff --git a/Quotient/connectiondata.cpp b/Quotient/connectiondata.cpp index 176096d55..573704928 100644 --- a/Quotient/connectiondata.cpp +++ b/Quotient/connectiondata.cpp @@ -104,7 +104,7 @@ QUrl ConnectionData::baseUrl() const { return d->baseUrl; } HomeserverData ConnectionData::homeserverData() const { - return { d->baseUrl, d->supportedSpecVersions, d->accessToken }; + return { d->baseUrl, d->accessToken, d->supportedSpecVersions }; } NetworkAccessManager* ConnectionData::nam() const @@ -124,12 +124,14 @@ void ConnectionData::setBaseUrl(QUrl baseUrl) } } -void ConnectionData::setToken(QByteArray token) +void ConnectionData::setAccessToken(QByteArray token) { d->accessToken = std::move(token); NetworkAccessManager::setAccessToken(d->userId, d->accessToken); } +void ConnectionData::setToken(QByteArray token) { setAccessToken(std::move(token)); } + const QString& ConnectionData::deviceId() const { return d->deviceId; } const QString& ConnectionData::userId() const { return d->userId; } @@ -138,18 +140,19 @@ void ConnectionData::setDeviceId(const QString& deviceId) { d->deviceId = device void ConnectionData::setUserId(const QString& userId) { setIdentity(userId, d->deviceId); } -void ConnectionData::setIdentity(const QString& userId, const QString& deviceId) +void ConnectionData::setIdentity(const QString& userId, const QString& deviceId, + QByteArray accessToken) { if (d->baseUrl.isValid()) { if (d->userId != userId) NetworkAccessManager::dropAccount(d->userId); if (!userId.isEmpty()) { - NetworkAccessManager::addAccount(userId, d->baseUrl); - NetworkAccessManager::setAccessToken(userId, d->accessToken); + NetworkAccessManager::addAccount(userId, d->baseUrl, accessToken); } } d->userId = userId; d->deviceId = deviceId; + d->accessToken = std::move(accessToken); } void ConnectionData::setSupportedSpecVersions(QStringList versions) diff --git a/Quotient/connectiondata.h b/Quotient/connectiondata.h index 51d9c3e28..712c968f9 100644 --- a/Quotient/connectiondata.h +++ b/Quotient/connectiondata.h @@ -32,12 +32,14 @@ class QUOTIENT_API ConnectionData { Quotient::NetworkAccessManager *nam() const; void setBaseUrl(QUrl baseUrl); + [[deprecated("Use setAccessToken() or setIdentity() instead")]] void setToken(QByteArray accessToken); [[deprecated("Use setIdentity() instead")]] void setDeviceId(const QString& deviceId); [[deprecated("Use setIdentity() instead")]] void setUserId(const QString& userId); - void setIdentity(const QString& userId, const QString& deviceId); + void setIdentity(const QString& userId, const QString& deviceId, QByteArray accessToken = {}); + void setAccessToken(QByteArray accessToken); void setSupportedSpecVersions(QStringList versions); QString lastEvent() const; diff --git a/Quotient/networkaccessmanager.cpp b/Quotient/networkaccessmanager.cpp index ce930164b..a6dc285c6 100644 --- a/Quotient/networkaccessmanager.cpp +++ b/Quotient/networkaccessmanager.cpp @@ -27,7 +27,7 @@ class { HomeserverData hsData; }; - void addConnection(QString accountId, QUrl baseUrl) + void addConnection(const QString& accountId, const QUrl& baseUrl, const QByteArray& accessToken) { if (baseUrl.isEmpty()) return; @@ -35,12 +35,12 @@ class { const QWriteLocker _(&namLock); if (auto it = std::ranges::find(connectionData, accountId, &ConnectionData::accountId); it != connectionData.end()) { - it->hsData.baseUrl = std::move(baseUrl); + it->hsData.baseUrl = baseUrl; + it->hsData.accessToken = accessToken; } else - connectionData.push_back( - { std::move(accountId), HomeserverData{ std::move(baseUrl), {}, {}} }); + connectionData.emplace_back(accountId, HomeserverData{ baseUrl, accessToken }); } - void addSpecVersions(QStringView accountId, QStringList versions) + void addSpecVersions(QStringView accountId, const QStringList& versions) { if (versions.isEmpty()) return; @@ -51,7 +51,7 @@ class { "versions on an inexistent account")) return; - it->hsData.supportedSpecVersions = std::move(versions); + it->hsData.supportedSpecVersions = versions; } void dropConnection(QStringView accountId) { @@ -63,7 +63,7 @@ class { { const QReadLocker _(&namLock); auto it = std::ranges::find(connectionData, accountId, &ConnectionData::accountId); - return it == connectionData.cend() ? HomeserverData{ } : it->hsData; + return it == connectionData.cend() ? HomeserverData{} : it->hsData; } void addIgnoredSslError(const QSslError& error) { @@ -97,16 +97,23 @@ class { } // anonymous namespace -void NetworkAccessManager::addAccount(QString accountId, QUrl homeserver) +void NetworkAccessManager::addAccount(const QString& accountId, const QUrl& homeserver, + const QByteArray& accessToken) { Q_ASSERT(!accountId.isEmpty()); - d.addConnection( accountId, std::move(homeserver) ); + d.addConnection(accountId, homeserver, accessToken); +} + +void NetworkAccessManager::setAccessToken(const QString& userId, const QByteArray& token) +{ + d.setAccessToken(userId, token); } -void NetworkAccessManager::updateAccountSpecVersions(QStringView accountId, QStringList versions) +void NetworkAccessManager::updateAccountSpecVersions(QStringView accountId, + const QStringList& versions) { Q_ASSERT(!accountId.isEmpty()); - d.addSpecVersions(accountId, std::move(versions)); + d.addSpecVersions(accountId, versions); } void NetworkAccessManager::dropAccount(QStringView accountId) @@ -192,8 +199,3 @@ QStringList NetworkAccessManager::supportedSchemesImplementation() const { return QNetworkAccessManager::supportedSchemesImplementation() << u"mxc"_s; } - -void NetworkAccessManager::setAccessToken(const QString& userId, const QByteArray& token) -{ - d.setAccessToken(userId, token); -} diff --git a/Quotient/networkaccessmanager.h b/Quotient/networkaccessmanager.h index 0feea0471..95e68332a 100644 --- a/Quotient/networkaccessmanager.h +++ b/Quotient/networkaccessmanager.h @@ -14,8 +14,9 @@ class QUOTIENT_API NetworkAccessManager : public QNetworkAccessManager { public: using QNetworkAccessManager::QNetworkAccessManager; - static void addAccount(QString accountId, QUrl homeserver); - static void updateAccountSpecVersions(QStringView accountId, QStringList versions); + static void addAccount(const QString& accountId, const QUrl& homeserver, + const QByteArray& accessToken = {}); + static void updateAccountSpecVersions(QStringView accountId, const QStringList &versions); static void dropAccount(QStringView accountId); static QList ignoredSslErrors(); diff --git a/Quotient/util.h b/Quotient/util.h index 901bb263e..b9205ae1a 100644 --- a/Quotient/util.h +++ b/Quotient/util.h @@ -454,8 +454,8 @@ QUOTIENT_API bool isGuestUserId(const UserId& uId); struct QUOTIENT_API HomeserverData { QUrl baseUrl; - QStringList supportedSpecVersions; - QByteArray accessToken; + QByteArray accessToken = {}; + QStringList supportedSpecVersions = {}; bool checkMatrixSpecVersion(QStringView targetVersion) const; }; From 8c42c2f501cdeb3118112d81283984d956c86361 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 19 Sep 2024 22:33:12 +0200 Subject: [PATCH 07/11] Refactor post-login connection setup 1. Mock connections no more use ConnectionEncryptionData::setup(), just initialise the object directly. There's not that much work, and it's entirely independent from what CED::setup() normally does. 2. Clearing the database has moved from Connection::P::loginToServer() to CED::setup(); the previous logic almost never worked because encryptionData is usually initialised asynchronously now, and even in the rare asynchoronous cases the clearing happened at the wrong moment - after the first save of the newly initialised Olm account. --- Quotient/connection.cpp | 54 +++++++++++++------------ Quotient/connection_p.h | 4 +- Quotient/connectionencryptiondata_p.cpp | 23 ++++------- Quotient/connectionencryptiondata_p.h | 5 ++- Quotient/room.cpp | 1 + 5 files changed, 44 insertions(+), 43 deletions(-) diff --git a/Quotient/connection.cpp b/Quotient/connection.cpp index dfb719fcb..fbddae58c 100644 --- a/Quotient/connection.cpp +++ b/Quotient/connection.cpp @@ -69,7 +69,6 @@ Connection::Connection(const QUrl& server, QObject* parent) : QObject(parent) , d(makeImpl(std::make_unique(server))) { - //connect(qApp, &QCoreApplication::aboutToQuit, this, &Connection::saveOlmAccount); d->q = this; // All d initialization should occur before this line setObjectName(server.toString()); } @@ -168,11 +167,11 @@ void Connection::loginWithToken(const QString& loginToken, QString() /*password*/, loginToken, deviceId, initialDeviceName); } -void Connection::assumeIdentity(const QString& mxId, const QString& deviceId, const QString& accessToken) +void Connection::assumeIdentity(const QString& mxId, const QString& deviceId, + const QString& accessToken) { - d->completeSetup(mxId, deviceId); + d->completeSetup(mxId, false, deviceId, accessToken); d->ensureHomeserver(mxId).then([this, mxId, accessToken] { - d->data->setToken(accessToken.toLatin1()); callApi().onResult([this, mxId](const GetTokenOwnerJob* job) { switch (job->error()) { case BaseJob::Success: @@ -280,7 +279,7 @@ void Connection::Private::dropAccessToken() << qUtf8Printable(job->errorString()); }); - data->setToken({}); + data->setAccessToken({}); } template @@ -289,26 +288,26 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) q->callApi(std::forward(loginArgs)...) .onResult([this](const LoginJob* loginJob) { if (loginJob->status().good()) { - data->setToken(loginJob->accessToken().toLatin1()); - completeSetup(loginJob->userId(), loginJob->deviceId()); saveAccessTokenToKeychain(); - if (encryptionData) - encryptionData->database.clear(); + completeSetup(loginJob->userId(), true, loginJob->deviceId(), + loginJob->accessToken()); } else emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); }); } -void Connection::Private::completeSetup(const QString& mxId, const QString& deviceId, bool mock) +void Connection::Private::completeSetup(const QString& mxId, bool newLogin, + const std::optional& deviceId, + const std::optional& accessToken) { - data->setIdentity(mxId, deviceId); + data->setIdentity(mxId, deviceId.value_or(u""_s), accessToken.value_or(u""_s).toLatin1()); q->setObjectName(data->userId() % u'/' % data->deviceId()); qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << data->userId() << "from device" << data->deviceId(); connect(qApp, &QCoreApplication::aboutToQuit, q, &Connection::saveState); - if (!mock) { + if (accessToken.has_value()) { q->loadVersions(); q->loadCapabilities(); q->user()->load(); // Load the local user's profile @@ -318,21 +317,27 @@ void Connection::Private::completeSetup(const QString& mxId, const QString& devi if (useEncryption) { using _impl::ConnectionEncryptionData; - ConnectionEncryptionData::setup(q, mock, encryptionData).then([this](bool successful) { - if (!successful || !encryptionData) - useEncryption = false; - - emit q->encryptionChanged(useEncryption); - emit q->stateChanged(); - emit q->ready(); - emit q->connected(); - }); + if (!accessToken) { + // Mock connection; initialise bare bones necessary for testing + qInfo(E2EE) << "Using a mock pickling key"; + encryptionData = std::make_unique(q, PicklingKey::generate()); + encryptionData->database.clear(); + encryptionData->olmAccount.setupNewAccount(); + } else + ConnectionEncryptionData::setup(q, encryptionData, newLogin).then([this](bool successful) { + if (!successful || !encryptionData) + useEncryption = false; + + emit q->encryptionChanged(useEncryption); + emit q->stateChanged(); + emit q->ready(); + emit q->connected(); + }); } else { qCInfo(E2EE) << "End-to-end encryption (E2EE) support is off for" << q->objectName(); emit q->ready(); emit q->connected(); } - } QFuture Connection::Private::ensureHomeserver(const QString& userId, @@ -1898,12 +1903,11 @@ void Connection::reloadDevices() } } -Connection* Connection::makeMockConnection(const QString& mxId, - bool enableEncryption) +Connection* Connection::makeMockConnection(const QString& mxId, bool enableEncryption) { auto* c = new Connection; c->enableEncryption(enableEncryption); - c->d->completeSetup(mxId, {}, true); + c->d->completeSetup(mxId); return c; } diff --git a/Quotient/connection_p.h b/Quotient/connection_p.h index 7eca6ea52..216bf305a 100644 --- a/Quotient/connection_p.h +++ b/Quotient/connection_p.h @@ -93,7 +93,9 @@ class Q_DECL_HIDDEN Quotient::Connection::Private { QFuture ensureHomeserver(const QString& userId, const std::optional& flow = {}); template void loginToServer(LoginArgTs&&... loginArgs); - void completeSetup(const QString& mxId, const QString& deviceId, bool mock = false); + void completeSetup(const QString& mxId, bool newLogin = true, + const std::optional& deviceId = {}, + const std::optional& accessToken = {}); void removeRoom(const QString& roomId); void consumeRoomData(SyncDataList&& roomDataList, bool fromCache); diff --git a/Quotient/connectionencryptiondata_p.cpp b/Quotient/connectionencryptiondata_p.cpp index e6a122905..ebf8858f9 100644 --- a/Quotient/connectionencryptiondata_p.cpp +++ b/Quotient/connectionencryptiondata_p.cpp @@ -34,16 +34,9 @@ inline QFuture runKeychainJob(QKeychain::Job* j, const QString& return ft; } -QFuture setupPicklingKey(Connection* connection, bool mock, +QFuture setupPicklingKey(Connection* connection, std::unique_ptr& encryptionData) { - if (mock) { - qInfo(E2EE) << "Using a mock pickling key"; - encryptionData = - std::make_unique(connection, PicklingKey::generate()); - return QtFuture::makeReadyFuture(); - } - using namespace QKeychain; const auto keychainId = connection->userId() + "-Pickle"_L1; qCInfo(MAIN) << "Keychain request: app" << qAppName() << "id" << keychainId; @@ -94,15 +87,15 @@ QFuture setupPicklingKey(Connection* connection, bool mock, }); } -QFuture ConnectionEncryptionData::setup(Connection* connection, bool mock, - std::unique_ptr& result) +QFuture ConnectionEncryptionData::setup(Connection* connection, + std::unique_ptr& result, + bool clearDatabase) { - return setupPicklingKey(connection, mock, result) - .then([connection, mock, &result] { - if (mock) { + return setupPicklingKey(connection, result) + .then([connection, &result, clearDatabase] { + if (clearDatabase) { + qCInfo(E2EE) << "Clearing the database for account" << connection->objectName(); result->database.clear(); - result->olmAccount.setupNewAccount(); - return true; } if (const auto outcome = result->database.setupOlmAccount(result->olmAccount)) { if (outcome == OLM_SUCCESS) { diff --git a/Quotient/connectionencryptiondata_p.h b/Quotient/connectionencryptiondata_p.h index 970dbcbb8..2dcc67996 100644 --- a/Quotient/connectionencryptiondata_p.h +++ b/Quotient/connectionencryptiondata_p.h @@ -16,8 +16,9 @@ struct DevicesList; namespace _impl { class ConnectionEncryptionData { public: - static QFuture setup(Connection* connection, bool mock, - std::unique_ptr& result); + static QFuture setup(Connection* connection, + std::unique_ptr& result, + bool clearDatabase = false); Connection* q; QOlmAccount olmAccount; diff --git a/Quotient/room.cpp b/Quotient/room.cpp index 1a63da7c6..d80234476 100644 --- a/Quotient/room.cpp +++ b/Quotient/room.cpp @@ -3174,6 +3174,7 @@ Room::Change Room::Private::processStateEvent(const RoomEvent& curEvent, [this](const EncryptionEvent&) { // As encryption can only be switched on once, emit the signal here // instead of aggregating and emitting in updateData() + qCInfo(MAIN) << "E2EE switched on in" << q->objectName(); emit q->encryption(); return Change::Other; }, From 95c2017033a8fe382a84341cfdec10866cb72c49 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Thu, 19 Sep 2024 22:33:40 +0200 Subject: [PATCH 08/11] quotest: test Connection::assumeIdentity() too --- quotest/quotest.cpp | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index fc3b0407a..bb12c9916 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -198,7 +198,33 @@ TestManager::TestManager(int& argc, char** argv) clog.flush(); // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) - connect(c, &Connection::connected, this, &TestManager::setupAndRun); + connect(c, &Connection::connected, this, [this] { + if (QUO_ALARM(c->homeserver().isEmpty() || !c->homeserver().isValid()) + || QUO_ALARM(c->domain() != c->userId().section(u':', 1))) { + clog << "Connection information doesn't look right, check the parameters passed to " + "quotest\n"; + QCoreApplication::exit(-2); + return; + } + clog << "Connected, server: " << c->homeserver().toDisplayString().toStdString() << '\n' + << "Access token: " << c->accessToken().toStdString() << endl; + + // Test Connection::assumeIdentity() while we can replace connection objects + auto* newC = new Connection(c->homeserver(), this); + newC->assumeIdentity(c->userId(), c->deviceId(), QString::fromLatin1(c->accessToken())); + // NB: this will need to change when we switch E2EE on in quotest because encryption + // data is initialised asynchronously + if (QUO_ALARM(newC->homeserver() != c->homeserver()) + || QUO_ALARM(newC->userId() != c->userId()) || QUO_ALARM(!newC->isLoggedIn())) { + clog << "Connection::assumeIdentity() failed to do its job\n"; + QCoreApplication::exit(-2); + return; + } + + c->deleteLater(); + c = newC; + setupAndRun(); + }); connect(c, &Connection::resolveError, this, [](const QString& error) { clog << "Failed to resolve the server: " << error.toStdString() << endl; @@ -213,7 +239,6 @@ TestManager::TestManager(int& argc, char** argv) QCoreApplication::exit(-2); }, Qt::QueuedConnection); - connect(c, &Connection::loadedRoomState, this, &TestManager::onNewRoom); // Big countdown watchdog QTimer::singleShot(180000, this, [this] { @@ -232,6 +257,8 @@ void TestManager::setupAndRun() clog << "Connected, server: " << c->homeserver().toDisplayString().toStdString() << '\n' << "Access token: " << c->accessToken().toStdString() << endl; + connect(c, &Connection::loadedRoomState, this, &TestManager::onNewRoom); + c->setLazyLoading(true); clog << "Joining " << targetRoomName.toStdString() << endl; From c6110cd75ee64c7a5256d6b3d4f45daa6b2d4430 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 20 Sep 2024 07:56:21 +0200 Subject: [PATCH 09/11] Fix Sonar warnings --- Quotient/connection.cpp | 2 +- Quotient/connectiondata.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Quotient/connection.cpp b/Quotient/connection.cpp index fbddae58c..36a39b452 100644 --- a/Quotient/connection.cpp +++ b/Quotient/connection.cpp @@ -171,7 +171,7 @@ void Connection::assumeIdentity(const QString& mxId, const QString& deviceId, const QString& accessToken) { d->completeSetup(mxId, false, deviceId, accessToken); - d->ensureHomeserver(mxId).then([this, mxId, accessToken] { + d->ensureHomeserver(mxId).then([this, mxId] { callApi().onResult([this, mxId](const GetTokenOwnerJob* job) { switch (job->error()) { case BaseJob::Success: diff --git a/Quotient/connectiondata.cpp b/Quotient/connectiondata.cpp index 573704928..e4454fe92 100644 --- a/Quotient/connectiondata.cpp +++ b/Quotient/connectiondata.cpp @@ -130,7 +130,7 @@ void ConnectionData::setAccessToken(QByteArray token) NetworkAccessManager::setAccessToken(d->userId, d->accessToken); } -void ConnectionData::setToken(QByteArray token) { setAccessToken(std::move(token)); } +void ConnectionData::setToken(QByteArray accessToken) { setAccessToken(std::move(accessToken)); } const QString& ConnectionData::deviceId() const { return d->deviceId; } From aa95ac1c4d1f81ae2a200894f6e513d1bd603394 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 20 Sep 2024 10:12:50 +0200 Subject: [PATCH 10/11] Typo fix --- Quotient/connection.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Quotient/connection.h b/Quotient/connection.h index b0324b686..e644eecb6 100644 --- a/Quotient/connection.h +++ b/Quotient/connection.h @@ -498,7 +498,7 @@ class QUOTIENT_API Connection : public QObject { //! Start a pre-created job object on this connection Q_INVOKABLE BaseJob* run(BaseJob* job, RunningPolicy runningPolicy = ForegroundRequest); - //! \brief Start a pre-created job on this connection and get a job handler to it + //! \brief Start a pre-created job on this connection and get a job handle to it //! //! This is a template overload for run(BaseJob*, RunningPolicy) - if you call run() on any //! derived job (99% of the cases when you're going to call it), this overload will be chosen From 0cf729324986a310c1c85e346cef18957528eb12 Mon Sep 17 00:00:00 2001 From: Alexey Rusakov Date: Fri, 20 Sep 2024 07:55:59 +0200 Subject: [PATCH 11/11] Fix building with Xcode --- Quotient/networkaccessmanager.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Quotient/networkaccessmanager.cpp b/Quotient/networkaccessmanager.cpp index a6dc285c6..c27222692 100644 --- a/Quotient/networkaccessmanager.cpp +++ b/Quotient/networkaccessmanager.cpp @@ -27,18 +27,17 @@ class { HomeserverData hsData; }; - void addConnection(const QString& accountId, const QUrl& baseUrl, const QByteArray& accessToken) + void addConnection(const QString& accountId, HomeserverData hsData) { - if (baseUrl.isEmpty()) + if (hsData.baseUrl.isEmpty()) return; const QWriteLocker _(&namLock); if (auto it = std::ranges::find(connectionData, accountId, &ConnectionData::accountId); - it != connectionData.end()) { - it->hsData.baseUrl = baseUrl; - it->hsData.accessToken = accessToken; - } else - connectionData.emplace_back(accountId, HomeserverData{ baseUrl, accessToken }); + it != connectionData.end()) + it->hsData = std::move(hsData); + else // Xcode doesn't like emplace_back() below for some reason (anon class?..) + connectionData.push_back({ accountId, std::move(hsData) }); } void addSpecVersions(QStringView accountId, const QStringList& versions) { @@ -101,7 +100,7 @@ void NetworkAccessManager::addAccount(const QString& accountId, const QUrl& home const QByteArray& accessToken) { Q_ASSERT(!accountId.isEmpty()); - d.addConnection(accountId, homeserver, accessToken); + d.addConnection(accountId, { homeserver, accessToken }); } void NetworkAccessManager::setAccessToken(const QString& userId, const QByteArray& token)