Use non blocking passord reading

This also remove the do while loop that might cause problem and expose
the error message to the user.

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
This commit is contained in:
Carl Schwan
2021-12-12 18:18:34 +01:00
parent 6e1c07047e
commit d1bbb5e3f7
2 changed files with 88 additions and 60 deletions

View File

@@ -262,31 +262,48 @@ void Controller::invokeLogin()
id = accountId; id = accountId;
} }
if (!account.homeserver().isEmpty()) { if (!account.homeserver().isEmpty()) {
auto accessToken = loadAccessTokenFromKeyChain(account); auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, id, this, accessTokenLoadingJob](QKeychain::Job *) {
auto connection = new Connection(account.homeserver()); AccountSettings account{accountId};
connect(connection, &Connection::connected, this, [this, connection, id] { QString accessToken;
connection->loadState(); if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
addConnection(connection); accessToken = accessTokenLoadingJob->binaryData();
if (connection->userId() == id) {
setActiveConnection(connection);
connectSingleShot(connection, &Connection::syncDone, this, &Controller::initiated);
}
});
connect(connection, &Connection::loginError, this, [this, connection](const QString &error, const QString &) {
if (error == "Unrecognised access token") {
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
logout(connection, false);
} else { } else {
Q_EMIT errorOccured(i18n("Login Failed: %1", error)); // No access token from the keychain, try token file
logout(connection, true); // TODO FIXME this code is racy since the file might have
// already been removed. But since the other code do a blocking
// dbus call, the probability are not high that it will happen.
// loadAccessTokenFromFile is also mostly legacy nowadays
accessToken = loadAccessTokenFromFile(account);
if (accessToken.isEmpty()) {
return;
}
} }
Q_EMIT initiated();
auto connection = new Connection(account.homeserver());
connect(connection, &Connection::connected, this, [this, connection, id] {
connection->loadState();
addConnection(connection);
if (connection->userId() == id) {
setActiveConnection(connection);
connectSingleShot(connection, &Connection::syncDone, this, &Controller::initiated);
}
});
connect(connection, &Connection::loginError, this, [this, connection](const QString &error, const QString &) {
if (error == "Unrecognised access token") {
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
logout(connection, false);
} else {
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
logout(connection, true);
}
Q_EMIT initiated();
});
connect(connection, &Connection::networkError, this, [this](const QString &error, const QString &, int, int) {
Q_EMIT errorOccured(i18n("Network Error: %1", error));
});
connection->assumeIdentity(account.userId(), accessToken, account.deviceId());
}); });
connect(connection, &Connection::networkError, this, [this](const QString &error, const QString &, int, int) {
Q_EMIT errorOccured(i18n("Network Error: %1", error));
});
connection->assumeIdentity(account.userId(), accessToken, account.deviceId());
} }
} }
if (accounts.isEmpty()) { if (accounts.isEmpty()) {
@@ -309,48 +326,54 @@ QByteArray Controller::loadAccessTokenFromFile(const AccountSettings &account)
return {}; return {};
} }
QByteArray Controller::loadAccessTokenFromKeyChain(const AccountSettings &account) QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
{ {
QKeychain::Error error; qDebug() << "Reading access token from the keychain for" << account.userId();
QString errorString; auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
do { job->setKey(account.userId());
qDebug() << "Reading access token from the keychain for" << account.userId();
QKeychain::ReadPasswordJob job(qAppName());
job.setAutoDelete(false);
job.setKey(account.userId());
QEventLoop loop;
QKeychain::ReadPasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
if (job.error() == QKeychain::Error::NoError) { // Handling of errors
return job.binaryData(); connect(job, &QKeychain::Job::emitFinishedWithError, this, [this, &account, job](QKeychain::Error error, const QString &errorString) {
} if (error == QKeychain::Error::EntryNotFound) {
Q_EMIT globalErrorOccured(i18n("Unable to read access token"), i18n("Please make sure that the keychain is opened.")); // no access token from the keychain, try token file
error = job.error(); auto accessToken = loadAccessTokenFromFile(account);
errorString = job.errorString(); if (!accessToken.isEmpty()) {
} while (error == QKeychain::Error::OtherError); qDebug() << "Migrating the access token from file to the keychain for " << account.userId();
bool removed = false;
qWarning() << "Could not read the access token from the keychain:" << errorString; bool saved = saveAccessTokenToKeyChain(account, accessToken);
// no access token from the keychain, try token file if (saved) {
auto accessToken = loadAccessTokenFromFile(account); QFile accountTokenFile{accessTokenFileName(account)};
if (error == QKeychain::Error::EntryNotFound) { removed = accountTokenFile.remove();
if (!accessToken.isEmpty()) { }
qDebug() << "Migrating the access token from file to the keychain for " << account.userId(); if (!(saved && removed)) {
bool removed = false; qDebug() << "Migrating the access token from the file to the keychain "
bool saved = saveAccessTokenToKeyChain(account, accessToken); "failed";
if (saved) { }
QFile accountTokenFile{accessTokenFileName(account)}; return;
removed = accountTokenFile.remove();
}
if (!(saved && removed)) {
qDebug() << "Migrating the access token from the file to the keychain "
"failed";
} }
} }
}
return accessToken; switch (error) {
case QKeychain::EntryNotFound:
Q_EMIT globalErrorOccured(i18n("Access token wasn't found"), i18n("Maybe it was deleted?"));
break;
case QKeychain::AccessDeniedByUser:
case QKeychain::AccessDenied:
Q_EMIT globalErrorOccured(i18n("Access to keychain was denied."), i18n("Please allow NeoChat to read the access token"));
break;
case QKeychain::NoBackendAvailable:
Q_EMIT globalErrorOccured(i18n("No keychain available."), i18n("Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
break;
case QKeychain::OtherError:
Q_EMIT globalErrorOccured(i18n("Unable to read access token"), errorString);
break;
default:
break;
}
});
job->start();
return job;
} }
bool Controller::saveAccessTokenToFile(const AccountSettings &account, const QByteArray &accessToken) bool Controller::saveAccessTokenToFile(const AccountSettings &account, const QByteArray &accessToken)

View File

@@ -23,6 +23,11 @@ class NeoChatRoom;
class NeoChatUser; class NeoChatUser;
class QQuickWindow; class QQuickWindow;
namespace QKeychain
{
class ReadPasswordJob;
}
using namespace Quotient; using namespace Quotient;
class Controller : public QObject class Controller : public QObject
@@ -99,7 +104,7 @@ private:
bool m_busy = false; bool m_busy = false;
static QByteArray loadAccessTokenFromFile(const AccountSettings &account); static QByteArray loadAccessTokenFromFile(const AccountSettings &account);
QByteArray loadAccessTokenFromKeyChain(const AccountSettings &account); QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const AccountSettings &account);
void loadSettings(); void loadSettings();
void saveSettings() const; void saveSettings() const;