diff --git a/src/settings/subsonicsettingspage.cpp b/src/settings/subsonicsettingspage.cpp index bbabd1945..3549e86aa 100644 --- a/src/settings/subsonicsettingspage.cpp +++ b/src/settings/subsonicsettingspage.cpp @@ -80,6 +80,17 @@ void SubsonicSettingsPage::Load() { ui_->checkbox_verify_certificate->setChecked(s.value("verifycertificate", false).toBool()); ui_->checkbox_download_album_covers->setChecked(s.value("downloadalbumcovers", true).toBool()); ui_->checkbox_server_scrobbling->setChecked(s.value("serversidescrobbling", false).toBool()); + + AuthMethod auth_method = static_cast(s.value("auth_method", AuthMethod_MD5).toInt()); + switch(auth_method) { + case AuthMethod_Hex: + ui_->auth_method_hex->setChecked(true); + break; + case AuthMethod_MD5: + ui_->auth_method_md5->setChecked(true); + break; + } + s.endGroup(); Init(ui_->layout_subsonicsettingspage->parentWidget()); @@ -100,6 +111,12 @@ void SubsonicSettingsPage::Save() { s.setValue("verifycertificate", ui_->checkbox_verify_certificate->isChecked()); s.setValue("downloadalbumcovers", ui_->checkbox_download_album_covers->isChecked()); s.setValue("serversidescrobbling", ui_->checkbox_server_scrobbling->isChecked()); + if (ui_->auth_method_hex->isChecked()) { + s.setValue("authmethod", AuthMethod_Hex); + } + else { + s.setValue("authmethod", AuthMethod_MD5); + } s.endGroup(); } @@ -117,7 +134,7 @@ void SubsonicSettingsPage::TestClicked() { return; } - emit Test(server_url, ui_->username->text(), ui_->password->text()); + emit Test(server_url, ui_->username->text(), ui_->password->text(), ui_->auth_method_hex->isChecked() ? AuthMethod_Hex : AuthMethod_MD5); ui_->button_test->setEnabled(false); } diff --git a/src/settings/subsonicsettingspage.h b/src/settings/subsonicsettingspage.h index 383cf4c8e..33b8cfcb3 100644 --- a/src/settings/subsonicsettingspage.h +++ b/src/settings/subsonicsettingspage.h @@ -42,13 +42,18 @@ class SubsonicSettingsPage : public SettingsPage { static const char *kSettingsGroup; + enum AuthMethod { + AuthMethod_Hex, + AuthMethod_MD5 + }; + void Load() override; void Save() override; bool eventFilter(QObject *object, QEvent *event) override; signals: - void Test(QUrl url, QString username, QString password, bool redirect = false); + void Test(QUrl url, QString username, QString password, AuthMethod auth_method, bool redirect = false); private slots: void TestClicked(); diff --git a/src/settings/subsonicsettingspage.ui b/src/settings/subsonicsettingspage.ui index 98bf75505..aa07b3c65 100644 --- a/src/settings/subsonicsettingspage.ui +++ b/src/settings/subsonicsettingspage.ui @@ -7,7 +7,7 @@ 0 0 460 - 500 + 644 @@ -93,6 +93,57 @@ + + + + Authentication method: + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Hex (insecure) + + + + + + + MD5 token + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + diff --git a/src/subsonic/subsonicbaserequest.cpp b/src/subsonic/subsonicbaserequest.cpp index 758112878..031e38f4b 100644 --- a/src/subsonic/subsonicbaserequest.cpp +++ b/src/subsonic/subsonicbaserequest.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -39,9 +40,12 @@ #include #include +#include "core/utilities.h" #include "subsonicservice.h" #include "subsonicbaserequest.h" +#include "settings/subsonicsettingspage.h" + SubsonicBaseRequest::SubsonicBaseRequest(SubsonicService *service, QObject *parent) : QObject(parent), service_(service), @@ -59,8 +63,19 @@ QUrl SubsonicBaseRequest::CreateUrl(const QString &ressource_name, const QList

auth_method() == SubsonicSettingsPage::AuthMethod_Hex) { + params << Param("p", QString("enc:" + password().toUtf8().toHex())); + } + else { + const QString salt = Utilities::CryptographicRandomString(20); + QCryptographicHash md5(QCryptographicHash::Md5); + md5.addData(password().toUtf8()); + md5.addData(salt.toUtf8()); + params << Param("s", salt); + params << Param("t", md5.result().toHex()); + } QUrlQuery url_query; for (const Param ¶m : params) { diff --git a/src/subsonic/subsonicservice.cpp b/src/subsonic/subsonicservice.cpp index e6859ade1..09009f3d0 100644 --- a/src/subsonic/subsonicservice.cpp +++ b/src/subsonic/subsonicservice.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ #include #include +#include "core/utilities.h" #include "core/application.h" #include "core/player.h" #include "core/logging.h" @@ -75,6 +77,7 @@ SubsonicService::SubsonicService(Application *app, QObject *parent) http2_(true), verify_certificate_(false), download_album_covers_(true), + auth_method_(SubsonicSettingsPage::AuthMethod_MD5), ping_redirects_(0) { app->player()->RegisterUrlHandler(url_handler_); @@ -136,16 +139,17 @@ void SubsonicService::ReloadSettings() { http2_ = s.value("http2", true).toBool(); verify_certificate_ = s.value("verifycertificate", false).toBool(); download_album_covers_ = s.value("downloadalbumcovers", true).toBool(); + auth_method_ = static_cast(s.value("authmethod", SubsonicSettingsPage::AuthMethod_MD5).toInt()); s.endGroup(); } void SubsonicService::SendPing() { - SendPingWithCredentials(server_url_, username_, password_, false); + SendPingWithCredentials(server_url_, username_, password_, auth_method_, false); } -void SubsonicService::SendPingWithCredentials(QUrl url, const QString &username, const QString &password, const bool redirect) { +void SubsonicService::SendPingWithCredentials(QUrl url, const QString &username, const QString &password, const SubsonicSettingsPage::AuthMethod auth_method, const bool redirect) { if (!network_ || !redirect) { network_ = std::make_unique(); @@ -155,11 +159,22 @@ void SubsonicService::SendPingWithCredentials(QUrl url, const QString &username, ping_redirects_ = 0; } - const ParamList params = ParamList() << Param("c", kClientName) - << Param("v", kApiVersion) - << Param("f", "json") - << Param("u", username) - << Param("p", QString("enc:" + password.toUtf8().toHex())); + ParamList params = ParamList() << Param("c", kClientName) + << Param("v", kApiVersion) + << Param("f", "json") + << Param("u", username); + + if (auth_method == SubsonicSettingsPage::AuthMethod_Hex) { + params << Param("p", QString("enc:" + password.toUtf8().toHex())); + } + else { + const QString salt = Utilities::CryptographicRandomString(20); + QCryptographicHash md5(QCryptographicHash::Md5); + md5.addData(password_.toUtf8()); + md5.addData(salt.toUtf8()); + params << Param("s", salt); + params << Param("t", md5.result().toHex()); + } QUrlQuery url_query(url.query()); for (const Param ¶m : params) { @@ -170,8 +185,9 @@ void SubsonicService::SendPingWithCredentials(QUrl url, const QString &username, if (!url.path().isEmpty() && url.path().right(1) == "/") { url.setPath(url.path() + QString("rest/ping.view")); } - else + else { url.setPath(url.path() + QString("/rest/ping.view")); + } } url.setQuery(url_query); @@ -200,9 +216,9 @@ void SubsonicService::SendPingWithCredentials(QUrl url, const QString &username, QNetworkReply *reply = network_->get(req); replies_ << reply; QObject::connect(reply, &QNetworkReply::sslErrors, this, &SubsonicService::HandlePingSSLErrors); - QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, url, username, password]() { HandlePingReply(reply, url, username, password); }); + QObject::connect(reply, &QNetworkReply::finished, this, [this, reply, url, username, password, auth_method]() { HandlePingReply(reply, url, username, password, auth_method); }); - //qLog(Debug) << "Subsonic: Sending request" << url << query; + //qLog(Debug) << "Subsonic: Sending request" << url << url.query(); } @@ -214,7 +230,7 @@ void SubsonicService::HandlePingSSLErrors(const QList &ssl_errors) { } -void SubsonicService::HandlePingReply(QNetworkReply *reply, const QUrl &url, const QString &username, const QString &password) { +void SubsonicService::HandlePingReply(QNetworkReply *reply, const QUrl &url, const QString &username, const QString &password, const SubsonicSettingsPage::AuthMethod auth_method) { Q_UNUSED(url); @@ -246,7 +262,7 @@ void SubsonicService::HandlePingReply(QNetworkReply *reply, const QUrl &url, con if (!redirect_url.isEmpty()) { ++ping_redirects_; qLog(Debug) << "Redirecting ping request to" << redirect_url.toString(QUrl::RemoveQuery); - SendPingWithCredentials(redirect_url, username, password, true); + SendPingWithCredentials(redirect_url, username, password, auth_method, true); return; } } diff --git a/src/subsonic/subsonicservice.h b/src/subsonic/subsonicservice.h index 80085d84d..493c202c9 100644 --- a/src/subsonic/subsonicservice.h +++ b/src/subsonic/subsonicservice.h @@ -39,6 +39,7 @@ #include "core/song.h" #include "internet/internetservice.h" +#include "settings/subsonicsettingspage.h" class QSortFilterProxyModel; class QNetworkReply; @@ -72,6 +73,7 @@ class SubsonicService : public InternetService { bool http2() const { return http2_; } bool verify_certificate() const { return verify_certificate_; } bool download_album_covers() const { return download_album_covers_; } + SubsonicSettingsPage::AuthMethod auth_method() const { return auth_method_; } CollectionBackend *collection_backend() const { return collection_backend_; } CollectionModel *collection_model() const { return collection_model_; } @@ -87,13 +89,13 @@ class SubsonicService : public InternetService { public slots: void ShowConfig() override; void SendPing(); - void SendPingWithCredentials(QUrl url, const QString &username, const QString &password, const bool redirect = false); + void SendPingWithCredentials(QUrl url, const QString &username, const QString &password, const SubsonicSettingsPage::AuthMethod auth_method, const bool redirect = false); void GetSongs() override; void ResetSongsRequest() override; private slots: void HandlePingSSLErrors(const QList &ssl_errors); - void HandlePingReply(QNetworkReply *reply, const QUrl &url, const QString &username, const QString &password); + void HandlePingReply(QNetworkReply *reply, const QUrl &url, const QString &username, const QString &password, const SubsonicSettingsPage::AuthMethod auth_method); void SongsResultsReceived(const SongList &songs, const QString &error); private: @@ -125,6 +127,7 @@ class SubsonicService : public InternetService { bool http2_; bool verify_certificate_; bool download_album_covers_; + SubsonicSettingsPage::AuthMethod auth_method_; QStringList errors_; int ping_redirects_;