diff options
author | Christian Stenger <christian.stenger@qt.io> | 2017-04-07 14:29:01 +0200 |
---|---|---|
committer | hjk <hjk@qt.io> | 2017-04-12 14:17:20 +0000 |
commit | a01507e90e0a051c9b916abafe9fc01a0da00128 (patch) | |
tree | 543d55fad48b02142dab2ded62267a4f72aeb342 /src/plugins/cpaster | |
parent | d4ca232d54a9a5dabc6955139f3d3c2dcb0875f0 (diff) | |
download | qt-creator-a01507e90e0a051c9b916abafe9fc01a0da00128.tar.gz |
CPaster: Fix pasting to KDE paster
Add minimal handling for credentials.
Task-number: QTCREATORBUG-17942
Change-Id: I5f3821fa944fa7800bd1f51947f46d301e2b00b8
Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
Reviewed-by: hjk <hjk@qt.io>
Diffstat (limited to 'src/plugins/cpaster')
-rw-r--r-- | src/plugins/cpaster/authenticationdialog.cpp | 64 | ||||
-rw-r--r-- | src/plugins/cpaster/authenticationdialog.h | 51 | ||||
-rw-r--r-- | src/plugins/cpaster/cpaster.pro | 8 | ||||
-rw-r--r-- | src/plugins/cpaster/cpaster.qbs | 2 | ||||
-rw-r--r-- | src/plugins/cpaster/kdepasteprotocol.cpp | 126 | ||||
-rw-r--r-- | src/plugins/cpaster/kdepasteprotocol.h | 27 | ||||
-rw-r--r-- | src/plugins/cpaster/protocol.cpp | 19 | ||||
-rw-r--r-- | src/plugins/cpaster/protocol.h | 5 |
8 files changed, 290 insertions, 12 deletions
diff --git a/src/plugins/cpaster/authenticationdialog.cpp b/src/plugins/cpaster/authenticationdialog.cpp new file mode 100644 index 0000000000..326cdb2d80 --- /dev/null +++ b/src/plugins/cpaster/authenticationdialog.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "authenticationdialog.h" + +#include <QDialogButtonBox> +#include <QFormLayout> +#include <QLabel> +#include <QLineEdit> +#include <QVBoxLayout> + +namespace CodePaster { + +AuthenticationDialog::AuthenticationDialog(const QString &details, QWidget *parent) + : QDialog(parent) +{ + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + auto *mainLayout = new QVBoxLayout; + mainLayout->addWidget(new QLabel(details)); + auto *formLayout = new QFormLayout; + formLayout->addRow(tr("Username:"), m_user = new QLineEdit); + formLayout->addRow(tr("Password:"), m_pass = new QLineEdit); + m_pass->setEchoMode(QLineEdit::Password); + mainLayout->addLayout(formLayout); + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + mainLayout->addWidget(buttonBox); + setLayout(mainLayout); +} + +QString AuthenticationDialog::userName() const +{ + return m_user->text(); +} + +QString AuthenticationDialog::password() const +{ + return m_pass->text(); +} + +} // namespace CodePaster diff --git a/src/plugins/cpaster/authenticationdialog.h b/src/plugins/cpaster/authenticationdialog.h new file mode 100644 index 0000000000..f52dbaef24 --- /dev/null +++ b/src/plugins/cpaster/authenticationdialog.h @@ -0,0 +1,51 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QDialog> + +QT_BEGIN_NAMESPACE +class QLineEdit; +QT_END_NAMESPACE + +namespace CodePaster { + +class AuthenticationDialog : public QDialog +{ +public: + AuthenticationDialog(const QString &details, QWidget *parent = nullptr); + + bool authenticated() const { return m_authenticated; } + QString userName() const; + QString password() const; + +private: + bool m_authenticated = false; + QLineEdit *m_user = nullptr; + QLineEdit *m_pass = nullptr; +}; + +} // namespace CodePaster diff --git a/src/plugins/cpaster/cpaster.pro b/src/plugins/cpaster/cpaster.pro index cc30094162..20c85c596b 100644 --- a/src/plugins/cpaster/cpaster.pro +++ b/src/plugins/cpaster/cpaster.pro @@ -14,7 +14,8 @@ HEADERS += cpasterplugin.h \ fileshareprotocolsettingspage.h \ kdepasteprotocol.h \ urlopenprotocol.h \ - codepasterservice.h + codepasterservice.h \ + authenticationdialog.h SOURCES += cpasterplugin.cpp \ settingspage.cpp \ @@ -28,7 +29,8 @@ SOURCES += cpasterplugin.cpp \ fileshareprotocol.cpp \ fileshareprotocolsettingspage.cpp \ kdepasteprotocol.cpp \ - urlopenprotocol.cpp + urlopenprotocol.cpp \ + authenticationdialog.cpp FORMS += settingspage.ui \ pasteselect.ui \ @@ -39,3 +41,5 @@ include(../../shared/cpaster/cpaster.pri) RESOURCES += \ cpaster.qrc + +DEFINES *= CPASTER_PLUGIN_GUI diff --git a/src/plugins/cpaster/cpaster.qbs b/src/plugins/cpaster/cpaster.qbs index 4b3cb99a84..f9144b53d8 100644 --- a/src/plugins/cpaster/cpaster.qbs +++ b/src/plugins/cpaster/cpaster.qbs @@ -45,6 +45,8 @@ QtcPlugin { "settingspage.ui", "urlopenprotocol.cpp", "urlopenprotocol.h", + "authenticationdialog.cpp", + "authenticationdialog.h" ] Group { diff --git a/src/plugins/cpaster/kdepasteprotocol.cpp b/src/plugins/cpaster/kdepasteprotocol.cpp index d182005c1d..c5c09cb9bf 100644 --- a/src/plugins/cpaster/kdepasteprotocol.cpp +++ b/src/plugins/cpaster/kdepasteprotocol.cpp @@ -24,7 +24,11 @@ ****************************************************************************/ #include "kdepasteprotocol.h" +#ifdef CPASTER_PLUGIN_GUI +#include "authenticationdialog.h" +#endif +#include <coreplugin/icore.h> #include <utils/qtcassert.h> #include <QDebug> @@ -33,7 +37,7 @@ #include <QJsonDocument> #include <QJsonObject> #include <QJsonArray> - +#include <QRegularExpression> #include <QNetworkReply> #include <algorithm> @@ -118,7 +122,7 @@ void StickyNotesPasteProtocol::paste(const QString &text, pasteData += QUrl::toPercentEncoding(description.left(maxDescriptionLength)); } - m_pasteReply = httpPost(m_hostUrl + QLatin1String("api/json/create"), pasteData); + m_pasteReply = httpPost(m_hostUrl + QLatin1String("api/json/create"), pasteData, true); connect(m_pasteReply, &QNetworkReply::finished, this, &StickyNotesPasteProtocol::pasteFinished); if (debug) qDebug() << "paste: sending " << m_pasteReply << pasteData; @@ -262,9 +266,127 @@ void StickyNotesPasteProtocol::listFinished() m_listReply = nullptr; } +KdePasteProtocol::KdePasteProtocol() +{ + setHostUrl(QLatin1String("https://pastebin.kde.org/")); + connect(this, &KdePasteProtocol::authenticationFailed, this, [this] () { + m_loginFailed = true; + paste(m_text, m_contentType, m_expiryDays, QString(), QString(), m_description); + }); +} + +void KdePasteProtocol::paste(const QString &text, Protocol::ContentType ct, int expiryDays, + const QString &username, const QString &comment, + const QString &description) +{ + Q_UNUSED(username); + Q_UNUSED(comment); + // KDE paster needs authentication nowadays +#ifdef CPASTER_PLUGIN_GUI + QString details = tr("Pasting to KDE paster needs authentication.<br/>" + "Enter your KDE Identity credentials to continue."); + if (m_loginFailed) + details.prepend(tr("<span style='background-color:LightYellow;color:red'>Login failed</span><br/><br/>")); + + AuthenticationDialog authDialog(details, Core::ICore::dialogParent()); + authDialog.setWindowTitle("Authenticate for KDE paster"); + if (authDialog.exec() != QDialog::Accepted) { + m_loginFailed = false; + return; + } + const QString user = authDialog.userName(); + const QString passwd = authDialog.password(); +#else + // FIXME get the credentials for the cmdline cpaster somehow + const QString user; + const QString passwd; + qDebug() << "KDE needs credentials for pasting"; + return; +#endif + // store input data as members to be able to use them after the authentication succeeded + m_text = text; + m_contentType = ct; + m_expiryDays = expiryDays; + m_description = description; + authenticate(user, passwd); +} + QString KdePasteProtocol::protocolName() { return QLatin1String("Paste.KDE.Org"); } +void KdePasteProtocol::authenticate(const QString &user, const QString &passwd) +{ + QTC_ASSERT(!m_authReply, return); + + // first we need to obtain the hidden form token for logging in + m_authReply = httpGet(hostUrl() + "user/login"); + connect(m_authReply, &QNetworkReply::finished, this, [this, user, passwd] () { + onPreAuthFinished(user, passwd); + }); +} + +void KdePasteProtocol::onPreAuthFinished(const QString &user, const QString &passwd) +{ + if (m_authReply->error() != QNetworkReply::NoError) { + m_authReply->deleteLater(); + m_authReply = nullptr; + return; + } + const QByteArray page = m_authReply->readAll(); + m_authReply->deleteLater(); + const QRegularExpression regex("name=\"_token\"\\s+type=\"hidden\"\\s+value=\"(.*?)\">"); + const QRegularExpressionMatch match = regex.match(QLatin1String(page)); + if (!match.hasMatch()) { + m_authReply = nullptr; + return; + } + const QString token = match.captured(1); + + QByteArray data("username=" + QUrl::toPercentEncoding(user) + + "&password=" + QUrl::toPercentEncoding(passwd) + + "&_token=" + QUrl::toPercentEncoding(token)); + m_authReply = httpPost(hostUrl() + "user/login", data, true); + connect(m_authReply, &QNetworkReply::finished, this, &KdePasteProtocol::onAuthFinished); +} + +void KdePasteProtocol::onAuthFinished() +{ + if (m_authReply->error() != QNetworkReply::NoError) { + m_authReply->deleteLater(); + m_authReply = nullptr; + return; + } + const QVariant attribute = m_authReply->attribute(QNetworkRequest::RedirectionTargetAttribute); + m_redirectUrl = redirectUrl(attribute.toUrl().toString(), m_redirectUrl); + if (!m_redirectUrl.isEmpty()) { // we need to perform a redirect + QUrl url(m_redirectUrl); + if (url.path().isEmpty()) + url.setPath("/"); // avoid issue inside cookiesForUrl() + m_authReply->deleteLater(); + m_authReply = httpGet(url.url(), true); + connect(m_authReply, &QNetworkReply::finished, this, &KdePasteProtocol::onAuthFinished); + } else { // auth should be done now + const QByteArray page = m_authReply->readAll(); + m_authReply->deleteLater(); + m_authReply = nullptr; + if (page.contains("https://identity.kde.org")) // we're back on the login page + emit authenticationFailed(); + else { + m_loginFailed = false; + StickyNotesPasteProtocol::paste(m_text, m_contentType, m_expiryDays, QString(), + QString(), m_description); + } + } +} + +QString KdePasteProtocol::redirectUrl(const QString &redirect, const QString &oldRedirect) const +{ + QString redirectUrl; + if (!redirect.isEmpty() && redirect != oldRedirect) + redirectUrl = redirect; + return redirectUrl; +} + } // namespace CodePaster diff --git a/src/plugins/cpaster/kdepasteprotocol.h b/src/plugins/cpaster/kdepasteprotocol.h index 7cd3d0d83b..e7bbd1ba1d 100644 --- a/src/plugins/cpaster/kdepasteprotocol.h +++ b/src/plugins/cpaster/kdepasteprotocol.h @@ -70,14 +70,33 @@ private: class KdePasteProtocol : public StickyNotesPasteProtocol { + Q_OBJECT public: - KdePasteProtocol() - { - setHostUrl(QLatin1String("https://pastebin.kde.org/")); - } + KdePasteProtocol(); + + void paste(const QString &text, ContentType ct = Text, int expiryDays = 1, + const QString &username = QString(), + const QString &comment = QString() , + const QString &description = QString()) override; QString name() const override { return protocolName(); } static QString protocolName(); +signals: + void authenticationFailed(); +private: + void authenticate(const QString &user, const QString &passwd); + void onPreAuthFinished(const QString &user, const QString &passwd); + void onAuthFinished(); + QString redirectUrl(const QString &redirect, const QString &oldRedirect) const; + + QNetworkReply *m_authReply = nullptr; + QString m_text; + ContentType m_contentType = Text; + int m_expiryDays = 1; + bool m_loginFailed = false; + QString m_description; + QString m_redirectUrl; + }; } // namespace CodePaster diff --git a/src/plugins/cpaster/protocol.cpp b/src/plugins/cpaster/protocol.cpp index f79bed2a46..8b745006d2 100644 --- a/src/plugins/cpaster/protocol.cpp +++ b/src/plugins/cpaster/protocol.cpp @@ -35,6 +35,8 @@ #include <coreplugin/icore.h> #include <coreplugin/dialogs/ioptionspage.h> +#include <QNetworkCookie> +#include <QNetworkCookieJar> #include <QNetworkRequest> #include <QNetworkReply> @@ -173,17 +175,30 @@ bool Protocol::showConfigurationError(const Protocol *p, // --------- NetworkProtocol -QNetworkReply *NetworkProtocol::httpGet(const QString &link) +static void addCookies(QNetworkRequest &request) +{ + auto accessMgr = Utils::NetworkAccessManager::instance(); + const QList<QNetworkCookie> cookies = accessMgr->cookieJar()->cookiesForUrl(request.url()); + for (const QNetworkCookie &cookie : cookies) + request.setHeader(QNetworkRequest::CookieHeader, QVariant::fromValue(cookie)); +} + +QNetworkReply *NetworkProtocol::httpGet(const QString &link, bool handleCookies) { QUrl url(link); QNetworkRequest r(url); + if (handleCookies) + addCookies(r); return Utils::NetworkAccessManager::instance()->get(r); } -QNetworkReply *NetworkProtocol::httpPost(const QString &link, const QByteArray &data) +QNetworkReply *NetworkProtocol::httpPost(const QString &link, const QByteArray &data, + bool handleCookies) { QUrl url(link); QNetworkRequest r(url); + if (handleCookies) + addCookies(r); r.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(QByteArray("application/x-www-form-urlencoded"))); return Utils::NetworkAccessManager::instance()->post(r, data); diff --git a/src/plugins/cpaster/protocol.h b/src/plugins/cpaster/protocol.h index 76891ae279..4dc2acb6b2 100644 --- a/src/plugins/cpaster/protocol.h +++ b/src/plugins/cpaster/protocol.h @@ -110,9 +110,10 @@ public: ~NetworkProtocol() override; protected: - QNetworkReply *httpGet(const QString &url); + QNetworkReply *httpGet(const QString &url, bool handleCookies = false); - QNetworkReply *httpPost(const QString &link, const QByteArray &data); + QNetworkReply *httpPost(const QString &link, const QByteArray &data, + bool handleCookies = false); // Check connectivity of host, displaying a message box. bool httpStatus(QString url, QString *errorMessage, bool useHttps = false); |