diff --git a/src/core/private/newtokensfetchjob.cpp b/src/core/private/newtokensfetchjob.cpp index 283f757..23643d2 100644 --- a/src/core/private/newtokensfetchjob.cpp +++ b/src/core/private/newtokensfetchjob.cpp @@ -1,141 +1,143 @@ /* * Copyright (C) 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 "newtokensfetchjob_p.h" #include "../debug.h" #include #include #include #include using namespace KGAPI2; class Q_DECL_HIDDEN NewTokensFetchJob::Private { public: Private() : expiresIn(0) { } QString tmpToken; QString apiKey; QString secretKey; + int localPort; QString accessToken; QString refreshToken; qulonglong expiresIn; }; -NewTokensFetchJob::NewTokensFetchJob(const QString &tmpToken, const QString &apiKey, const QString &secretKey, QObject *parent): +NewTokensFetchJob::NewTokensFetchJob(const QString &tmpToken, const QString &apiKey, const QString &secretKey, int localPort, QObject *parent): Job(parent), d(new Private) { d->tmpToken = tmpToken; d->apiKey = apiKey; d->secretKey = secretKey; + d->localPort = localPort; } NewTokensFetchJob::~NewTokensFetchJob() { delete d; } QString NewTokensFetchJob::accessToken() const { if (isRunning()) { qCWarning(KGAPIDebug) << "Called accessToken() on running job!"; return QString(); } return d->accessToken; } QString NewTokensFetchJob::refreshToken() const { if (isRunning()) { qCWarning(KGAPIDebug) << "Called refreshToken() on running job!"; return QString(); } return d->refreshToken; } qulonglong NewTokensFetchJob::expiresIn() const { if (isRunning()) { qCWarning(KGAPIDebug) << "Called expiresIn() on running job!"; return 0; } return d->expiresIn; } void NewTokensFetchJob::start() { QNetworkRequest request; request.setUrl(QUrl(QStringLiteral("https://accounts.google.com/o/oauth2/token"))); request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded")); QUrl params; params.addQueryItem(QStringLiteral("client_id"), d->apiKey); params.addQueryItem(QStringLiteral("client_secret"), d->secretKey); params.addQueryItem(QStringLiteral("code"), d->tmpToken); - params.addQueryItem(QStringLiteral("redirect_uri"), QStringLiteral("urn:ietf:wg:oauth:2.0:oob")); + params.addQueryItem(QStringLiteral("redirect_uri"), QStringLiteral("http://127.0.0.1:%1").arg(d->localPort)); // we need to use the same URL as in AuthWidget params.addQueryItem(QStringLiteral("grant_type"), QStringLiteral("authorization_code")); enqueueRequest(request, params.encodedQuery()); } void NewTokensFetchJob::dispatchRequest(QNetworkAccessManager* accessManager, const QNetworkRequest& request, const QByteArray& data, const QString& contentType) { Q_UNUSED(contentType); accessManager->post(request, data); } void NewTokensFetchJob::handleReply(const QNetworkReply *reply, const QByteArray& rawData) { Q_UNUSED(reply); QJsonDocument document = QJsonDocument::fromJson(rawData); if (document.isNull()) { qCDebug(KGAPIDebug) << "Failed to parse server response."; qCDebug(KGAPIRaw) << rawData; setError(KGAPI2::AuthCancelled); setErrorString(tr("Failed to parse server response.")); return; } QVariantMap parsed_data = document.toVariant().toMap(); qCDebug(KGAPIRaw) << "Retrieved new tokens pair:" << parsed_data; d->accessToken = parsed_data.value(QStringLiteral("access_token")).toString(); d->refreshToken = parsed_data.value(QStringLiteral("refresh_token")).toString(); d->expiresIn = parsed_data.value(QStringLiteral("expires_in")).toULongLong(); } diff --git a/src/core/private/newtokensfetchjob_p.h b/src/core/private/newtokensfetchjob_p.h index e0af281..018af04 100644 --- a/src/core/private/newtokensfetchjob_p.h +++ b/src/core/private/newtokensfetchjob_p.h @@ -1,61 +1,61 @@ /* * This file is part of LibKGAPI library * * Copyright (C) 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 . */ #ifndef LIBKGAPI2_PRIVATE_NEWTOKENSFETCHJOB_P_H #define LIBKGAPI2_PRIVATE_NEWTOKENSFETCHJOB_P_H #include "job.h" #include "kgapicore_export.h" namespace KGAPI2 { /** * @internal * * Exported, but not publicly avilable! */ class KGAPICORE_EXPORT NewTokensFetchJob : public KGAPI2::Job { Q_OBJECT public: - explicit NewTokensFetchJob(const QString &tmpToken, const QString &apiKey, const QString &secretKey, QObject* parent = nullptr); + explicit NewTokensFetchJob(const QString &tmpToken, const QString &apiKey, const QString &secretKey, int localPort, QObject* parent = nullptr); virtual ~NewTokensFetchJob(); QString accessToken() const; QString refreshToken() const; qulonglong expiresIn() const; protected: void start() override; void handleReply(const QNetworkReply *reply, const QByteArray& rawData) override; void dispatchRequest(QNetworkAccessManager* accessManager, const QNetworkRequest& request, const QByteArray& data, const QString& contentType) override; private: class Private; Private * const d; friend class Private; }; } // namespace KGAPI2 #endif // LIBKGAPI2_PRIVATE_NEWTOKENSFETCHJOB_P_H diff --git a/src/core/ui/authwidget.cpp b/src/core/ui/authwidget.cpp index ac09b63..1612396 100644 --- a/src/core/ui/authwidget.cpp +++ b/src/core/ui/authwidget.cpp @@ -1,118 +1,132 @@ /* Copyright 2012 Dan Vratil 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.h" #include "authwidget_p.h" #include "../../debug.h" +#include using namespace KGAPI2; AuthWidget::AuthWidget(QWidget* parent): QWidget(parent), d(new AuthWidgetPrivate(this)) { } AuthWidget::~AuthWidget() { delete d; } void AuthWidget::setUsername(const QString& username) { d->username = username; } void AuthWidget::setPassword(const QString& password) { d->password = password; } void AuthWidget::clearCredentials() { d->username.clear(); d->password.clear(); } void AuthWidget::setAccount(const AccountPtr& account) { d->account = account; } void AuthWidget::setShowProgressBar(bool showProgressBar) { d->showProgressBar = showProgressBar; if (showProgressBar && d->progress == UserLogin) { d->progressbar->setVisible(true); } else { d->progressbar->setVisible(false); } } bool AuthWidget::getShowProgressBar() const { return d->showProgressBar; } AuthWidget::Progress AuthWidget::getProgress() const { return d->progress; } void AuthWidget::authenticate() { Q_ASSERT(!d->apiKey.isEmpty()); if (d->account.isNull()) { Q_EMIT error(InvalidAccount, tr("Invalid account")); return; } if (d->account->scopes().isEmpty()) { Q_EMIT error(InvalidAccount, tr("No scopes to authenticate for")); return; } QStringList scopes; scopes.reserve(d->account->scopes().size()); Q_FOREACH(const QUrl & scope, d->account->scopes()) { scopes << scope.toString(); } + d->server = new QTcpServer(this); + if (! d->server->listen(QHostAddress::LocalHost)) { + Q_EMIT error(InvalidAccount, tr("Could not start oauth http server")); + return; + } + d->serverPort = d->server->serverPort(); + connect(d->server, &QTcpServer::newConnection, [&]() { + d->connection = d->server->nextPendingConnection(); + d->connection->setParent(this); + connect(d->connection, &QTcpSocket::readyRead, d, &AuthWidgetPrivate::socketReady); + d->server->close(); + delete d->server; + }); QUrl url(QStringLiteral("https://accounts.google.com/o/oauth2/auth")); url.addQueryItem(QStringLiteral("client_id"), d->apiKey); - url.addQueryItem(QStringLiteral("redirect_uri"), QStringLiteral("urn:ietf:wg:oauth:2.0:oob")); + url.addQueryItem(QStringLiteral("redirect_uri"), QStringLiteral("http://127.0.0.1:%1").arg(d->serverPort)); url.addQueryItem(QStringLiteral("scope"), scopes.join(QStringLiteral(" "))); url.addQueryItem(QStringLiteral("response_type"), QStringLiteral("code")); qCDebug(KGAPIRaw) << "Requesting new token:" << url; d->sslIndicator->setVisible(true); d->urlEdit->setVisible(true); d->webview->setVisible(true); if (d->showProgressBar) { d->progressbar->setVisible(true); } d->webview->setUrl(url); d->setProgress(AuthWidget::UserLogin); } diff --git a/src/core/ui/authwidget_p.cpp b/src/core/ui/authwidget_p.cpp index 159b9a6..3db810c 100644 --- a/src/core/ui/authwidget_p.cpp +++ b/src/core/ui/authwidget_p.cpp @@ -1,341 +1,321 @@ /* 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 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) { setupUi(); } 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::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 // 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)); } - } else if (isTokenPage(url)) { - /* Access token here - hide browser and tell user to wait until we - * finish the authentication process ourselves */ - sslIndicator->setVisible(false); - urlEdit->setVisible(false); - webview->setVisible(false); - progressbar->setVisible(false); - label->setVisible(true); - - setProgress(AuthWidget::TokensRetrieval); } } -void AuthWidgetPrivate::webviewFinished(bool ok) -{ - if (!ok) { - qCWarning(KGAPIDebug) << "Failed to load" << webview->url(); - } +void AuthWidgetPrivate::socketReady() { + QByteArray data = connection->readLine(); + connection->deleteLater(); - const QUrl url = webview->url(); - urlEdit->setText(url.toDisplayString(QUrl::PrettyDecoded)); - urlEdit->setCursorPosition(0); - qCDebug(KGAPIDebug) << "URLFinished:" << url; + sslIndicator->setVisible(false); + urlEdit->setVisible(false); + webview->setVisible(false); + progressbar->setVisible(false); + label->setVisible(true); - if (!isGoogleHost(url)) { + QStringList line = QString::fromLatin1(data).split(QStringLiteral(" ")); + if (line.size() != 3 || line.at(0) != QStringLiteral("GET") || !line.at(2).startsWith(QStringLiteral("HTTP/1.1"))) { + qCDebug(KGAPIDebug) << QStringLiteral("Token response invalid"); + emitError(InvalidResponse, QStringLiteral("Token response invalid")); return; } - if (isTokenPage(url)) { - const auto token = url.queryItemValue(QStringLiteral("approvalCode")); - if (!token.isEmpty()) { - qCDebug(KGAPIDebug) << "Got token: " << token; - auto fetch = new KGAPI2::NewTokensFetchJob(token, apiKey, secretKey); - connect(fetch, &Job::finished, this, &AuthWidgetPrivate::tokensReceived); + //qCDebug(KGAPIRaw) << "Recieving data on socket: " << line; + QUrl url(line.at(1)); + QUrlQuery query(url.query()); + const QString code = query.queryItemValue(QStringLiteral("code")); + if (code.isEmpty()) { + QString error = query.queryItemValue(QStringLiteral("error")); + if (!error.isEmpty()) { + emitError(UnknownError, error); + qCDebug(KGAPIDebug) << error; } else { - qCWarning(KGAPIDebug) << "Failed to parse token from URL, peaking into HTML..."; - webview->page()->runJavaScript( - QStringLiteral("document.getElementById(\"code\").value;"), - [this](const QVariant &result) { - const auto token = result.toString(); - if (token.isEmpty()) { - qCWarning(KGAPIDebug) << "Peaked into HTML, but cound not find token :("; - webview->page()->toHtml([](const QString &html) { - qCDebug(KGAPIDebug) << "Parsing token page failed"; - qCDebug(KGAPIDebug) << html; - }); - emitError(AuthError, tr("Parsing token page failed.")); - return; - } - qCDebug(KGAPIDebug) << "Peaked into HTML and found token: " << token; - auto fetch = new KGAPI2::NewTokensFetchJob(token, apiKey, secretKey); - connect(fetch, &Job::finished, this, &AuthWidgetPrivate::tokensReceived); - }); + qCDebug(KGAPIDebug) << QStringLiteral("Could not extract token from HTTP answer"); + emitError(InvalidResponse, QStringLiteral("Could not extract token from HTTP answer")); } - } else { - //qCDebug(KGAPIDebug) << "Unhandled page:" << url.host() << ", " << url.path(); + return; } + + 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" diff --git a/src/core/ui/authwidget_p.h b/src/core/ui/authwidget_p.h index 29ece44..553b3ce 100644 --- a/src/core/ui/authwidget_p.h +++ b/src/core/ui/authwidget_p.h @@ -1,94 +1,99 @@ /* Copyright 2012 Dan Vratil 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 LIBKGAPI_AUTHWIDGET_P_H #define LIBKGAPI_AUTHWIDGET_P_H #include #include "ui/authwidget.h" #include "types.h" #include #include #include #include +#include class QVBoxLayout; class QLabel; namespace KGAPI2 { class Job; class Q_DECL_HIDDEN AuthWidgetPrivate: public QObject { Q_OBJECT public: explicit AuthWidgetPrivate(AuthWidget *parent); virtual ~AuthWidgetPrivate(); bool showProgressBar; QString username; QString password; AccountPtr account; AuthWidget::Progress progress; QString apiKey; QString secretKey; QToolButton *sslIndicator; QLineEdit *urlEdit; QProgressBar *progressbar; QVBoxLayout *vbox; QWebEngineView *webview; QLabel *label; + QTcpServer *server; + int serverPort; + QTcpSocket *connection; + private Q_SLOTS: void emitError(const KGAPI2::Error errCode, const QString &msg); void webviewUrlChanged(const QUrl &url); - void webviewFinished(bool ok); + void socketReady(); void tokensReceived(KGAPI2::Job *job); void accountInfoReceived(KGAPI2::Job *job); private: void setupUi(); void setProgress(AuthWidget::Progress progress); bool isGoogleHost(const QUrl &url) const { return url.host() == QLatin1String("accounts.google.com"); } bool isSigninPage(const QUrl &url) const { return url.path() == QLatin1String("/signin/oauth"); } bool isUsernameFrame(const QUrl &url) { return url.path() == QLatin1String("/signin/oauth/identifier"); } bool isPasswordFrame(const QUrl &url) { return url.path() == QLatin1String("/signin/v2/challenge/pwd"); } - bool isTokenPage(const QUrl &url) { return url.path() == QLatin1String("/o/oauth2/approval/v2/approvalnativeapp"); } void setSslIcon(const QString &icon); + AuthWidget *q; friend class AuthWidget; }; } // namespace KGAPI2 #endif // LIBKGAPI_AUTHWIDGET_P_H