From 8edda6b706523527438c1a54618c86423b30b137 Mon Sep 17 00:00:00 2001 From: Func Date: Fri, 6 Sep 2024 06:25:51 +0800 Subject: [PATCH] Logout existing sessions after an auth config change Closes #18443. --- src/base/preferences.cpp | 6 ++++ src/base/preferences.h | 3 ++ src/webui/api/authcontroller.cpp | 2 +- src/webui/api/isessionmanager.h | 2 +- src/webui/webapplication.cpp | 47 +++++++++++++++++++++++++++----- src/webui/webapplication.h | 9 ++++-- 6 files changed, 58 insertions(+), 11 deletions(-) diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index ec2fe2493859..da03446246c0 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -763,6 +763,7 @@ void Preferences::setWebUIUsername(const QString &username) if (username == getWebUIUsername()) return; + m_credentialsChanged = true; setValue(u"Preferences/WebUI/Username"_s, username); } @@ -776,6 +777,7 @@ void Preferences::setWebUIPassword(const QByteArray &password) if (password == getWebUIPassword()) return; + m_credentialsChanged = true; setValue(u"Preferences/WebUI/Password_PBKDF2"_s, password); } @@ -1977,5 +1979,9 @@ void Preferences::setAddNewTorrentDialogSavePathHistoryLength(const int value) void Preferences::apply() { if (SettingsStorage::instance()->save()) + { emit changed(); + if (m_credentialsChanged) + emit webCredentialsChanged(); + } } diff --git a/src/base/preferences.h b/src/base/preferences.h index a0ee4d0afa27..6f33c4ea6f83 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -430,7 +430,10 @@ public slots: signals: void changed(); + void webCredentialsChanged(); private: static Preferences *m_instance; + + bool m_credentialsChanged = false; }; diff --git a/src/webui/api/authcontroller.cpp b/src/webui/api/authcontroller.cpp index eb1d1baf2376..5b66c15132db 100644 --- a/src/webui/api/authcontroller.cpp +++ b/src/webui/api/authcontroller.cpp @@ -81,7 +81,7 @@ void AuthController::loginAction() { m_clientFailedLogins.remove(clientAddr); - m_sessionManager->sessionStart(); + m_sessionManager->sessionStart(true); setResult(u"Ok."_s); LogMsg(tr("WebAPI login success. IP: %1").arg(clientAddr)); } diff --git a/src/webui/api/isessionmanager.h b/src/webui/api/isessionmanager.h index 64396a7da9df..fb28ab302a07 100644 --- a/src/webui/api/isessionmanager.h +++ b/src/webui/api/isessionmanager.h @@ -43,6 +43,6 @@ struct ISessionManager virtual ~ISessionManager() = default; virtual QString clientId() const = 0; virtual ISession *session() = 0; - virtual void sessionStart() = 0; + virtual void sessionStart(bool authenticated) = 0; virtual void sessionEnd() = 0; }; diff --git a/src/webui/webapplication.cpp b/src/webui/webapplication.cpp index ddcf391768e2..2c7bd219c6cf 100644 --- a/src/webui/webapplication.cpp +++ b/src/webui/webapplication.cpp @@ -170,6 +170,7 @@ WebApplication::WebApplication(IApplication *app, QObject *parent) configure(); connect(Preferences::instance(), &Preferences::changed, this, &WebApplication::configure); + connect(Preferences::instance(), &Preferences::webCredentialsChanged, this, &WebApplication::logoutAllSessions); m_sessionCookieName = Preferences::instance()->getWebAPISessionCookieName(); if (!isValidCookieName(m_sessionCookieName)) @@ -435,9 +436,29 @@ void WebApplication::configure() } } - m_isLocalAuthEnabled = pref->isWebUILocalAuthEnabled(); - m_isAuthSubnetWhitelistEnabled = pref->isWebUIAuthSubnetWhitelistEnabled(); - m_authSubnetWhitelist = pref->getWebUIAuthSubnetWhitelist(); + const bool isLocalAuthEnabled = pref->isWebUILocalAuthEnabled(); + const bool isAuthSubnetWhitelistEnabled = pref->isWebUIAuthSubnetWhitelistEnabled(); + const auto authSubnetWhitelist = pref->getWebUIAuthSubnetWhitelist(); + if (((isLocalAuthEnabled != m_isLocalAuthEnabled) && isLocalAuthEnabled) || + ((isAuthSubnetWhitelistEnabled != m_isAuthSubnetWhitelistEnabled) && !isAuthSubnetWhitelistEnabled) || + ((authSubnetWhitelist != m_authSubnetWhitelist) && !m_authSubnetWhitelist.isEmpty())) + { + // remove sessions which bypassed authentication + Algorithm::removeIf(m_sessions, [](const QString &, const WebSession *session) + { + if (!session->isAuthenticated()) + { + delete session; + return true; + } + + return false; + }); + } + + m_isLocalAuthEnabled = isLocalAuthEnabled; + m_isAuthSubnetWhitelistEnabled = isAuthSubnetWhitelistEnabled; + m_authSubnetWhitelist = authSubnetWhitelist; m_sessionTimeout = pref->getWebUISessionTimeout(); m_domainList = pref->getServerDomains().split(u';', Qt::SkipEmptyParts); @@ -525,6 +546,12 @@ void WebApplication::configure() } } +void WebApplication::logoutAllSessions() +{ + qDeleteAll(m_sessions); + m_sessions.clear(); +} + void WebApplication::declarePublicAPI(const QString &apiPath) { m_publicAPIs << apiPath; @@ -677,7 +704,7 @@ void WebApplication::sessionInitialize() } if (!m_currentSession && !isAuthNeeded()) - sessionStart(); + sessionStart(false); } QString WebApplication::generateSid() const @@ -710,7 +737,7 @@ bool WebApplication::isPublicAPI(const QString &scope, const QString &action) co return m_publicAPIs.contains(u"%1/%2"_s.arg(scope, action)); } -void WebApplication::sessionStart() +void WebApplication::sessionStart(bool authenticated) { Q_ASSERT(!m_currentSession); @@ -726,7 +753,7 @@ void WebApplication::sessionStart() return false; }); - m_currentSession = new WebSession(generateSid(), app()); + m_currentSession = new WebSession(generateSid(), app(), authenticated); m_sessions[m_currentSession->id()] = m_currentSession; m_currentSession->registerAPIController(u"app"_s, new AppController(app(), this)); @@ -911,9 +938,10 @@ QHostAddress WebApplication::resolveClientAddress() const // WebSession -WebSession::WebSession(const QString &sid, IApplication *app) +WebSession::WebSession(const QString &sid, IApplication *app, bool authenticated) : ApplicationComponent(app) , m_sid {sid} + , m_authenticated {authenticated} { updateTimestamp(); } @@ -935,6 +963,11 @@ void WebSession::updateTimestamp() m_timer.start(); } +bool WebSession::isAuthenticated() const +{ + return m_authenticated; +} + void WebSession::registerAPIController(const QString &scope, APIController *controller) { Q_ASSERT(controller); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index 80530b15dd3e..493ec99fd5b4 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -71,12 +71,13 @@ namespace BitTorrent class WebSession final : public ApplicationComponent, public ISession { public: - explicit WebSession(const QString &sid, IApplication *app); + explicit WebSession(const QString &sid, IApplication *app, bool authenticated); QString id() const override; bool hasExpired(qint64 seconds) const; void updateTimestamp(); + bool isAuthenticated() const; void registerAPIController(const QString &scope, APIController *controller); APIController *getAPIController(const QString &scope) const; @@ -84,6 +85,7 @@ class WebSession final : public ApplicationComponent, public ISession private: const QString m_sid; QElapsedTimer m_timer; // timestamp + bool m_authenticated; QMap m_apiControllers; }; @@ -106,10 +108,13 @@ class WebApplication final : public ApplicationComponent void setUsername(const QString &username); void setPasswordHash(const QByteArray &passwordHash); +public slots: + void logoutAllSessions(); + private: QString clientId() const override; WebSession *session() override; - void sessionStart() override; + void sessionStart(bool authenticated) override; void sessionEnd() override; void doProcessRequest();