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 <QCryptographicHash>
#include <QDesktopServices>
#include <QMenu>
#include <QMessageBox>
#include <QSettings>
#ifdef HAVE_LIBLASTFM1
@ -49,14 +52,16 @@
#endif
#include "lastfmcompat.h"
#include "internet/core/internetmodel.h"
#include "internet/core/internetplaylistitem.h"
#include "core/application.h"
#include "core/closure.h"
#include "core/logging.h"
#include "core/network.h"
#include "core/player.h"
#include "core/song.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/lastfmcoverprovider.h"
#include "ui/iconloader.h"
@ -82,7 +87,8 @@ LastFMService::LastFMService(Application* app, QObject* parent)
already_scrobbled_(false),
scrobbling_enabled_(false),
connection_problems_(false),
app_(app) {
app_(app),
network_(new NetworkAccessManager) {
#ifdef HAVE_LIBLASTFM1
lastfm::ws::setScheme(lastfm::ws::Https);
#endif
@ -130,37 +136,50 @@ bool LastFMService::IsSubscriber() const {
return settings.value("Subscriber", false).toBool();
}
void LastFMService::GetToken() {
QMap<QString, QString> params;
params["method"] = "auth.getToken";
QNetworkReply* reply = lastfm::ws::post(params);
NewClosure(reply, SIGNAL(finished()), this,
SLOT(GetTokenReplyFinished(QNetworkReply*)), reply);
}
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());
namespace {
QByteArray SignApiRequest(QList<QPair<QString, QString>> params) {
qSort(params);
QString to_sign;
for (const auto& p : params) {
to_sign += p.first;
to_sign += p.second;
}
to_sign += LastFMService::kSecret;
return QCryptographicHash::hash(to_sign.toUtf8(), QCryptographicHash::Md5).toHex();
}
} // namespace
void LastFMService::Authenticate(const QString& token) {
QMap<QString, QString> params;
params["method"] = "auth.getSession";
params["token"] = token;
void LastFMService::Authenticate() {
QUrl url("https://www.last.fm/api/auth/");
url.addQueryItem("api_key", kApiKey);
QNetworkReply* reply = lastfm::ws::post(params);
NewClosure(reply, SIGNAL(finished()), this,
SLOT(AuthenticateReplyFinished(QNetworkReply*)), reply);
// If we need more detailed error reporting, handle error(NetworkError) signal
LocalRedirectServer* server = new LocalRedirectServer(this);
server->Listen();
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) {
@ -181,14 +200,14 @@ void LastFMService::AuthenticateReplyFinished(QNetworkReply* reply) {
settings.setValue("Session", lastfm::ws::SessionKey);
settings.setValue("Subscriber", is_subscriber);
} else {
emit AuthenticationComplete(false, lfm["error"].text().trimmed());
emit AuthenticationComplete(false);
return;
}
// Invalidate the scrobbler - it will get recreated later
scrobbler_.reset(nullptr);
emit AuthenticationComplete(true, QString());
emit AuthenticationComplete(true);
}
void LastFMService::SignOut() {

View File

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

View File

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

View File

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