Authenticate Last.fm with "oauth".

This commit is contained in:
John Maguire 2017-08-24 19:32:14 +01:00
parent a056a73165
commit a8cb9bbd2a
4 changed files with 61 additions and 66 deletions

View File

@ -39,7 +39,10 @@
#include "lastfmservice.h" #include "lastfmservice.h"
#include <QCryptographicHash>
#include <QDesktopServices>
#include <QMenu> #include <QMenu>
#include <QMessageBox>
#include <QSettings> #include <QSettings>
#ifdef HAVE_LIBLASTFM1 #ifdef HAVE_LIBLASTFM1
@ -49,14 +52,16 @@
#endif #endif
#include "lastfmcompat.h" #include "lastfmcompat.h"
#include "internet/core/internetmodel.h"
#include "internet/core/internetplaylistitem.h"
#include "core/application.h" #include "core/application.h"
#include "core/closure.h" #include "core/closure.h"
#include "core/logging.h" #include "core/logging.h"
#include "core/network.h"
#include "core/player.h" #include "core/player.h"
#include "core/song.h" #include "core/song.h"
#include "core/taskmanager.h" #include "core/taskmanager.h"
#include "internet/core/internetmodel.h"
#include "internet/core/internetplaylistitem.h"
#include "internet/core/localredirectserver.h"
#include "covers/coverproviders.h" #include "covers/coverproviders.h"
#include "covers/lastfmcoverprovider.h" #include "covers/lastfmcoverprovider.h"
#include "ui/iconloader.h" #include "ui/iconloader.h"
@ -82,7 +87,8 @@ LastFMService::LastFMService(Application* app, QObject* parent)
already_scrobbled_(false), already_scrobbled_(false),
scrobbling_enabled_(false), scrobbling_enabled_(false),
connection_problems_(false), connection_problems_(false),
app_(app) { app_(app),
network_(new NetworkAccessManager) {
#ifdef HAVE_LIBLASTFM1 #ifdef HAVE_LIBLASTFM1
lastfm::ws::setScheme(lastfm::ws::Https); lastfm::ws::setScheme(lastfm::ws::Https);
#endif #endif
@ -130,37 +136,50 @@ bool LastFMService::IsSubscriber() const {
return settings.value("Subscriber", false).toBool(); return settings.value("Subscriber", false).toBool();
} }
void LastFMService::GetToken() { namespace {
QMap<QString, QString> params; QByteArray SignApiRequest(QList<QPair<QString, QString>> params) {
params["method"] = "auth.getToken"; qSort(params);
QNetworkReply* reply = lastfm::ws::post(params); QString to_sign;
NewClosure(reply, SIGNAL(finished()), this, for (const auto& p : params) {
SLOT(GetTokenReplyFinished(QNetworkReply*)), reply); to_sign += p.first;
} to_sign += p.second;
void LastFMService::GetTokenReplyFinished(QNetworkReply* reply) {
reply->deleteLater();
// Parse the reply
lastfm::XmlQuery lfm(lastfm::compat::EmptyXmlQuery());
if (lastfm::compat::ParseQuery(reply->readAll(), &lfm)) {
QString token = lfm["token"].text();
emit TokenReceived(true, token);
} else {
emit TokenReceived(false, lfm["error"].text().trimmed());
} }
to_sign += LastFMService::kSecret;
return QCryptographicHash::hash(to_sign.toUtf8(), QCryptographicHash::Md5).toHex();
} }
} // namespace
void LastFMService::Authenticate(const QString& token) { void LastFMService::Authenticate() {
QMap<QString, QString> params; QUrl url("https://www.last.fm/api/auth/");
params["method"] = "auth.getSession"; url.addQueryItem("api_key", kApiKey);
params["token"] = token;
QNetworkReply* reply = lastfm::ws::post(params); LocalRedirectServer* server = new LocalRedirectServer(this);
NewClosure(reply, SIGNAL(finished()), this, server->Listen();
SLOT(AuthenticateReplyFinished(QNetworkReply*)), reply);
// If we need more detailed error reporting, handle error(NetworkError) signal url.addQueryItem("cb", server->url().toString());
NewClosure(server, SIGNAL(Finished()), [this, server]() {
server->deleteLater();
const QUrl& url = server->request_url();
QString token = url.queryItemValue("token");
QUrl session_url("https://ws.audioscrobbler.com/2.0/");
session_url.addQueryItem("api_key", kApiKey);
session_url.addQueryItem("method", "auth.getSession");
session_url.addQueryItem("token", token);
session_url.addQueryItem("api_sig", SignApiRequest(session_url.queryItems()));
QNetworkReply* reply = network_->get(QNetworkRequest(session_url));
NewClosure(reply, SIGNAL(finished()), this, SLOT(AuthenticateReplyFinished(QNetworkReply*)), reply);
});
if (!QDesktopServices::openUrl(url)) {
QMessageBox box(QMessageBox::NoIcon, tr("Last.fm Authentication"), tr("Please open this url in your browser: <a href=\"%1\">%1</a>").arg(url.toString()), QMessageBox::Ok);
box.setTextFormat(Qt::RichText);
qLog(Debug) << "Last.fm authentication URL: " << url.toString();
box.exec();
}
} }
void LastFMService::AuthenticateReplyFinished(QNetworkReply* reply) { void LastFMService::AuthenticateReplyFinished(QNetworkReply* reply) {
@ -181,14 +200,14 @@ void LastFMService::AuthenticateReplyFinished(QNetworkReply* reply) {
settings.setValue("Session", lastfm::ws::SessionKey); settings.setValue("Session", lastfm::ws::SessionKey);
settings.setValue("Subscriber", is_subscriber); settings.setValue("Subscriber", is_subscriber);
} else { } else {
emit AuthenticationComplete(false, lfm["error"].text().trimmed()); emit AuthenticationComplete(false);
return; return;
} }
// Invalidate the scrobbler - it will get recreated later // Invalidate the scrobbler - it will get recreated later
scrobbler_.reset(nullptr); scrobbler_.reset(nullptr);
emit AuthenticationComplete(true, QString()); emit AuthenticationComplete(true);
} }
void LastFMService::SignOut() { void LastFMService::SignOut() {

View File

@ -38,8 +38,8 @@ uint qHash(const lastfm::Track& track);
class Application; class Application;
class LastFMUrlHandler; class LastFMUrlHandler;
class NetworkAccessManager;
class QAction; class QAction;
class QNetworkAccessManager;
class Song; class Song;
class LastFMService : public Scrobbler { class LastFMService : public Scrobbler {
@ -69,8 +69,7 @@ class LastFMService : public Scrobbler {
bool PreferAlbumArtist() const { return prefer_albumartist_; } bool PreferAlbumArtist() const { return prefer_albumartist_; }
bool HasConnectionProblems() const { return connection_problems_; } bool HasConnectionProblems() const { return connection_problems_; }
void GetToken(); void Authenticate();
void Authenticate(const QString& token);
void SignOut(); void SignOut();
void UpdateSubscriberStatus(); void UpdateSubscriberStatus();
@ -83,8 +82,7 @@ class LastFMService : public Scrobbler {
void ToggleScrobbling(); void ToggleScrobbling();
signals: signals:
void TokenReceived(bool success, const QString& token); void AuthenticationComplete(bool success);
void AuthenticationComplete(bool success, const QString& error_message);
void ScrobblingEnabledChanged(bool value); void ScrobblingEnabledChanged(bool value);
void ButtonVisibilityChanged(bool value); void ButtonVisibilityChanged(bool value);
void ScrobbleButtonVisibilityChanged(bool value); void ScrobbleButtonVisibilityChanged(bool value);
@ -97,7 +95,6 @@ signals:
void SavedItemsChanged(); void SavedItemsChanged();
private slots: private slots:
void GetTokenReplyFinished(QNetworkReply* reply);
void AuthenticateReplyFinished(QNetworkReply* reply); void AuthenticateReplyFinished(QNetworkReply* reply);
void UpdateSubscriberStatusFinished(QNetworkReply* reply); void UpdateSubscriberStatusFinished(QNetworkReply* reply);
@ -129,6 +126,7 @@ signals:
bool connection_problems_; bool connection_problems_;
Application* app_; Application* app_;
std::unique_ptr<NetworkAccessManager> network_;
}; };
#endif // INTERNET_LASTFM_LASTFMSERVICE_H_ #endif // INTERNET_LASTFM_LASTFMSERVICE_H_

View File

@ -43,10 +43,8 @@ LastFMSettingsPage::LastFMSettingsPage(SettingsDialog* dialog)
// Icons // Icons
setWindowIcon(IconLoader::Load("lastfm", IconLoader::Provider)); setWindowIcon(IconLoader::Load("lastfm", IconLoader::Provider));
connect(service_, SIGNAL(TokenReceived(bool,QString)), connect(service_, SIGNAL(AuthenticationComplete(bool)),
SLOT(TokenReceived(bool,QString))); SLOT(AuthenticationComplete(bool)));
connect(service_, SIGNAL(AuthenticationComplete(bool, QString)),
SLOT(AuthenticationComplete(bool, QString)));
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(Logout())); connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(Logout()));
connect(ui_->login_state, SIGNAL(LoginClicked()), SLOT(Login())); connect(ui_->login_state, SIGNAL(LoginClicked()), SLOT(Login()));
connect(ui_->login, SIGNAL(clicked()), SLOT(Login())); connect(ui_->login, SIGNAL(clicked()), SLOT(Login()));
@ -62,26 +60,10 @@ void LastFMSettingsPage::Login() {
waiting_for_auth_ = true; waiting_for_auth_ = true;
ui_->login_state->SetLoggedIn(LoginStateWidget::LoginInProgress); ui_->login_state->SetLoggedIn(LoginStateWidget::LoginInProgress);
service_->GetToken(); service_->Authenticate();
} }
void LastFMSettingsPage::TokenReceived(bool success, const QString &token) { void LastFMSettingsPage::AuthenticationComplete(bool success) {
if (!success) {
QMessageBox::warning(this, tr("Last.fm authentication failed"), token);
return;
}
QString url = QString(LastFMService::kAuthLoginUrl).arg(LastFMService::kApiKey, token);
QDesktopServices::openUrl(QUrl(url));
QMessageBox::information(this, tr("Last.fm authentication"),
tr("Click Ok once you authenticated Clementine in your last.fm account."));
service_->Authenticate(token);
}
void LastFMSettingsPage::AuthenticationComplete(bool success,
const QString& message) {
if (!waiting_for_auth_) return; // Wasn't us that was waiting for auth if (!waiting_for_auth_) return; // Wasn't us that was waiting for auth
waiting_for_auth_ = false; waiting_for_auth_ = false;
@ -90,10 +72,7 @@ void LastFMSettingsPage::AuthenticationComplete(bool success,
// Save settings // Save settings
Save(); Save();
} else { } else {
QString dialog_text = tr("Your Last.fm credentials were incorrect"); QString dialog_text = tr("Failed to login to last.fm. Please try again.");
if (!message.isEmpty()) {
dialog_text = message;
}
QMessageBox::warning(this, tr("Authentication failed"), dialog_text); QMessageBox::warning(this, tr("Authentication failed"), dialog_text);
} }

View File

@ -38,8 +38,7 @@ class LastFMSettingsPage : public SettingsPage {
private slots: private slots:
void Login(); void Login();
void TokenReceived(bool success, const QString& token); void AuthenticationComplete(bool success);
void AuthenticationComplete(bool success, const QString& error_message);
void Logout(); void Logout();
private: private: