aboutsummaryrefslogtreecommitdiff
path: root/lib/connection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/connection.cpp')
-rw-r--r--lib/connection.cpp109
1 files changed, 79 insertions, 30 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp
index 694e4f16..f59d2962 100644
--- a/lib/connection.cpp
+++ b/lib/connection.cpp
@@ -37,6 +37,7 @@
#include "csapi/versions.h"
#include "csapi/voip.h"
#include "csapi/wellknown.h"
+#include "csapi/whoami.h"
#include "events/directchatevent.h"
#include "events/eventloader.h"
@@ -133,10 +134,27 @@ public:
!= "json";
bool lazyLoading = false;
+ /** \brief Check the homeserver and resolve it if needed, before connecting
+ *
+ * A single entry for functions that need to check whether the homeserver
+ * is valid before running. May execute connectFn either synchronously
+ * or asynchronously. In case of errors, emits resolveError() if
+ * the homeserver URL is not valid and cannot be resolved from userId, or
+ * the homeserver doesn't support the requested login flow.
+ *
+ * \param userId fully-qualified MXID to resolve HS from
+ * \param connectFn a function to execute once the HS URL is good
+ * \param flow optionally, a login flow that should be supported for
+ * connectFn to work; `none`, if there's no login flow
+ * requirements
+ * \sa resolveServer, resolveError
+ */
+ void checkAndConnect(const QString &userId,
+ const std::function<void ()> &connectFn,
+ const std::optional<LoginFlow> &flow = none);
template <typename... LoginArgTs>
void loginToServer(LoginArgTs&&... loginArgs);
- void assumeIdentity(const QString& userId, const QString& accessToken,
- const QString& deviceId);
+ void completeSetup(const QString &mxId);
void removeRoom(const QString& roomId);
void consumeRoomData(SyncDataList&& roomDataList, bool fromCache);
@@ -264,12 +282,15 @@ void Connection::resolveServer(const QString& mxid)
return;
}
- auto domain = maybeBaseUrl.host();
- qCDebug(MAIN) << "Finding the server" << domain;
+ qCDebug(MAIN) << "Finding the server" << maybeBaseUrl.host();
- d->data->setBaseUrl(maybeBaseUrl); // Just enough to check .well-known file
+ const auto& oldBaseUrl = d->data->baseUrl();
+ d->data->setBaseUrl(maybeBaseUrl); // Temporarily set it for this one call
d->resolverJob = callApi<GetWellknownJob>();
- connect(d->resolverJob, &BaseJob::finished, this, [this, maybeBaseUrl] {
+ connect(d->resolverJob, &BaseJob::finished, this, [this, maybeBaseUrl, oldBaseUrl] {
+ // Revert baseUrl so that setHomeserver() below triggers signals
+ // in case the base URL actually changed
+ d->data->setBaseUrl(oldBaseUrl);
if (d->resolverJob->error() != BaseJob::NotFoundError) {
if (!d->resolverJob->status().good()) {
qCWarning(MAIN)
@@ -297,6 +318,7 @@ void Connection::resolveServer(const QString& mxid)
<< "for base URL";
setHomeserver(maybeBaseUrl);
}
+ Q_ASSERT(d->loginFlowsJob != nullptr);
connect(d->loginFlowsJob, &BaseJob::success, this,
&Connection::resolved);
connect(d->loginFlowsJob, &BaseJob::failure, this, [this] {
@@ -324,10 +346,10 @@ void Connection::loginWithPassword(const QString& userId,
const QString& initialDeviceName,
const QString& deviceId)
{
- checkAndConnect(userId, [=] {
+ d->checkAndConnect(userId, [=] {
d->loginToServer(LoginFlows::Password.type, makeUserIdentifier(userId),
password, /*token*/ "", deviceId, initialDeviceName);
- });
+ }, LoginFlows::Password);
}
SsoSession* Connection::prepareForSso(const QString& initialDeviceName,
@@ -340,6 +362,7 @@ void Connection::loginWithToken(const QByteArray& loginToken,
const QString& initialDeviceName,
const QString& deviceId)
{
+ Q_ASSERT(d->data->baseUrl().isValid() && d->loginFlows.contains(LoginFlows::Token));
d->loginToServer(LoginFlows::Token.type,
none /*user is encoded in loginToken*/, "" /*password*/,
loginToken, deviceId, initialDeviceName);
@@ -348,8 +371,21 @@ void Connection::loginWithToken(const QByteArray& loginToken,
void Connection::assumeIdentity(const QString& mxId, const QString& accessToken,
const QString& deviceId)
{
- checkAndConnect(mxId,
- [=] { d->assumeIdentity(userId, accessToken, deviceId); });
+ d->checkAndConnect(mxId, [this, mxId, accessToken, deviceId] {
+ d->data->setToken(accessToken.toLatin1());
+ d->data->setDeviceId(deviceId); // Can't we deduce this from access_token?
+ auto* job = callApi<GetTokenOwnerJob>();
+ connect(job, &BaseJob::success, this, [this, job, mxId] {
+ if (mxId != job->userId())
+ qCWarning(MAIN).nospace()
+ << "The access_token owner (" << job->userId()
+ << ") is different from passed MXID (" << mxId << ")!";
+ d->completeSetup(job->userId());
+ });
+ connect(job, &BaseJob::failure, this, [this, job] {
+ emit loginError(job->errorString(), job->rawDataSample());
+ });
+ });
}
void Connection::reloadCapabilities()
@@ -389,8 +425,9 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs)
auto loginJob =
q->callApi<LoginJob>(std::forward<LoginArgTs>(loginArgs)...);
connect(loginJob, &BaseJob::success, q, [this, loginJob] {
- assumeIdentity(loginJob->userId(), loginJob->accessToken(),
- loginJob->deviceId());
+ data->setToken(loginJob->accessToken().toLatin1());
+ data->setDeviceId(loginJob->deviceId());
+ completeSetup(loginJob->userId());
#ifndef Quotient_E2EE_ENABLED
qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off.";
#else // Quotient_E2EE_ENABLED
@@ -403,17 +440,14 @@ void Connection::Private::loginToServer(LoginArgTs&&... loginArgs)
});
}
-void Connection::Private::assumeIdentity(const QString& userId,
- const QString& accessToken,
- const QString& deviceId)
+void Connection::Private::completeSetup(const QString& mxId)
{
- data->setUserId(userId);
+ data->setUserId(mxId);
q->user(); // Creates a User object for the local user
- data->setToken(accessToken.toLatin1());
- data->setDeviceId(deviceId);
- q->setObjectName(userId % '/' % deviceId);
+ q->setObjectName(data->userId() % '/' % data->deviceId());
qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString()
- << "by user" << userId << "from device" << deviceId;
+ << "by user" << data->userId()
+ << "from device" << data->deviceId();
#ifndef Quotient_E2EE_ENABLED
qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off.";
#else // Quotient_E2EE_ENABLED
@@ -430,22 +464,37 @@ void Connection::Private::assumeIdentity(const QString& userId,
q->reloadCapabilities();
}
-void Connection::checkAndConnect(const QString& userId,
- std::function<void()> connectFn)
+void Connection::Private::checkAndConnect(const QString& userId,
+ const std::function<void()>& connectFn,
+ const std::optional<LoginFlow>& flow)
{
- if (d->data->baseUrl().isValid()) {
+ if (data->baseUrl().isValid() && (!flow || loginFlows.contains(*flow))) {
connectFn();
return;
}
- // Not good to go, try to fix the homeserver URL.
+ // Not good to go, try to ascertain the homeserver URL and flows
if (userId.startsWith('@') && userId.indexOf(':') != -1) {
- connectSingleShot(this, &Connection::homeserverChanged, this, connectFn);
- // NB: doResolveServer can emit resolveError, so this is a part of
- // checkAndConnect function contract.
- resolveServer(userId);
+ q->resolveServer(userId);
+ if (flow)
+ connectSingleShot(q, &Connection::loginFlowsChanged, q,
+ [this, flow, connectFn] {
+ if (loginFlows.contains(*flow))
+ connectFn();
+ else
+ emit q->loginError(
+ tr("The homeserver at %1 does not support"
+ " the login flow '%2'")
+ .arg(data->baseUrl().toDisplayString()),
+ flow->type);
+ });
+ else
+ connectSingleShot(q, &Connection::homeserverChanged, q, connectFn);
} else
- emit resolveError(tr("%1 is an invalid homeserver URL")
- .arg(d->data->baseUrl().toString()));
+ emit q->resolveError(tr("Please provide the fully-qualified user id"
+ " (such as @user:example.org) so that the"
+ " homeserver could be resolved; the current"
+ " homeserver URL(%1) is not good")
+ .arg(data->baseUrl().toDisplayString()));
}
void Connection::logout()