Browse Source

Add WebAPI session timeout settings

Raise default timeout to 1 hour.
adaptive-webui-19844
Chocobo1 6 years ago
parent
commit
89124bdcc8
No known key found for this signature in database
GPG Key ID: 210D9C873253A68C
  1. 10
      src/base/preferences.cpp
  2. 2
      src/base/preferences.h
  3. 3
      src/gui/optionsdialog.cpp
  4. 113
      src/gui/optionsdialog.ui
  5. 3
      src/webui/api/appcontroller.cpp
  6. 32
      src/webui/webapplication.cpp
  7. 13
      src/webui/webapplication.h
  8. 8
      src/webui/www/private/preferences_content.html

10
src/base/preferences.cpp

@ -598,6 +598,16 @@ void Preferences::setWebUIPassword(const QByteArray &password)
setValue("Preferences/WebUI/Password_PBKDF2", password); setValue("Preferences/WebUI/Password_PBKDF2", password);
} }
int Preferences::getWebUISessionTimeout() const
{
return value("Preferences/WebUI/SessionTimeout", 3600).toInt();
}
void Preferences::setWebUISessionTimeout(const int timeout)
{
setValue("Preferences/WebUI/SessionTimeout", timeout);
}
bool Preferences::isWebUiClickjackingProtectionEnabled() const bool Preferences::isWebUiClickjackingProtectionEnabled() const
{ {
return value("Preferences/WebUI/ClickjackingProtection", true).toBool(); return value("Preferences/WebUI/ClickjackingProtection", true).toBool();

2
src/base/preferences.h

@ -190,6 +190,8 @@ public:
void setWebUiUsername(const QString &username); void setWebUiUsername(const QString &username);
QByteArray getWebUIPassword() const; QByteArray getWebUIPassword() const;
void setWebUIPassword(const QByteArray &password); void setWebUIPassword(const QByteArray &password);
int getWebUISessionTimeout() const;
void setWebUISessionTimeout(int timeout);
// WebUI security // WebUI security
bool isWebUiClickjackingProtectionEnabled() const; bool isWebUiClickjackingProtectionEnabled() const;

3
src/gui/optionsdialog.cpp

@ -403,6 +403,7 @@ OptionsDialog::OptionsDialog(QWidget *parent)
connect(m_ui->checkBypassLocalAuth, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkBypassLocalAuth, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, m_ui->IPSubnetWhitelistButton, &QPushButton::setEnabled); connect(m_ui->checkBypassAuthSubnetWhitelist, &QAbstractButton::toggled, m_ui->IPSubnetWhitelistButton, &QPushButton::setEnabled);
connect(m_ui->spinSessionTimeout, qSpinBoxValueChanged, this, &ThisType::enableApplyButton);
connect(m_ui->checkClickjacking, &QCheckBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkClickjacking, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkCSRFProtection, &QCheckBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkCSRFProtection, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->groupHostHeaderValidation, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->groupHostHeaderValidation, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
@ -724,6 +725,7 @@ void OptionsDialog::saveOptions()
pref->setWebUiHttpsEnabled(m_ui->checkWebUiHttps->isChecked()); pref->setWebUiHttpsEnabled(m_ui->checkWebUiHttps->isChecked());
pref->setWebUIHttpsCertificatePath(m_ui->textWebUIHttpsCert->selectedPath()); pref->setWebUIHttpsCertificatePath(m_ui->textWebUIHttpsCert->selectedPath());
pref->setWebUIHttpsKeyPath(m_ui->textWebUIHttpsKey->selectedPath()); pref->setWebUIHttpsKeyPath(m_ui->textWebUIHttpsKey->selectedPath());
pref->setWebUISessionTimeout(m_ui->spinSessionTimeout->value());
// Authentication // Authentication
pref->setWebUiUsername(webUiUsername()); pref->setWebUiUsername(webUiUsername());
if (!webUiPassword().isEmpty()) if (!webUiPassword().isEmpty())
@ -1092,6 +1094,7 @@ void OptionsDialog::loadOptions()
m_ui->checkBypassLocalAuth->setChecked(!pref->isWebUiLocalAuthEnabled()); m_ui->checkBypassLocalAuth->setChecked(!pref->isWebUiLocalAuthEnabled());
m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUiAuthSubnetWhitelistEnabled()); m_ui->checkBypassAuthSubnetWhitelist->setChecked(pref->isWebUiAuthSubnetWhitelistEnabled());
m_ui->IPSubnetWhitelistButton->setEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked()); m_ui->IPSubnetWhitelistButton->setEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked());
m_ui->spinSessionTimeout->setValue(pref->getWebUISessionTimeout());
// Security // Security
m_ui->checkClickjacking->setChecked(pref->isWebUiClickjackingProtectionEnabled()); m_ui->checkClickjacking->setChecked(pref->isWebUiClickjackingProtectionEnabled());

113
src/gui/optionsdialog.ui

@ -2922,60 +2922,101 @@ Specify an IPv4 or IPv6 address. You can specify "0.0.0.0" for any IPv
<property name="title"> <property name="title">
<string>Authentication</string> <string>Authentication</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_10"> <layout class="QVBoxLayout" name="verticalLayout_35">
<item row="5" column="1"> <item>
<widget class="QPushButton" name="IPSubnetWhitelistButton"> <layout class="QGridLayout" name="gridLayout_8">
<property name="sizePolicy"> <item row="0" column="0">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> <widget class="QLabel" name="lblWebUiUsername">
<horstretch>0</horstretch> <property name="text">
<verstretch>0</verstretch> <string>Username:</string>
</sizepolicy> </property>
</property> </widget>
<property name="text"> </item>
<string>IP subnet whitelist...</string> <item row="0" column="1">
</property> <widget class="QLineEdit" name="textWebUiUsername"/>
</widget> </item>
</item> <item row="1" column="0">
<item row="2" column="1"> <widget class="QLabel" name="lblWebUiPassword">
<widget class="QLineEdit" name="textWebUiPassword"> <property name="text">
<property name="echoMode"> <string>Password:</string>
<enum>QLineEdit::Password</enum> </property>
</property> </widget>
<property name="placeholderText"> </item>
<string>Change current password</string> <item row="1" column="1">
</property> <widget class="QLineEdit" name="textWebUiPassword">
</widget> <property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
<property name="placeholderText">
<string>Change current password</string>
</property>
</widget>
</item>
</layout>
</item> </item>
<item row="3" column="0" colspan="2"> <item>
<widget class="QCheckBox" name="checkBypassLocalAuth"> <widget class="QCheckBox" name="checkBypassLocalAuth">
<property name="text"> <property name="text">
<string>Bypass authentication for clients on localhost</string> <string>Bypass authentication for clients on localhost</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0" colspan="2"> <item>
<widget class="QCheckBox" name="checkBypassAuthSubnetWhitelist"> <widget class="QCheckBox" name="checkBypassAuthSubnetWhitelist">
<property name="text"> <property name="text">
<string>Bypass authentication for clients in whitelisted IP subnets</string> <string>Bypass authentication for clients in whitelisted IP subnets</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0" rowspan="2"> <item>
<widget class="QLabel" name="lblWebUiUsername"> <widget class="QPushButton" name="IPSubnetWhitelistButton">
<property name="text"> <property name="sizePolicy">
<string>Username:</string> <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="lblWebUiPassword">
<property name="text"> <property name="text">
<string>Password:</string> <string>IP subnet whitelist...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1" rowspan="2"> <item>
<widget class="QLineEdit" name="textWebUiUsername"/> <layout class="QHBoxLayout" name="horizontalLayout_13">
<item>
<widget class="QLabel" name="lblSessionTimeout">
<property name="text">
<string>Session timeout:</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinSessionTimeout">
<property name="specialValueText">
<string>Disabled</string>
</property>
<property name="suffix">
<string> sec</string>
</property>
<property name="maximum">
<number>2147483647</number>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_11">
<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>
</item> </item>
</layout> </layout>
</widget> </widget>

3
src/webui/api/appcontroller.cpp

@ -229,6 +229,7 @@ void AppController::preferencesAction()
for (const Utils::Net::Subnet &subnet : asConst(pref->getWebUiAuthSubnetWhitelist())) for (const Utils::Net::Subnet &subnet : asConst(pref->getWebUiAuthSubnetWhitelist()))
authSubnetWhitelistStringList << Utils::Net::subnetToString(subnet); authSubnetWhitelistStringList << Utils::Net::subnetToString(subnet);
data["bypass_auth_subnet_whitelist"] = authSubnetWhitelistStringList.join("\n"); data["bypass_auth_subnet_whitelist"] = authSubnetWhitelistStringList.join("\n");
data["web_ui_session_timeout"] = pref->getWebUISessionTimeout();
// Use alternative Web UI // Use alternative Web UI
data["alternative_webui_enabled"] = pref->isAltWebUiEnabled(); data["alternative_webui_enabled"] = pref->isAltWebUiEnabled();
data["alternative_webui_path"] = pref->getWebUiRootFolder(); data["alternative_webui_path"] = pref->getWebUiRootFolder();
@ -538,6 +539,8 @@ void AppController::setPreferencesAction()
// recognize new lines and commas as delimiters // recognize new lines and commas as delimiters
pref->setWebUiAuthSubnetWhitelist(it.value().toString().split(QRegularExpression("\n|,"), QString::SkipEmptyParts)); pref->setWebUiAuthSubnetWhitelist(it.value().toString().split(QRegularExpression("\n|,"), QString::SkipEmptyParts));
} }
if (hasKey("web_ui_session_timeout"))
pref->setWebUISessionTimeout(it.value().toInt());
// Use alternative Web UI // Use alternative Web UI
if (hasKey("alternative_webui_enabled")) if (hasKey("alternative_webui_enabled"))
pref->setAltWebUiEnabled(it.value().toBool()); pref->setAltWebUiEnabled(it.value().toBool());

32
src/webui/webapplication.cpp

@ -332,6 +332,7 @@ void WebApplication::configure()
m_isLocalAuthEnabled = pref->isWebUiLocalAuthEnabled(); m_isLocalAuthEnabled = pref->isWebUiLocalAuthEnabled();
m_isAuthSubnetWhitelistEnabled = pref->isWebUiAuthSubnetWhitelistEnabled(); m_isAuthSubnetWhitelistEnabled = pref->isWebUiAuthSubnetWhitelistEnabled();
m_authSubnetWhitelist = pref->getWebUiAuthSubnetWhitelist(); m_authSubnetWhitelist = pref->getWebUiAuthSubnetWhitelist();
m_sessionTimeout = pref->getWebUISessionTimeout();
m_domainList = pref->getServerDomains().split(';', QString::SkipEmptyParts); m_domainList = pref->getServerDomains().split(';', QString::SkipEmptyParts);
std::for_each(m_domainList.begin(), m_domainList.end(), [](QString &entry) { entry = entry.trimmed(); }); std::for_each(m_domainList.begin(), m_domainList.end(), [](QString &entry) { entry = entry.trimmed(); });
@ -471,8 +472,7 @@ void WebApplication::sessionInitialize()
if (!sessionId.isEmpty()) { if (!sessionId.isEmpty()) {
m_currentSession = m_sessions.value(sessionId); m_currentSession = m_sessions.value(sessionId);
if (m_currentSession) { if (m_currentSession) {
const qint64 now = QDateTime::currentMSecsSinceEpoch() / 1000; if (m_currentSession->hasExpired(m_sessionTimeout)) {
if ((now - m_currentSession->m_timestamp) > INACTIVE_TIME) {
// session is outdated - removing it // session is outdated - removing it
delete m_sessions.take(sessionId); delete m_sessions.take(sessionId);
m_currentSession = nullptr; m_currentSession = nullptr;
@ -523,14 +523,14 @@ void WebApplication::sessionStart()
Q_ASSERT(!m_currentSession); Q_ASSERT(!m_currentSession);
// remove outdated sessions // remove outdated sessions
const qint64 now = QDateTime::currentMSecsSinceEpoch() / 1000; Algorithm::removeIf(m_sessions, [this](const QString &, const WebSession *session)
Algorithm::removeIf(m_sessions, [now](const QString &, const WebSession *session)
{ {
if ((now - session->timestamp()) <= INACTIVE_TIME) if (session->hasExpired(m_sessionTimeout)) {
return false; delete session;
return true;
}
delete session; return false;
return true;
}); });
m_currentSession = new WebSession(generateSid()); m_currentSession = new WebSession(generateSid());
@ -650,9 +650,16 @@ QString WebSession::id() const
return m_sid; return m_sid;
} }
qint64 WebSession::timestamp() const bool WebSession::hasExpired(const qint64 seconds) const
{ {
return m_timestamp; if (seconds <= 0)
return false;
return m_timer.hasExpired(seconds * 1000);
}
void WebSession::updateTimestamp()
{
m_timer.start();
} }
QVariant WebSession::getData(const QString &id) const QVariant WebSession::getData(const QString &id) const
@ -664,8 +671,3 @@ void WebSession::setData(const QString &id, const QVariant &data)
{ {
m_data[id] = data; m_data[id] = data;
} }
void WebSession::updateTimestamp()
{
m_timestamp = QDateTime::currentMSecsSinceEpoch() / 1000;
}

13
src/webui/webapplication.h

@ -29,6 +29,7 @@
#pragma once #pragma once
#include <QDateTime> #include <QDateTime>
#include <QElapsedTimer>
#include <QHash> #include <QHash>
#include <QObject> #include <QObject>
#include <QRegularExpression> #include <QRegularExpression>
@ -48,26 +49,23 @@ class APIController;
class WebApplication; class WebApplication;
constexpr char C_SID[] = "SID"; // name of session id cookie constexpr char C_SID[] = "SID"; // name of session id cookie
constexpr int INACTIVE_TIME = 900; // Session inactive time (in secs = 15 min.)
class WebSession : public ISession class WebSession : public ISession
{ {
friend class WebApplication;
public: public:
explicit WebSession(const QString &sid); explicit WebSession(const QString &sid);
QString id() const override; QString id() const override;
qint64 timestamp() const;
bool hasExpired(qint64 seconds) const;
void updateTimestamp();
QVariant getData(const QString &id) const override; QVariant getData(const QString &id) const override;
void setData(const QString &id, const QVariant &data) override; void setData(const QString &id, const QVariant &data) override;
private: private:
void updateTimestamp();
const QString m_sid; const QString m_sid;
qint64 m_timestamp; QElapsedTimer m_timer; // timestamp
QVariantHash m_data; QVariantHash m_data;
}; };
@ -148,6 +146,7 @@ private:
bool m_isLocalAuthEnabled; bool m_isLocalAuthEnabled;
bool m_isAuthSubnetWhitelistEnabled; bool m_isAuthSubnetWhitelistEnabled;
QList<Utils::Net::Subnet> m_authSubnetWhitelist; QList<Utils::Net::Subnet> m_authSubnetWhitelist;
int m_sessionTimeout;
// security related // security related
QStringList m_domainList; QStringList m_domainList;

8
src/webui/www/private/preferences_content.html

@ -731,6 +731,12 @@
<div class="formRow" style="padding-left: 30px; padding-top: 5px;"> <div class="formRow" style="padding-left: 30px; padding-top: 5px;">
<textarea id="bypass_auth_subnet_whitelist_textarea" rows="5" cols="48" placeholder="Example: 172.17.32.0/24, fdff:ffff:c8::/40"></textarea> <textarea id="bypass_auth_subnet_whitelist_textarea" rows="5" cols="48" placeholder="Example: 172.17.32.0/24, fdff:ffff:c8::/40"></textarea>
</div> </div>
<table>
<tr>
<td><label for="webUISessionTimeoutInput">QBT_TR(Session timeout:)QBT_TR[CONTEXT=OptionsDialog]</label></td>
<td><input type="number" id="webUISessionTimeoutInput" style="width: 4em;" min="0" />&nbsp;&nbsp;QBT_TR(sec)QBT_TR[CONTEXT=OptionsDialog]</td>
</tr>
</table>
</fieldset> </fieldset>
<fieldset class="settings"> <fieldset class="settings">
@ -1340,6 +1346,7 @@
$('bypass_auth_subnet_whitelist_checkbox').setProperty('checked', pref.bypass_auth_subnet_whitelist_enabled); $('bypass_auth_subnet_whitelist_checkbox').setProperty('checked', pref.bypass_auth_subnet_whitelist_enabled);
$('bypass_auth_subnet_whitelist_textarea').setProperty('value', pref.bypass_auth_subnet_whitelist); $('bypass_auth_subnet_whitelist_textarea').setProperty('value', pref.bypass_auth_subnet_whitelist);
updateBypasssAuthSettings(); updateBypasssAuthSettings();
$('webUISessionTimeoutInput').setProperty('value', pref.web_ui_session_timeout.toInt());
// Use alternative Web UI // Use alternative Web UI
$('use_alt_webui_checkbox').setProperty('checked', pref.alternative_webui_enabled); $('use_alt_webui_checkbox').setProperty('checked', pref.alternative_webui_enabled);
@ -1667,6 +1674,7 @@
settings.set('bypass_local_auth', $('bypass_local_auth_checkbox').getProperty('checked')); settings.set('bypass_local_auth', $('bypass_local_auth_checkbox').getProperty('checked'));
settings.set('bypass_auth_subnet_whitelist_enabled', $('bypass_auth_subnet_whitelist_checkbox').getProperty('checked')); settings.set('bypass_auth_subnet_whitelist_enabled', $('bypass_auth_subnet_whitelist_checkbox').getProperty('checked'));
settings.set('bypass_auth_subnet_whitelist', $('bypass_auth_subnet_whitelist_textarea').getProperty('value')); settings.set('bypass_auth_subnet_whitelist', $('bypass_auth_subnet_whitelist_textarea').getProperty('value'));
settings.set('web_ui_session_timeout', $('webUISessionTimeoutInput').getProperty('value'));
// Use alternative Web UI // Use alternative Web UI
var alternative_webui_enabled = $('use_alt_webui_checkbox').getProperty('checked'); var alternative_webui_enabled = $('use_alt_webui_checkbox').getProperty('checked');

Loading…
Cancel
Save