diff --git a/src/core/accountstorage_kwallet.cpp b/src/core/accountstorage_kwallet.cpp index 363a921..3f4a408 100644 --- a/src/core/accountstorage_kwallet.cpp +++ b/src/core/accountstorage_kwallet.cpp @@ -1,216 +1,216 @@ /* Copyright (C) 2018 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "accountstorage_kwallet_p.h" #include "account.h" #include "../debug.h" #include #include #include #include #include using namespace KGAPI2; namespace { static const QString FolderName = QStringLiteral("LibKGAPI"); static const QString AccountNameKey = QStringLiteral("name"); static const QString ScopesKey = QStringLiteral("scopes"); static const QString AccessTokenKey = QStringLiteral("accessToken"); static const QString RefreshTokenKey = QStringLiteral("refreshToken"); static const QString ExpiresKey = QStringLiteral("expires"); } AccountStorage *KWalletStorageFactory::create() const { - return new KWalletStrorage(); + return new KWalletStorage(); } -KWalletStrorage::KWalletStrorage() +KWalletStorage::KWalletStorage() { } -KWalletStrorage::~KWalletStrorage() +KWalletStorage::~KWalletStorage() { delete mWallet; } -void KWalletStrorage::open(const std::function &callback) +void KWalletStorage::open(const std::function &callback) { const auto openedCallback = [=](bool opened) { mWalletOpening = false; if (!opened) { qCWarning(KGAPIDebug, "KWallet: failed to open"); mWallet->deleteLater(); mWallet = nullptr; callback(false); return; } if (mWallet->currentFolder() == FolderName) { callback(true); return; } if (!mWallet->hasFolder(FolderName)) { if (!mWallet->createFolder(FolderName)) { qCWarning(KGAPIDebug, "KWallet: failed to create a new folder"); callback(false); return; } } if (!mWallet->setFolder(FolderName)) { qCWarning(KGAPIDebug, "KWallet: failed to change folder"); callback(false); return; } callback(true); }; if (mWallet) { if (mWallet->isOpen(KWallet::Wallet::NetworkWallet())) { callback(true); return; } if (mWalletOpening) { QObject::connect(mWallet, &KWallet::Wallet::walletOpened, openedCallback); return; } delete mWallet; } mWalletOpening = true; mWallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), 0, KWallet::Wallet::Asynchronous); if (!mWallet) { qCWarning(KGAPIDebug, "KWallet: failed to open wallet (maybe it's disabled?"); callback(false); } else { QObject::connect(mWallet, &KWallet::Wallet::walletOpened, [this]() { mWalletOpening = false; }); QObject::connect(mWallet, &KWallet::Wallet::walletOpened, openedCallback); } } -bool KWalletStrorage::opened() const +bool KWalletStorage::opened() const { return mWallet && mWallet->isOpen(KWallet::Wallet::NetworkWallet()); } -AccountPtr KWalletStrorage::getAccount(const QString &apiKey, const QString &accountName) +AccountPtr KWalletStorage::getAccount(const QString &apiKey, const QString &accountName) { if (!opened()) { qCWarning(KGAPIDebug, "Trying to get an account from a closed wallet!"); return {}; } QMap accounts; mWallet->readMap(apiKey, accounts); const auto accountIt = accounts.constFind(accountName); if (accountIt == accounts.cend()) { return {}; } return parseAccount(*accountIt); } -bool KWalletStrorage::storeAccount(const QString &apiKey, const AccountPtr &account) +bool KWalletStorage::storeAccount(const QString &apiKey, const AccountPtr &account) { if (!opened()) { qCWarning(KGAPIDebug, "Trying to store an account in a closed wallet!"); return false; } QMap accounts; if (mWallet->readMap(apiKey, accounts) == 0) { accounts[account->accountName()] = serializeAccount(account); if (mWallet->writeMap(apiKey, accounts) != 0) { qCWarning(KGAPIDebug, "KWallet: failed to write accounts map"); return false; } } else { qCWarning(KGAPIDebug, "KWallet: failed to read accounts map"); return false; } return true; } -void KWalletStrorage::removeAccount(const QString &apiKey, const QString &accountName) +void KWalletStorage::removeAccount(const QString &apiKey, const QString &accountName) { if (!opened()) { qCWarning(KGAPIDebug, "Trying to remove an account from a closed wallet!"); return; } QMap accounts; if (mWallet->readMap(apiKey, accounts) == 0) { if (accounts.remove(accountName)) { if (!mWallet->writeMap(apiKey, accounts)) { qCWarning(KGAPIDebug, "KWallet: failed to write accounts map"); } } } else { qCWarning(KGAPIDebug, "KWallet: failed to read accounts map"); } } -AccountPtr KWalletStrorage::parseAccount(const QString &str) const +AccountPtr KWalletStorage::parseAccount(const QString &str) const { const auto doc = QJsonDocument::fromJson(str.toUtf8()); if (doc.isNull()) { qCWarning(KGAPIDebug, "Failed to parse account returned from KWallet"); return {}; } const auto obj = doc.object(); QJsonArray scopesArray = obj.value(ScopesKey).toArray(); QList scopes; scopes.reserve(scopesArray.size()); std::transform(scopesArray.constBegin(), scopesArray.constEnd(), std::back_inserter(scopes), [](const QJsonValue &val) { return QUrl::fromEncoded(val.toString().toUtf8()); }); auto acc = AccountPtr::create(obj.value(AccountNameKey).toString(), obj.value(AccessTokenKey).toString(), obj.value(RefreshTokenKey).toString(), scopes); acc->setExpireDateTime(QDateTime::fromString(obj.value(ExpiresKey).toString(), Qt::ISODate)); return acc; } -QString KWalletStrorage::serializeAccount(const AccountPtr &account) const +QString KWalletStorage::serializeAccount(const AccountPtr &account) const { QJsonArray scopesArray; const auto scopes = account->scopes(); std::transform(scopes.cbegin(), scopes.cend(), std::back_inserter(scopesArray), [](const QUrl &scope) { return scope.toString(QUrl::FullyEncoded); }); return QString::fromUtf8( QJsonDocument({ { AccountNameKey, account->accountName() }, { AccessTokenKey, account->accessToken() }, { RefreshTokenKey, account->refreshToken() }, { ExpiresKey, account->expireDateTime().toString(Qt::ISODate) }, { ScopesKey, scopesArray } }).toJson(QJsonDocument::Compact)); } diff --git a/src/core/accountstorage_kwallet_p.h b/src/core/accountstorage_kwallet_p.h index f14bf33..0d2e836 100644 --- a/src/core/accountstorage_kwallet_p.h +++ b/src/core/accountstorage_kwallet_p.h @@ -1,62 +1,62 @@ /* Copyright (C) 2018 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #ifndef LIBKGAPI2_ACCOUNSTSTORAGE_KWALLET_P_H_ #define LIBKGAPI2_ACCOUNSTSTORAGE_KWALLET_P_H_ #include "accountstorage_p.h" namespace KWallet { class Wallet; } namespace KGAPI2 { class KWalletStorageFactory : public AccountStorageFactory { public: AccountStorage *create() const override; }; -class KWalletStrorage : public AccountStorage +class KWalletStorage : public AccountStorage { public: - explicit KWalletStrorage(); - ~KWalletStrorage() override; + explicit KWalletStorage(); + ~KWalletStorage() override; void open(const std::function &callback) override; bool opened() const override; AccountPtr getAccount(const QString &apiKey, const QString &accountName) override; bool storeAccount(const QString &apiKey, const AccountPtr &account) override; void removeAccount(const QString &apiKey, const QString &accountName) override; private: AccountPtr parseAccount(const QString &str) const; QString serializeAccount(const AccountPtr &account) const; bool mWalletOpening = false; KWallet::Wallet *mWallet = nullptr; }; } #endif diff --git a/src/core/ui/authwidget_p.cpp b/src/core/ui/authwidget_p.cpp index 16aaed7..4fc61e5 100644 --- a/src/core/ui/authwidget_p.cpp +++ b/src/core/ui/authwidget_p.cpp @@ -1,369 +1,371 @@ /* Copyright 2012, 2013 Daniel Vrátil This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "authwidget_p.h" #include "account.h" #include "accountinfo/accountinfo.h" #include "accountinfo/accountinfofetchjob.h" #include "private/newtokensfetchjob_p.h" #include "../../debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KGAPI2; namespace { class WebView : public QWebEngineView { Q_OBJECT public: explicit WebView(QWidget *parent = nullptr) : QWebEngineView(parent) { // Don't store cookies, so that subsequent invocations of AuthJob won't remember // the previous accounts. QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); } void contextMenuEvent(QContextMenuEvent *e) override { // No menu e->accept(); } }; class WebPage : public QWebEnginePage { Q_OBJECT public: explicit WebPage(QObject *parent = nullptr) : QWebEnginePage(parent) , mLastError(nullptr) { } QWebEngineCertificateError *lastCertificateError() const { return mLastError; } bool certificateError(const QWebEngineCertificateError &err) override { if (mLastError) { delete mLastError; } mLastError = new QWebEngineCertificateError(err.error(), err.url(), err.isOverridable(), err.errorDescription()); Q_EMIT sslError(); return false; // don't let it through } Q_SIGNALS: void sslError(); private: QWebEngineCertificateError *mLastError; }; } AuthWidgetPrivate::AuthWidgetPrivate(AuthWidget *parent): QObject(), showProgressBar(true), progress(AuthWidget::None), q(parent) { } AuthWidgetPrivate::~AuthWidgetPrivate() { } void AuthWidgetPrivate::setSslIcon(const QString &iconName) { // FIXME: workaround for silly Breeze icons: the small 22x22 icons are // monochromatic, which is absolutely useless since we are trying to security // information here, so instead we force use the bigger 48x48 icons which // have colors and downscale them sslIndicator->setIcon(QIcon::fromTheme(iconName).pixmap(48)); } void AuthWidgetPrivate::setupUi() { vbox = new QVBoxLayout(q); q->setLayout(vbox); label = new QLabel(q); label->setText(QLatin1String("") % tr("Authorizing token. This should take just a moment...") % QLatin1String("")); label->setWordWrap(true); label->setAlignment(Qt::AlignCenter); label->setVisible(false); vbox->addWidget(label); auto hbox = new QHBoxLayout; hbox->setSpacing(0); sslIndicator = new QToolButton(q); connect(sslIndicator, &QToolButton::clicked, this, [this]() { auto page = qobject_cast(webview->page()); if (auto err = page->lastCertificateError()) { QMessageBox msg; msg.setIconPixmap(QIcon::fromTheme(QStringLiteral("security-low")).pixmap(64)); msg.setText(err->errorDescription()); msg.addButton(QMessageBox::Ok); msg.exec(); } }); hbox->addWidget(sslIndicator); urlEdit = new QLineEdit(q); urlEdit->setReadOnly(true); hbox->addWidget(urlEdit); vbox->addLayout(hbox); progressbar = new QProgressBar(q); progressbar->setMinimum(0); progressbar->setMaximum(100); progressbar->setValue(0); vbox->addWidget(progressbar); webview = new WebView(q); auto webpage = new WebPage(webview); connect(webpage, &WebPage::sslError, this, [this]() { setSslIcon(QStringLiteral("security-low")); }); webview->setPage(webpage); vbox->addWidget(webview); connect(webview, &QWebEngineView::loadProgress, progressbar, &QProgressBar::setValue); connect(webview, &QWebEngineView::urlChanged, this, &AuthWidgetPrivate::webviewUrlChanged); connect(webview, &QWebEngineView::loadFinished, this, &AuthWidgetPrivate::webviewFinished); } void AuthWidgetPrivate::setUrl(const QUrl &url) { webview->setUrl(url); webview->setFocus(); } void AuthWidgetPrivate::setVisible(bool visible) { sslIndicator->setVisible(visible); urlEdit->setVisible(visible); webview->setVisible(visible); if (showProgressBar && visible) { progressbar->setVisible(visible); } else { progressbar->setVisible(visible); } } void AuthWidgetPrivate::setProgress(AuthWidget::Progress progress) { qCDebug(KGAPIDebug) << progress; this->progress = progress; Q_EMIT q->progress(progress); } void AuthWidgetPrivate::emitError(const enum Error errCode, const QString& msg) { label->setVisible(true); sslIndicator->setVisible(false); urlEdit->setVisible(false); webview->setVisible(false); progressbar->setVisible(false); label->setText(QLatin1String("") % msg % QLatin1String("")); Q_EMIT q->error(errCode, msg); setProgress(AuthWidget::Error); } void AuthWidgetPrivate::webviewUrlChanged(const QUrl &url) { qCDebug(KGAPIDebug) << "URLChange:" << url; // Whoa! That should not happen! if (url.scheme() != QLatin1String("https")) { QTimer::singleShot(0, this, [this, url]() { QUrl sslUrl = url; sslUrl.setScheme(QStringLiteral("https")); webview->setUrl(sslUrl); }); return; } if (!isGoogleHost(url)) { // We handled SSL above, so we are secure. We are however outside of // accounts.google.com, which is a little suspicious in context of this class setSslIcon(QStringLiteral("security-medium")); return; } if (qobject_cast(webview->page())->lastCertificateError()) { setSslIcon(QStringLiteral("security-low")); } else { - // We have no way of obtaining current SSL certifiace from QWebEngine, but we + // We have no way of obtaining current SSL certificate from QWebEngine, but we // handled SSL and accounts.google.com cases above and QWebEngine did not report // any SSL error to us, so we can assume we are safe. setSslIcon(QStringLiteral("security-high")); } // Username and password inputs are loaded dynamically, so we only get // urlChanged, but not urlFinished. if (isUsernameFrame(url)) { if (!username.isEmpty()) { webview->page()->runJavaScript(QStringLiteral("document.getElementById(\"identifierId\").value = \"%1\";").arg(username)); } } else if (isPasswordFrame(url)) { if (!password.isEmpty()) { webview->page()->runJavaScript(QStringLiteral("var elems = document.getElementsByTagName(\"input\");" "for (var i = 0; i < elems.length; i++) {" " if (elems[i].type == \"password\" && elems[i].name == \"password\") {" " elems[i].value = \"%1\";" " break;" " }" "}").arg(password)); } } } void AuthWidgetPrivate::webviewFinished(bool ok) { if (!ok) { qCWarning(KGAPIDebug) << "Failed to load" << webview->url(); } const QUrl url = webview->url(); urlEdit->setText(url.toDisplayString(QUrl::PrettyDecoded)); urlEdit->setCursorPosition(0); qCDebug(KGAPIDebug) << "URLFinished:" << url; } void AuthWidgetPrivate::socketError(QAbstractSocket::SocketError socketError) { if (connection) connection->deleteLater(); qCDebug(KGAPIDebug) << QStringLiteral("Socket error when receiving response: %1").arg(socketError); emitError(InvalidResponse, tr("Error receiving response: %1").arg(socketError)); } void AuthWidgetPrivate::socketReady() { Q_ASSERT(connection); const QByteArray data = connection->readLine(); + connection->write("HTTP/1.1 200 OK\n"); + connection->flush(); connection->deleteLater(); qCDebug(KGAPIDebug) << QStringLiteral("Got connection on socket"); if (webview) { // when running in tests we don't have webview or any other widgets webview->stop(); } setVisible(false); if (label) { label->setVisible(true); } const auto line = data.split(' '); if (line.size() != 3 || line.at(0) != QByteArray("GET") || !line.at(2).startsWith(QByteArray("HTTP/1.1"))) { qCDebug(KGAPIDebug) << QStringLiteral("Token response invalid"); emitError(InvalidResponse, tr("Token response invalid")); return; } //qCDebug(KGAPIDebug) << "Receiving data on socket: " << data; const QUrl url(QString::fromLatin1(line.at(1))); const QUrlQuery query(url); const QString code = query.queryItemValue(QStringLiteral("code")); if (code.isEmpty()) { const QString error = query.queryItemValue(QStringLiteral("error")); if (!error.isEmpty()) { emitError(UnknownError, error); qCDebug(KGAPIDebug) << error; } else { qCDebug(KGAPIDebug) << QStringLiteral("Could not extract token from HTTP answer"); emitError(InvalidResponse, tr("Could not extract token from HTTP answer")); } return; } Q_ASSERT(serverPort != -1); auto fetch = new KGAPI2::NewTokensFetchJob(code, apiKey, secretKey, serverPort); connect(fetch, &Job::finished, this, &AuthWidgetPrivate::tokensReceived); } void AuthWidgetPrivate::tokensReceived(KGAPI2::Job* job) { KGAPI2::NewTokensFetchJob *tokensFetchJob = qobject_cast(job); account->setAccessToken(tokensFetchJob->accessToken()); account->setRefreshToken(tokensFetchJob->refreshToken()); account->setExpireDateTime(QDateTime::currentDateTime().addSecs(tokensFetchJob->expiresIn())); tokensFetchJob->deleteLater(); KGAPI2::AccountInfoFetchJob *fetchJob = new KGAPI2::AccountInfoFetchJob(account, this); connect(fetchJob, &Job::finished, this, &AuthWidgetPrivate::accountInfoReceived); qCDebug(KGAPIDebug) << "Requesting AccountInfo"; } void AuthWidgetPrivate::accountInfoReceived(KGAPI2::Job* job) { if (job->error()) { qCDebug(KGAPIDebug) << "Error when retrieving AccountInfo:" << job->errorString(); emitError((enum Error) job->error(), job->errorString()); return; } KGAPI2::ObjectsList objects = qobject_cast(job)->items(); Q_ASSERT(!objects.isEmpty()); KGAPI2::AccountInfoPtr accountInfo = objects.first().staticCast(); account->setAccountName(accountInfo->email()); job->deleteLater(); Q_EMIT q->authenticated(account); setProgress(AuthWidget::Finished); } #include "authwidget_p.moc"