Credentials: Use per-account keychain entries #5830

This requires a lot of migration code: the old entries need to be read,
saved to the new locations and then deleted.
This commit is contained in:
Christian Kamm 2017-09-12 17:15:22 +02:00 committed by ckamm
parent 0b4fd52d63
commit 671599c8b2
6 changed files with 130 additions and 32 deletions

View file

@ -54,6 +54,7 @@ ShibbolethCredentials::ShibbolethCredentials()
, _ready(false) , _ready(false)
, _stillValid(false) , _stillValid(false)
, _browser(0) , _browser(0)
, _keychainMigration(false)
{ {
} }
@ -62,6 +63,7 @@ ShibbolethCredentials::ShibbolethCredentials(const QNetworkCookie &cookie)
, _stillValid(true) , _stillValid(true)
, _browser(0) , _browser(0)
, _shibCookie(cookie) , _shibCookie(cookie)
, _keychainMigration(false)
{ {
} }
@ -131,14 +133,21 @@ void ShibbolethCredentials::fetchFromKeychain()
Q_EMIT fetched(); Q_EMIT fetched();
} else { } else {
_url = _account->url(); _url = _account->url();
_keychainMigration = false;
fetchFromKeychainHelper();
}
}
void ShibbolethCredentials::fetchFromKeychainHelper()
{
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
job->setSettings(ConfigFile::settingsWithGroup(Theme::instance()->appName(), job).release()); job->setSettings(ConfigFile::settingsWithGroup(Theme::instance()->appName(), job).release());
job->setInsecureFallback(false); job->setInsecureFallback(false);
job->setKey(keychainKey(_account->url().toString(), user())); job->setKey(keychainKey(_url.toString(), user(),
_keychainMigration ? QString() : _account->id()));
connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotReadJobDone(QKeychain::Job *))); connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotReadJobDone(QKeychain::Job *)));
job->start(); job->start();
} }
}
void ShibbolethCredentials::askFromUser() void ShibbolethCredentials::askFromUser()
{ {
@ -242,6 +251,16 @@ void ShibbolethCredentials::slotBrowserRejected()
void ShibbolethCredentials::slotReadJobDone(QKeychain::Job *job) void ShibbolethCredentials::slotReadJobDone(QKeychain::Job *job)
{ {
// If we can't find the credentials at the keys that include the account id,
// try to read them from the legacy locations that don't have a account id.
if (!_keychainMigration && job->error() == QKeychain::EntryNotFound) {
qCWarning(lcShibboleth)
<< "Could not find keychain entry, attempting to read from legacy location";
_keychainMigration = true;
fetchFromKeychainHelper();
return;
}
if (job->error() == QKeychain::NoError) { if (job->error() == QKeychain::NoError) {
ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(job); ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(job);
delete readJob->settings(); delete readJob->settings();
@ -260,6 +279,19 @@ void ShibbolethCredentials::slotReadJobDone(QKeychain::Job *job)
_ready = false; _ready = false;
Q_EMIT fetched(); Q_EMIT fetched();
} }
// If keychain data was read from legacy location, wipe these entries and store new ones
if (_keychainMigration && _ready) {
persist();
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
job->setSettings(ConfigFile::settingsWithGroup(Theme::instance()->appName(), job).release());
job->setKey(keychainKey(_account->url().toString(), user(), QString()));
job->start();
qCWarning(lcShibboleth) << "Migrated old keychain entries";
}
} }
void ShibbolethCredentials::showLoginWindow() void ShibbolethCredentials::showLoginWindow()
@ -313,7 +345,7 @@ void ShibbolethCredentials::storeShibCookie(const QNetworkCookie &cookie)
job->setSettings(ConfigFile::settingsWithGroup(Theme::instance()->appName(), job).release()); job->setSettings(ConfigFile::settingsWithGroup(Theme::instance()->appName(), job).release());
// we don't really care if it works... // we don't really care if it works...
//connect(job, SIGNAL(finished(QKeychain::Job*)), SLOT(slotWriteJobDone(QKeychain::Job*))); //connect(job, SIGNAL(finished(QKeychain::Job*)), SLOT(slotWriteJobDone(QKeychain::Job*)));
job->setKey(keychainKey(_account->url().toString(), user())); job->setKey(keychainKey(_account->url().toString(), user(), _account->id()));
job->setTextData(QString::fromUtf8(cookie.toRawForm())); job->setTextData(QString::fromUtf8(cookie.toRawForm()));
job->start(); job->start();
} }
@ -322,7 +354,7 @@ void ShibbolethCredentials::removeShibCookie()
{ {
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName()); DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
job->setSettings(ConfigFile::settingsWithGroup(Theme::instance()->appName(), job).release()); job->setSettings(ConfigFile::settingsWithGroup(Theme::instance()->appName(), job).release());
job->setKey(keychainKey(_account->url().toString(), user())); job->setKey(keychainKey(_account->url().toString(), user(), _account->id()));
job->start(); job->start();
} }
@ -336,5 +368,4 @@ void ShibbolethCredentials::addToCookieJar(const QNetworkCookie &cookie)
jar->blockSignals(false); jar->blockSignals(false);
} }
} // namespace OCC } // namespace OCC

View file

@ -84,6 +84,10 @@ private:
void storeShibCookie(const QNetworkCookie &cookie); void storeShibCookie(const QNetworkCookie &cookie);
void removeShibCookie(); void removeShibCookie();
void addToCookieJar(const QNetworkCookie &cookie); void addToCookieJar(const QNetworkCookie &cookie);
/// Reads data from keychain, progressing to slotReadJobDone
void fetchFromKeychainHelper();
QUrl _url; QUrl _url;
QByteArray prepareCookieData() const; QByteArray prepareCookieData() const;
@ -92,6 +96,7 @@ private:
QPointer<ShibbolethWebView> _browser; QPointer<ShibbolethWebView> _browser;
QNetworkCookie _shibCookie; QNetworkCookie _shibCookie;
QString _user; QString _user;
bool _keychainMigration;
}; };
} // namespace OCC } // namespace OCC

View file

@ -34,7 +34,7 @@ void AbstractCredentials::setAccount(Account *account)
_account = account; _account = account;
} }
QString AbstractCredentials::keychainKey(const QString &url, const QString &user) QString AbstractCredentials::keychainKey(const QString &url, const QString &user, const QString &accountId)
{ {
QString u(url); QString u(url);
if (u.isEmpty()) { if (u.isEmpty()) {
@ -51,6 +51,9 @@ QString AbstractCredentials::keychainKey(const QString &url, const QString &user
} }
QString key = user + QLatin1Char(':') + u; QString key = user + QLatin1Char(':') + u;
if (!accountId.isEmpty()) {
key += QLatin1Char(':') + accountId;
}
return key; return key;
} }
} // namespace OCC } // namespace OCC

View file

@ -85,7 +85,7 @@ public:
*/ */
virtual void forgetSensitiveData() = 0; virtual void forgetSensitiveData() = 0;
static QString keychainKey(const QString &url, const QString &user); static QString keychainKey(const QString &url, const QString &user, const QString &accountId);
Q_SIGNALS: Q_SIGNALS:
/** Emitted when fetchFromKeychain() is done. /** Emitted when fetchFromKeychain() is done.

View file

@ -104,6 +104,7 @@ static void addSettingsToJob(Account *account, QKeychain::Job *job)
HttpCredentials::HttpCredentials() HttpCredentials::HttpCredentials()
: _ready(false) : _ready(false)
, _keychainMigration(false)
{ {
} }
@ -114,6 +115,7 @@ HttpCredentials::HttpCredentials(const QString &user, const QString &password, c
, _ready(true) , _ready(true)
, _clientSslKey(key) , _clientSslKey(key)
, _clientSslCertificate(certificate) , _clientSslCertificate(certificate)
, _keychainMigration(false)
{ {
} }
@ -175,21 +177,43 @@ void HttpCredentials::fetchFromKeychain()
return; return;
} }
const QString kck = keychainKey(_account->url().toString(), _user);
if (_ready) { if (_ready) {
Q_EMIT fetched(); Q_EMIT fetched();
} else { } else {
_keychainMigration = false;
fetchFromKeychainHelper();
}
}
void HttpCredentials::fetchFromKeychainHelper()
{
// Read client cert from keychain // Read client cert from keychain
const QString kck = keychainKey(_account->url().toString(), _user + clientCertificatePEMC); const QString kck = keychainKey(
_account->url().toString(),
_user + clientCertificatePEMC,
_keychainMigration ? QString() : _account->id());
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job); addSettingsToJob(_account, job);
job->setInsecureFallback(false); job->setInsecureFallback(false);
job->setKey(kck); job->setKey(kck);
connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotReadClientCertPEMJobDone(QKeychain::Job *))); connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotReadClientCertPEMJobDone(QKeychain::Job *)));
job->start(); job->start();
} }
void HttpCredentials::deleteOldKeychainEntries()
{
auto startDeleteJob = [this](QString user) {
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job);
job->setInsecureFallback(true);
job->setKey(keychainKey(_account->url().toString(), user, QString()));
job->start();
};
startDeleteJob(_user);
startDeleteJob(_user + clientKeyPEMC);
startDeleteJob(_user + clientCertificatePEMC);
} }
void HttpCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incoming) void HttpCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incoming)
@ -204,12 +228,15 @@ void HttpCredentials::slotReadClientCertPEMJobDone(QKeychain::Job *incoming)
} }
// Load key too // Load key too
const QString kck = keychainKey(_account->url().toString(), _user + clientKeyPEMC); const QString kck = keychainKey(
_account->url().toString(),
_user + clientKeyPEMC,
_keychainMigration ? QString() : _account->id());
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job); addSettingsToJob(_account, job);
job->setInsecureFallback(false); job->setInsecureFallback(false);
job->setKey(kck); job->setKey(kck);
connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotReadClientKeyPEMJobDone(QKeychain::Job *))); connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotReadClientKeyPEMJobDone(QKeychain::Job *)));
job->start(); job->start();
} }
@ -236,12 +263,15 @@ void HttpCredentials::slotReadClientKeyPEMJobDone(QKeychain::Job *incoming)
} }
// Now fetch the actual server password // Now fetch the actual server password
const QString kck = keychainKey(_account->url().toString(), _user); const QString kck = keychainKey(
_account->url().toString(),
_user,
_keychainMigration ? QString() : _account->id());
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName()); ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
addSettingsToJob(_account, job); addSettingsToJob(_account, job);
job->setInsecureFallback(false); job->setInsecureFallback(false);
job->setKey(kck); job->setKey(kck);
connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotReadJobDone(QKeychain::Job *))); connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotReadJobDone(QKeychain::Job *)));
job->start(); job->start();
} }
@ -258,6 +288,17 @@ bool HttpCredentials::stillValid(QNetworkReply *reply)
void HttpCredentials::slotReadJobDone(QKeychain::Job *incomingJob) void HttpCredentials::slotReadJobDone(QKeychain::Job *incomingJob)
{ {
QKeychain::ReadPasswordJob *job = static_cast<ReadPasswordJob *>(incomingJob); QKeychain::ReadPasswordJob *job = static_cast<ReadPasswordJob *>(incomingJob);
QKeychain::Error error = job->error();
// If we can't find the credentials at the keys that include the account id,
// try to read them from the legacy locations that don't have a account id.
if (!_keychainMigration && error == QKeychain::EntryNotFound) {
qCWarning(lcHttpCredentials)
<< "Could not find keychain entries, attempting to read from legacy locations";
_keychainMigration = true;
fetchFromKeychainHelper();
return;
}
bool isOauth = _account->credentialSetting(QLatin1String(isOAuthC)).toBool(); bool isOauth = _account->credentialSetting(QLatin1String(isOAuthC)).toBool();
if (isOauth) { if (isOauth) {
@ -270,8 +311,6 @@ void HttpCredentials::slotReadJobDone(QKeychain::Job *incomingJob)
qCWarning(lcHttpCredentials) << "Strange: User is empty!"; qCWarning(lcHttpCredentials) << "Strange: User is empty!";
} }
QKeychain::Error error = job->error();
if (!_refreshToken.isEmpty() && error == NoError) { if (!_refreshToken.isEmpty() && error == NoError) {
refreshAccessToken(); refreshAccessToken();
} else if (!_password.isEmpty() && error == NoError) { } else if (!_password.isEmpty() && error == NoError) {
@ -290,6 +329,13 @@ void HttpCredentials::slotReadJobDone(QKeychain::Job *incomingJob)
_ready = false; _ready = false;
emit fetched(); emit fetched();
} }
// If keychain data was read from legacy location, wipe these entries and store new ones
if (_keychainMigration && _ready) {
persist();
deleteOldKeychainEntries();
qCWarning(lcHttpCredentials) << "Migrated old keychain entries";
}
} }
bool HttpCredentials::refreshAccessToken() bool HttpCredentials::refreshAccessToken()
@ -345,7 +391,7 @@ void HttpCredentials::invalidateToken()
// User must be fetched from config file to generate a valid key // User must be fetched from config file to generate a valid key
fetchUser(); fetchUser();
const QString kck = keychainKey(_account->url().toString(), _user); const QString kck = keychainKey(_account->url().toString(), _user, _account->id());
if (kck.isEmpty()) { if (kck.isEmpty()) {
qCWarning(lcHttpCredentials) << "InvalidateToken: User is empty, bailing out!"; qCWarning(lcHttpCredentials) << "InvalidateToken: User is empty, bailing out!";
return; return;
@ -407,7 +453,7 @@ void HttpCredentials::persist()
addSettingsToJob(_account, job); addSettingsToJob(_account, job);
job->setInsecureFallback(false); job->setInsecureFallback(false);
connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotWriteClientCertPEMJobDone(QKeychain::Job *))); connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotWriteClientCertPEMJobDone(QKeychain::Job *)));
job->setKey(keychainKey(_account->url().toString(), _user + clientCertificatePEMC)); job->setKey(keychainKey(_account->url().toString(), _user + clientCertificatePEMC, _account->id()));
job->setBinaryData(_clientSslCertificate.toPem()); job->setBinaryData(_clientSslCertificate.toPem());
job->start(); job->start();
} }
@ -420,7 +466,7 @@ void HttpCredentials::slotWriteClientCertPEMJobDone(Job *incomingJob)
addSettingsToJob(_account, job); addSettingsToJob(_account, job);
job->setInsecureFallback(false); job->setInsecureFallback(false);
connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotWriteClientKeyPEMJobDone(QKeychain::Job *))); connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotWriteClientKeyPEMJobDone(QKeychain::Job *)));
job->setKey(keychainKey(_account->url().toString(), _user + clientKeyPEMC)); job->setKey(keychainKey(_account->url().toString(), _user + clientKeyPEMC, _account->id()));
job->setBinaryData(_clientSslKey.toPem()); job->setBinaryData(_clientSslKey.toPem());
job->start(); job->start();
} }
@ -432,7 +478,7 @@ void HttpCredentials::slotWriteClientKeyPEMJobDone(Job *incomingJob)
addSettingsToJob(_account, job); addSettingsToJob(_account, job);
job->setInsecureFallback(false); job->setInsecureFallback(false);
connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotWriteJobDone(QKeychain::Job *))); connect(job, SIGNAL(finished(QKeychain::Job *)), SLOT(slotWriteJobDone(QKeychain::Job *)));
job->setKey(keychainKey(_account->url().toString(), _user)); job->setKey(keychainKey(_account->url().toString(), _user, _account->id()));
job->setTextData(isUsingOAuth() ? _refreshToken : _password); job->setTextData(isUsingOAuth() ? _refreshToken : _password);
job->start(); job->start();
} }

View file

@ -119,6 +119,18 @@ private Q_SLOTS:
void slotWriteJobDone(QKeychain::Job *); void slotWriteJobDone(QKeychain::Job *);
protected: protected:
/** Reads data from keychain locations
*
* Goes through
* slotReadClientCertPEMJobDone to
* slotReadClientCertPEMJobDone to
* slotReadJobDone
*/
void fetchFromKeychainHelper();
/// Wipes legacy keychain locations
void deleteOldKeychainEntries();
QString _user; QString _user;
QString _password; // user's password, or access_token for OAuth QString _password; // user's password, or access_token for OAuth
QString _refreshToken; // OAuth _refreshToken, set if OAuth is used. QString _refreshToken; // OAuth _refreshToken, set if OAuth is used.
@ -128,6 +140,7 @@ protected:
bool _ready; bool _ready;
QSslKey _clientSslKey; QSslKey _clientSslKey;
QSslCertificate _clientSslCertificate; QSslCertificate _clientSslCertificate;
bool _keychainMigration;
}; };