diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index ec2fe2493859..6357ff47ed4b 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -764,6 +764,7 @@ void Preferences::setWebUIUsername(const QString &username) return; setValue(u"Preferences/WebUI/Username"_s, username); + m_credentialsChanged = true; } QByteArray Preferences::getWebUIPassword() const @@ -777,6 +778,7 @@ void Preferences::setWebUIPassword(const QByteArray &password) return; setValue(u"Preferences/WebUI/Password_PBKDF2"_s, password); + m_credentialsChanged = true; } int Preferences::getWebUIMaxAuthFailCount() const @@ -1977,5 +1979,11 @@ void Preferences::setAddNewTorrentDialogSavePathHistoryLength(const int value) void Preferences::apply() { if (SettingsStorage::instance()->save()) + { emit changed(); + if (m_credentialsChanged) { + emit webCredentialsChanged(); + m_credentialsChanged = false; + } + } } 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..5234e52c622c 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 QList authSubnetWhitelist = pref->getWebUIAuthSubnetWhitelist(); + if ((isLocalAuthEnabled && (isLocalAuthEnabled != m_isLocalAuthEnabled)) + || (!isAuthSubnetWhitelistEnabled && (isAuthSubnetWhitelistEnabled != m_isAuthSubnetWhitelistEnabled)) + || (!m_authSubnetWhitelist.isEmpty() && (authSubnetWhitelist != m_authSubnetWhitelist))) + { + // 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(cost 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, const 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..7713a0724ec0 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 = false; 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();