Add MD5 token authentication for Subsonic

This commit is contained in:
Jonas Kvinge
2021-07-30 21:16:41 +02:00
parent 5b7fc80f26
commit 88d7cb3ed5
6 changed files with 126 additions and 19 deletions

View File

@@ -80,6 +80,17 @@ void SubsonicSettingsPage::Load() {
ui_->checkbox_verify_certificate->setChecked(s.value("verifycertificate", false).toBool()); ui_->checkbox_verify_certificate->setChecked(s.value("verifycertificate", false).toBool());
ui_->checkbox_download_album_covers->setChecked(s.value("downloadalbumcovers", true).toBool()); ui_->checkbox_download_album_covers->setChecked(s.value("downloadalbumcovers", true).toBool());
ui_->checkbox_server_scrobbling->setChecked(s.value("serversidescrobbling", false).toBool()); ui_->checkbox_server_scrobbling->setChecked(s.value("serversidescrobbling", false).toBool());
AuthMethod auth_method = static_cast<AuthMethod>(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(); s.endGroup();
Init(ui_->layout_subsonicsettingspage->parentWidget()); Init(ui_->layout_subsonicsettingspage->parentWidget());
@@ -100,6 +111,12 @@ void SubsonicSettingsPage::Save() {
s.setValue("verifycertificate", ui_->checkbox_verify_certificate->isChecked()); s.setValue("verifycertificate", ui_->checkbox_verify_certificate->isChecked());
s.setValue("downloadalbumcovers", ui_->checkbox_download_album_covers->isChecked()); s.setValue("downloadalbumcovers", ui_->checkbox_download_album_covers->isChecked());
s.setValue("serversidescrobbling", ui_->checkbox_server_scrobbling->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(); s.endGroup();
} }
@@ -117,7 +134,7 @@ void SubsonicSettingsPage::TestClicked() {
return; 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); ui_->button_test->setEnabled(false);
} }

View File

@@ -42,13 +42,18 @@ class SubsonicSettingsPage : public SettingsPage {
static const char *kSettingsGroup; static const char *kSettingsGroup;
enum AuthMethod {
AuthMethod_Hex,
AuthMethod_MD5
};
void Load() override; void Load() override;
void Save() override; void Save() override;
bool eventFilter(QObject *object, QEvent *event) override; bool eventFilter(QObject *object, QEvent *event) override;
signals: 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: private slots:
void TestClicked(); void TestClicked();

View File

@@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>460</width> <width>460</width>
<height>500</height> <height>644</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@@ -93,6 +93,57 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupbox_auth_method">
<property name="title">
<string>Authentication method:</string>
</property>
<layout class="QHBoxLayout" name="layout_auth_method">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="auth_method_hex">
<property name="text">
<string>Hex (insecure)</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="auth_method_md5">
<property name="text">
<string>MD5 token</string>
</property>
</widget>
</item>
<item>
<spacer name="spacer_auth">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="button_test"> <widget class="QPushButton" name="button_test">
<property name="text"> <property name="text">

View File

@@ -32,6 +32,7 @@
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QNetworkReply> #include <QNetworkReply>
#include <QCryptographicHash>
#include <QSslConfiguration> #include <QSslConfiguration>
#include <QSslSocket> #include <QSslSocket>
#include <QSslError> #include <QSslError>
@@ -39,9 +40,12 @@
#include <QJsonObject> #include <QJsonObject>
#include <QJsonValue> #include <QJsonValue>
#include "core/utilities.h"
#include "subsonicservice.h" #include "subsonicservice.h"
#include "subsonicbaserequest.h" #include "subsonicbaserequest.h"
#include "settings/subsonicsettingspage.h"
SubsonicBaseRequest::SubsonicBaseRequest(SubsonicService *service, QObject *parent) SubsonicBaseRequest::SubsonicBaseRequest(SubsonicService *service, QObject *parent)
: QObject(parent), : QObject(parent),
service_(service), service_(service),
@@ -59,8 +63,19 @@ QUrl SubsonicBaseRequest::CreateUrl(const QString &ressource_name, const QList<P
<< Param("c", client_name()) << Param("c", client_name())
<< Param("v", api_version()) << Param("v", api_version())
<< Param("f", "json") << Param("f", "json")
<< Param("u", username()) << Param("u", username());
<< Param("p", QString("enc:" + password().toUtf8().toHex()));
if (service_->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; QUrlQuery url_query;
for (const Param &param : params) { for (const Param &param : params) {

View File

@@ -36,6 +36,7 @@
#include <QSslConfiguration> #include <QSslConfiguration>
#include <QSslSocket> #include <QSslSocket>
#include <QSslError> #include <QSslError>
#include <QCryptographicHash>
#include <QJsonValue> #include <QJsonValue>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
@@ -43,6 +44,7 @@
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QtDebug> #include <QtDebug>
#include "core/utilities.h"
#include "core/application.h" #include "core/application.h"
#include "core/player.h" #include "core/player.h"
#include "core/logging.h" #include "core/logging.h"
@@ -75,6 +77,7 @@ SubsonicService::SubsonicService(Application *app, QObject *parent)
http2_(true), http2_(true),
verify_certificate_(false), verify_certificate_(false),
download_album_covers_(true), download_album_covers_(true),
auth_method_(SubsonicSettingsPage::AuthMethod_MD5),
ping_redirects_(0) { ping_redirects_(0) {
app->player()->RegisterUrlHandler(url_handler_); app->player()->RegisterUrlHandler(url_handler_);
@@ -136,16 +139,17 @@ void SubsonicService::ReloadSettings() {
http2_ = s.value("http2", true).toBool(); http2_ = s.value("http2", true).toBool();
verify_certificate_ = s.value("verifycertificate", false).toBool(); verify_certificate_ = s.value("verifycertificate", false).toBool();
download_album_covers_ = s.value("downloadalbumcovers", true).toBool(); download_album_covers_ = s.value("downloadalbumcovers", true).toBool();
auth_method_ = static_cast<SubsonicSettingsPage::AuthMethod>(s.value("authmethod", SubsonicSettingsPage::AuthMethod_MD5).toInt());
s.endGroup(); s.endGroup();
} }
void SubsonicService::SendPing() { 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) { if (!network_ || !redirect) {
network_ = std::make_unique<QNetworkAccessManager>(); network_ = std::make_unique<QNetworkAccessManager>();
@@ -155,11 +159,22 @@ void SubsonicService::SendPingWithCredentials(QUrl url, const QString &username,
ping_redirects_ = 0; ping_redirects_ = 0;
} }
const ParamList params = ParamList() << Param("c", kClientName) ParamList params = ParamList() << Param("c", kClientName)
<< Param("v", kApiVersion) << Param("v", kApiVersion)
<< Param("f", "json") << Param("f", "json")
<< Param("u", username) << Param("u", username);
<< Param("p", QString("enc:" + password.toUtf8().toHex()));
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()); QUrlQuery url_query(url.query());
for (const Param &param : params) { for (const Param &param : params) {
@@ -170,8 +185,9 @@ void SubsonicService::SendPingWithCredentials(QUrl url, const QString &username,
if (!url.path().isEmpty() && url.path().right(1) == "/") { if (!url.path().isEmpty() && url.path().right(1) == "/") {
url.setPath(url.path() + QString("rest/ping.view")); url.setPath(url.path() + QString("rest/ping.view"));
} }
else else {
url.setPath(url.path() + QString("/rest/ping.view")); url.setPath(url.path() + QString("/rest/ping.view"));
}
} }
url.setQuery(url_query); url.setQuery(url_query);
@@ -200,9 +216,9 @@ void SubsonicService::SendPingWithCredentials(QUrl url, const QString &username,
QNetworkReply *reply = network_->get(req); QNetworkReply *reply = network_->get(req);
replies_ << reply; replies_ << reply;
QObject::connect(reply, &QNetworkReply::sslErrors, this, &SubsonicService::HandlePingSSLErrors); 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<QSslError> &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); Q_UNUSED(url);
@@ -246,7 +262,7 @@ void SubsonicService::HandlePingReply(QNetworkReply *reply, const QUrl &url, con
if (!redirect_url.isEmpty()) { if (!redirect_url.isEmpty()) {
++ping_redirects_; ++ping_redirects_;
qLog(Debug) << "Redirecting ping request to" << redirect_url.toString(QUrl::RemoveQuery); 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; return;
} }
} }

View File

@@ -39,6 +39,7 @@
#include "core/song.h" #include "core/song.h"
#include "internet/internetservice.h" #include "internet/internetservice.h"
#include "settings/subsonicsettingspage.h"
class QSortFilterProxyModel; class QSortFilterProxyModel;
class QNetworkReply; class QNetworkReply;
@@ -72,6 +73,7 @@ class SubsonicService : public InternetService {
bool http2() const { return http2_; } bool http2() const { return http2_; }
bool verify_certificate() const { return verify_certificate_; } bool verify_certificate() const { return verify_certificate_; }
bool download_album_covers() const { return download_album_covers_; } bool download_album_covers() const { return download_album_covers_; }
SubsonicSettingsPage::AuthMethod auth_method() const { return auth_method_; }
CollectionBackend *collection_backend() const { return collection_backend_; } CollectionBackend *collection_backend() const { return collection_backend_; }
CollectionModel *collection_model() const { return collection_model_; } CollectionModel *collection_model() const { return collection_model_; }
@@ -87,13 +89,13 @@ class SubsonicService : public InternetService {
public slots: public slots:
void ShowConfig() override; void ShowConfig() override;
void SendPing(); 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 GetSongs() override;
void ResetSongsRequest() override; void ResetSongsRequest() override;
private slots: private slots:
void HandlePingSSLErrors(const QList<QSslError> &ssl_errors); void HandlePingSSLErrors(const QList<QSslError> &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); void SongsResultsReceived(const SongList &songs, const QString &error);
private: private:
@@ -125,6 +127,7 @@ class SubsonicService : public InternetService {
bool http2_; bool http2_;
bool verify_certificate_; bool verify_certificate_;
bool download_album_covers_; bool download_album_covers_;
SubsonicSettingsPage::AuthMethod auth_method_;
QStringList errors_; QStringList errors_;
int ping_redirects_; int ping_redirects_;