Port dropbox authentication to oauth2

This commit is contained in:
John Maguire 2015-09-25 16:18:58 +01:00
parent a2f471d75d
commit 415e6dc5e8
8 changed files with 39 additions and 293 deletions

View File

@ -1088,12 +1088,10 @@ optional_source(HAVE_GOOGLE_DRIVE
# Dropbox support
optional_source(HAVE_DROPBOX
SOURCES
internet/dropbox/dropboxauthenticator.cpp
internet/dropbox/dropboxservice.cpp
internet/dropbox/dropboxsettingspage.cpp
internet/dropbox/dropboxurlhandler.cpp
HEADERS
internet/dropbox/dropboxauthenticator.h
internet/dropbox/dropboxservice.h
internet/dropbox/dropboxsettingspage.h
internet/dropbox/dropboxurlhandler.h

View File

@ -67,7 +67,9 @@ void OAuthenticator::StartAuthorisation(const QString& oauth_endpoint,
}
url.addQueryItem("redirect_uri", redirect_url.toString());
url.addQueryItem("scope", scope);
if (!scope.isEmpty()) { // Empty scope is valid for Dropbox.
url.addQueryItem("scope", scope);
}
NewClosure(server, SIGNAL(Finished()), this, &OAuthenticator::RedirectArrived,
server, redirect_url);
@ -115,7 +117,8 @@ void OAuthenticator::RequestAccessToken(const QByteArray& code,
"application/x-www-form-urlencoded");
QNetworkReply* reply = network_.post(request, post_data.toUtf8());
connect(reply, SIGNAL(sslErrors(QList<QSslError>)), SLOT(SslErrors(QList<QSslError>)));
connect(reply, SIGNAL(sslErrors(QList<QSslError>)),
SLOT(SslErrors(QList<QSslError>)));
NewClosure(reply, SIGNAL(finished()), this,
SLOT(FetchAccessTokenFinished(QNetworkReply*)), reply);
}

View File

@ -1,181 +0,0 @@
/* This file is part of Clementine.
Copyright 2012, 2014, John Maguire <john.maguire@gmail.com>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "dropboxauthenticator.h"
#include <time.h>
#include <qjson/parser.h>
#include <QDesktopServices>
#include <QStringList>
#include <QTcpSocket>
#include "core/closure.h"
#include "core/logging.h"
#include "core/network.h"
#include "internet/core/localredirectserver.h"
namespace {
static const char* kAppKey = "qh6ca27eclt9p2k";
static const char* kAppSecret = "pg7y68h5efap8r6";
// OAuth 1.0 endpoints
static const char* kRequestTokenEndpoint =
"https://api.dropbox.com/1/oauth/request_token";
static const char* kAuthoriseEndpoint =
"https://www.dropbox.com/1/oauth/authorize";
static const char* kAccessTokenEndpoint =
"https://api.dropbox.com/1/oauth/access_token";
// Dropbox API endpoints
static const char* kAccountInfoEndpoint =
"https://api.dropbox.com/1/account/info";
} // namespace
DropboxAuthenticator::DropboxAuthenticator(QObject* parent)
: QObject(parent), network_(new NetworkAccessManager(this)) {}
void DropboxAuthenticator::StartAuthorisation() {
QUrl url(kRequestTokenEndpoint);
QByteArray authorisation_header =
GenerateAuthorisationHeader(QString::null, QString::null);
QNetworkRequest request(url);
request.setRawHeader("Authorization", authorisation_header);
QNetworkReply* reply = network_->post(request, QByteArray());
NewClosure(reply, SIGNAL(finished()), this,
SLOT(RequestTokenFinished(QNetworkReply*)), reply);
}
namespace {
// Parse a string like a=b&c=d into a map.
QMap<QString, QString> ParseParamList(const QString& params) {
QMap<QString, QString> ret;
QStringList components = params.split("&");
for (const QString& component : components) {
QStringList pairs = component.split("=");
if (pairs.size() != 2) {
continue;
}
ret[pairs[0]] = pairs[1];
}
return ret;
}
} // namespace
void DropboxAuthenticator::RequestTokenFinished(QNetworkReply* reply) {
reply->deleteLater();
QString result = reply->readAll();
QMap<QString, QString> params = ParseParamList(result);
token_ = params["oauth_token"];
secret_ = params["oauth_token_secret"];
Authorise();
}
void DropboxAuthenticator::Authorise() {
LocalRedirectServer* server = new LocalRedirectServer(this);
server->Listen();
NewClosure(server, SIGNAL(Finished()), this,
SLOT(RedirectArrived(LocalRedirectServer*)), server);
QUrl url(kAuthoriseEndpoint);
url.addQueryItem("oauth_token", token_);
url.addQueryItem("oauth_callback", server->url().toString());
QDesktopServices::openUrl(url);
}
void DropboxAuthenticator::RedirectArrived(LocalRedirectServer* server) {
server->deleteLater();
QUrl request_url = server->request_url();
qLog(Debug) << Q_FUNC_INFO << request_url;
uid_ = request_url.queryItemValue("uid");
RequestAccessToken();
}
void DropboxAuthenticator::RequestAccessToken() {
QUrl url(kAccessTokenEndpoint);
QNetworkRequest request(url);
QByteArray authorisation_header =
GenerateAuthorisationHeader(token_, secret_);
request.setRawHeader("Authorization", authorisation_header);
QNetworkReply* reply = network_->post(request, QByteArray());
NewClosure(reply, SIGNAL(finished()), this,
SLOT(RequestAccessTokenFinished(QNetworkReply*)), reply);
}
void DropboxAuthenticator::RequestAccessTokenFinished(QNetworkReply* reply) {
reply->deleteLater();
QString result = QString::fromAscii(reply->readAll());
qLog(Debug) << result;
QMap<QString, QString> params = ParseParamList(result);
access_token_ = params["oauth_token"];
access_token_secret_ = params["oauth_token_secret"];
qLog(Debug) << Q_FUNC_INFO << access_token_ << access_token_secret_;
RequestAccountInformation();
}
QByteArray DropboxAuthenticator::GenerateAuthorisationHeader() {
return GenerateAuthorisationHeader(access_token_, access_token_secret_);
}
QByteArray DropboxAuthenticator::GenerateAuthorisationHeader(
const QString& token, const QString& token_secret) {
typedef QPair<QString, QString> Param;
QByteArray signature =
QUrl::toPercentEncoding(QString("%1&%2").arg(kAppSecret, token_secret));
QList<Param> params;
params << Param("oauth_consumer_key", kAppKey)
<< Param("oauth_signature_method", "PLAINTEXT")
<< Param("oauth_timestamp", QString::number(time(nullptr)))
<< Param("oauth_nonce", QString::number(qrand()))
<< Param("oauth_signature", signature);
if (!token.isNull()) {
params << Param("oauth_token", token);
}
QStringList encoded_params;
for (const Param& p : params) {
encoded_params << QString("%1=\"%2\"").arg(p.first, p.second);
}
QString authorisation_header = QString("OAuth ") + encoded_params.join(", ");
return authorisation_header.toUtf8();
}
void DropboxAuthenticator::RequestAccountInformation() {
QUrl url(kAccountInfoEndpoint);
QNetworkRequest request(url);
request.setRawHeader("Authorization", GenerateAuthorisationHeader());
qLog(Debug) << Q_FUNC_INFO << url << request.rawHeader("Authorization");
QNetworkReply* reply = network_->get(request);
NewClosure(reply, SIGNAL(finished()), this,
SLOT(RequestAccountInformationFinished(QNetworkReply*)), reply);
}
void DropboxAuthenticator::RequestAccountInformationFinished(
QNetworkReply* reply) {
reply->deleteLater();
QJson::Parser parser;
QVariantMap response = parser.parse(reply).toMap();
name_ = response["display_name"].toString();
emit Finished();
}

View File

@ -1,76 +0,0 @@
/* This file is part of Clementine.
Copyright 2012, 2014, John Maguire <john.maguire@gmail.com>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INTERNET_DROPBOX_DROPBOXAUTHENTICATOR_H_
#define INTERNET_DROPBOX_DROPBOXAUTHENTICATOR_H_
#include <QObject>
#include <QTcpServer>
class LocalRedirectServer;
class NetworkAccessManager;
class QNetworkReply;
class DropboxAuthenticator : public QObject {
Q_OBJECT
public:
explicit DropboxAuthenticator(QObject* parent = nullptr);
void StartAuthorisation();
const QString& access_token() const { return access_token_; }
const QString& access_token_secret() const { return access_token_secret_; }
const QString& uid() const { return uid_; }
const QString& name() const { return name_; }
static QByteArray GenerateAuthorisationHeader(const QString& token,
const QString& secret);
signals:
void Finished();
private slots:
void RequestTokenFinished(QNetworkReply* reply);
void RedirectArrived(LocalRedirectServer* server);
void RequestAccessTokenFinished(QNetworkReply* reply);
void RequestAccountInformationFinished(QNetworkReply* reply);
private:
void Authorise();
void RequestAccessToken();
QByteArray GenerateAuthorisationHeader();
void RequestAccountInformation();
private:
NetworkAccessManager* network_;
QTcpServer server_;
// Temporary access token used for first authentication flow.
QString token_;
QString secret_;
// Permanent OAuth access tokens.
QString access_token_;
QString access_token_secret_;
// User's Dropbox uid & name.
QString uid_;
QString name_;
};
#endif // INTERNET_DROPBOX_DROPBOXAUTHENTICATOR_H_

View File

@ -30,7 +30,7 @@
#include "core/player.h"
#include "core/utilities.h"
#include "core/waitforsignal.h"
#include "internet/dropbox/dropboxauthenticator.h"
#include "internet/core/oauthenticator.h"
#include "internet/dropbox/dropboxurlhandler.h"
#include "library/librarybackend.h"
@ -57,8 +57,8 @@ DropboxService::DropboxService(Application* app, InternetModel* parent)
network_(new NetworkAccessManager(this)) {
QSettings settings;
settings.beginGroup(kSettingsGroup);
access_token_ = settings.value("access_token").toString();
access_token_secret_ = settings.value("access_token_secret").toString();
// OAuth2 version of dropbox auth token.
access_token_ = settings.value("access_token2").toString();
app->player()->RegisterUrlHandler(new DropboxUrlHandler(this, this));
}
@ -74,19 +74,14 @@ void DropboxService::Connect() {
}
}
void DropboxService::AuthenticationFinished(
DropboxAuthenticator* authenticator) {
void DropboxService::AuthenticationFinished(OAuthenticator* authenticator) {
authenticator->deleteLater();
access_token_ = authenticator->access_token();
access_token_secret_ = authenticator->access_token_secret();
QSettings settings;
settings.beginGroup(kSettingsGroup);
settings.setValue("access_token", access_token_);
settings.setValue("access_token_secret", access_token_secret_);
settings.setValue("name", authenticator->name());
settings.setValue("access_token2", access_token_);
emit Connected();
@ -94,8 +89,7 @@ void DropboxService::AuthenticationFinished(
}
QByteArray DropboxService::GenerateAuthorisationHeader() {
return DropboxAuthenticator::GenerateAuthorisationHeader(
access_token_, access_token_secret_);
return QString("Bearer %1").arg(access_token_).toUtf8();
}
void DropboxService::RequestFileList() {

View File

@ -23,8 +23,8 @@
#include "core/tagreaderclient.h"
class DropboxAuthenticator;
class NetworkAccessManager;
class OAuthenticator;
class QNetworkReply;
class DropboxService : public CloudFileService {
@ -40,12 +40,12 @@ class DropboxService : public CloudFileService {
QUrl GetStreamingUrlFromSongId(const QUrl& url);
signals:
signals:
void Connected();
public slots:
void Connect();
void AuthenticationFinished(DropboxAuthenticator* authenticator);
void AuthenticationFinished(OAuthenticator* authenticator);
private slots:
void RequestFileListFinished(QNetworkReply* reply);
@ -60,7 +60,6 @@ class DropboxService : public CloudFileService {
private:
QString access_token_;
QString access_token_secret_;
NetworkAccessManager* network_;
};

View File

@ -20,11 +20,21 @@
#include "ui_dropboxsettingspage.h"
#include "core/application.h"
#include "internet/dropbox/dropboxauthenticator.h"
#include "internet/core/oauthenticator.h"
#include "internet/dropbox/dropboxservice.h"
#include "internet/core/internetmodel.h"
#include "ui/settingsdialog.h"
namespace {
static const char* kOAuthEndpoint =
"https://www.dropbox.com/1/oauth2/authorize";
static const char* kOAuthClientId = "qh6ca27eclt9p2k";
static const char* kOAuthClientSecret = "pg7y68h5efap8r6";
static const char* kOAuthTokenEndpoint =
"https://api.dropboxapi.com/1/oauth2/token";
static const char* kOAuthScope = "";
}
DropboxSettingsPage::DropboxSettingsPage(SettingsDialog* parent)
: SettingsPage(parent),
ui_(new Ui::DropboxSettingsPage),
@ -44,10 +54,10 @@ void DropboxSettingsPage::Load() {
QSettings s;
s.beginGroup(DropboxService::kSettingsGroup);
const QString name = s.value("name").toString();
const QString access_token = s.value("access_token2").toString();
if (!name.isEmpty()) {
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn, name);
if (!access_token.isEmpty()) {
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn);
}
}
@ -57,13 +67,14 @@ void DropboxSettingsPage::Save() {
}
void DropboxSettingsPage::LoginClicked() {
DropboxAuthenticator* authenticator = new DropboxAuthenticator;
NewClosure(authenticator, SIGNAL(Finished()), this,
SLOT(Connected(DropboxAuthenticator*)), authenticator);
OAuthenticator* authenticator =
new OAuthenticator(kOAuthClientId, kOAuthClientSecret,
OAuthenticator::RedirectStyle::REMOTE_WITH_STATE);
connect(authenticator, SIGNAL(Finished()), SLOT(Connected()));
NewClosure(authenticator, SIGNAL(Finished()), service_,
SLOT(AuthenticationFinished(DropboxAuthenticator*)),
authenticator);
authenticator->StartAuthorisation();
SLOT(AuthenticationFinished(OAuthenticator*)), authenticator);
authenticator->StartAuthorisation(kOAuthEndpoint, kOAuthTokenEndpoint,
kOAuthScope);
ui_->login_button->setEnabled(false);
}
@ -81,7 +92,6 @@ void DropboxSettingsPage::LogoutClicked() {
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedOut);
}
void DropboxSettingsPage::Connected(DropboxAuthenticator* authenticator) {
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn,
authenticator->name());
void DropboxSettingsPage::Connected() {
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn);
}

View File

@ -24,7 +24,6 @@
#include <QModelIndex>
#include <QWidget>
class DropboxAuthenticator;
class DropboxService;
class Ui_DropboxSettingsPage;
@ -39,12 +38,12 @@ class DropboxSettingsPage : public SettingsPage {
void Save();
// QObject
bool eventFilter(QObject* object, QEvent* event);
bool eventFilter(QObject* object, QEvent* event) override;
private slots:
void LoginClicked();
void LogoutClicked();
void Connected(DropboxAuthenticator* authenticator);
void Connected();
private:
Ui_DropboxSettingsPage* ui_;