Add MD5 token authentication for Subsonic
This commit is contained in:
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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 ¶m : params) {
|
for (const Param ¶m : params) {
|
||||||
|
|||||||
@@ -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 ¶m : params) {
|
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) == "/") {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_;
|
||||||
|
|||||||
Reference in New Issue
Block a user