Browse Source

Add option to control WebUI clickjacking protection

Some users actually want embedding WebUI into their custom build iframe.
Closes #7370.
adaptive-webui-19844
Chocobo1 7 years ago
parent
commit
bad4d94f77
No known key found for this signature in database
GPG Key ID: 210D9C873253A68C
  1. 10
      src/base/preferences.cpp
  2. 4
      src/base/preferences.h
  3. 6
      src/gui/optionsdlg.cpp
  4. 7
      src/gui/optionsdlg.ui
  5. 5
      src/webui/api/appcontroller.cpp
  6. 10
      src/webui/webapplication.cpp
  7. 3
      src/webui/webapplication.h
  8. 10
      src/webui/www/private/preferences_content.html

10
src/base/preferences.cpp

@ -576,6 +576,16 @@ void Preferences::setWebUiPassword(const QString &new_password)
setValue("Preferences/WebUI/Password_ha1", md5.result().toHex()); setValue("Preferences/WebUI/Password_ha1", md5.result().toHex());
} }
bool Preferences::isWebUiClickjackingProtectionEnabled() const
{
return value("Preferences/WebUI/ClickjackingProtection", true).toBool();
}
void Preferences::setWebUiClickjackingProtectionEnabled(bool enabled)
{
setValue("Preferences/WebUI/ClickjackingProtection", enabled);
}
bool Preferences::isWebUiHttpsEnabled() const bool Preferences::isWebUiHttpsEnabled() const
{ {
return value("Preferences/WebUI/HTTPS/Enabled", false).toBool(); return value("Preferences/WebUI/HTTPS/Enabled", false).toBool();

4
src/base/preferences.h

@ -194,6 +194,10 @@ public:
QString getWebUiPassword() const; QString getWebUiPassword() const;
void setWebUiPassword(const QString &new_password); void setWebUiPassword(const QString &new_password);
// WebUI security
bool isWebUiClickjackingProtectionEnabled() const;
void setWebUiClickjackingProtectionEnabled(bool enabled);
// HTTPS // HTTPS
bool isWebUiHttpsEnabled() const; bool isWebUiHttpsEnabled() const;
void setWebUiHttpsEnabled(bool enabled); void setWebUiHttpsEnabled(bool enabled);

6
src/gui/optionsdlg.cpp

@ -378,6 +378,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->checkClickjacking, &QCheckBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->checkDynDNS, &QGroupBox::toggled, this, &ThisType::enableApplyButton); connect(m_ui->checkDynDNS, &QGroupBox::toggled, this, &ThisType::enableApplyButton);
connect(m_ui->comboDNSService, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton); connect(m_ui->comboDNSService, qComboBoxCurrentIndexChanged, this, &ThisType::enableApplyButton);
connect(m_ui->domainNameTxt, &QLineEdit::textChanged, this, &ThisType::enableApplyButton); connect(m_ui->domainNameTxt, &QLineEdit::textChanged, this, &ThisType::enableApplyButton);
@ -694,6 +695,8 @@ void OptionsDialog::saveOptions()
pref->setWebUiPassword(webUiPassword()); pref->setWebUiPassword(webUiPassword());
pref->setWebUiLocalAuthEnabled(!m_ui->checkBypassLocalAuth->isChecked()); pref->setWebUiLocalAuthEnabled(!m_ui->checkBypassLocalAuth->isChecked());
pref->setWebUiAuthSubnetWhitelistEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked()); pref->setWebUiAuthSubnetWhitelistEnabled(m_ui->checkBypassAuthSubnetWhitelist->isChecked());
// Security
pref->setWebUiClickjackingProtectionEnabled(m_ui->checkClickjacking->isChecked());
// DynDNS // DynDNS
pref->setDynDNSEnabled(m_ui->checkDynDNS->isChecked()); pref->setDynDNSEnabled(m_ui->checkDynDNS->isChecked());
pref->setDynDNSService(m_ui->comboDNSService->currentIndex()); pref->setDynDNSService(m_ui->comboDNSService->currentIndex());
@ -1096,6 +1099,9 @@ void OptionsDialog::loadOptions()
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());
// Security
m_ui->checkClickjacking->setChecked(pref->isWebUiClickjackingProtectionEnabled());
m_ui->checkDynDNS->setChecked(pref->isDynDNSEnabled()); m_ui->checkDynDNS->setChecked(pref->isDynDNSEnabled());
m_ui->comboDNSService->setCurrentIndex(static_cast<int>(pref->getDynDNSService())); m_ui->comboDNSService->setCurrentIndex(static_cast<int>(pref->getDynDNSService()));
m_ui->domainNameTxt->setText(pref->getDynDomainName()); m_ui->domainNameTxt->setText(pref->getDynDomainName());

7
src/gui/optionsdlg.ui

@ -3168,6 +3168,13 @@ Use ';' to split multiple entries. Can use wildcard '*'.</string>
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="checkClickjacking">
<property name="text">
<string>Enable clickjacking protection</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="checkDynDNS"> <widget class="QGroupBox" name="checkDynDNS">
<property name="title"> <property name="title">

5
src/webui/api/appcontroller.cpp

@ -205,6 +205,8 @@ void AppController::preferencesAction()
for (const Utils::Net::Subnet &subnet : copyAsConst(pref->getWebUiAuthSubnetWhitelist())) for (const Utils::Net::Subnet &subnet : copyAsConst(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");
// Security
data["web_ui_clickjacking_protection_enabled"] = pref->isWebUiClickjackingProtectionEnabled();
// Update my dynamic domain name // Update my dynamic domain name
data["dyndns_enabled"] = pref->isDynDNSEnabled(); data["dyndns_enabled"] = pref->isDynDNSEnabled();
data["dyndns_service"] = pref->getDynDNSService(); data["dyndns_service"] = pref->getDynDNSService();
@ -479,6 +481,9 @@ void AppController::setPreferencesAction()
// recognize new lines and commas as delimiters // recognize new lines and commas as delimiters
pref->setWebUiAuthSubnetWhitelist(m["bypass_auth_subnet_whitelist"].toString().split(QRegularExpression("\n|,"), QString::SkipEmptyParts)); pref->setWebUiAuthSubnetWhitelist(m["bypass_auth_subnet_whitelist"].toString().split(QRegularExpression("\n|,"), QString::SkipEmptyParts));
} }
// Security
if (m.contains("web_ui_clickjacking_protection_enabled"))
pref->setWebUiClickjackingProtectionEnabled(m["web_ui_clickjacking_protection_enabled"].toBool());
// Update my dynamic domain name // Update my dynamic domain name
if (m.contains("dyndns_enabled")) if (m.contains("dyndns_enabled"))
pref->setDynDNSEnabled(m["dyndns_enabled"].toBool()); pref->setDynDNSEnabled(m["dyndns_enabled"].toBool());

10
src/webui/webapplication.cpp

@ -428,6 +428,8 @@ void WebApplication::configure()
m_currentLocale = newLocale; m_currentLocale = newLocale;
m_translatedFiles.clear(); m_translatedFiles.clear();
} }
m_isClickjackingProtectionEnabled = pref->isWebUiClickjackingProtectionEnabled();
} }
void WebApplication::registerAPIController(const QString &scope, APIController *controller) void WebApplication::registerAPIController(const QString &scope, APIController *controller)
@ -525,11 +527,13 @@ Http::Response WebApplication::processRequest(const Http::Request &request, cons
print(error.message(), Http::CONTENT_TYPE_TXT); print(error.message(), Http::CONTENT_TYPE_TXT);
} }
// avoid clickjacking attacks
header(Http::HEADER_X_FRAME_OPTIONS, "SAMEORIGIN");
header(Http::HEADER_X_XSS_PROTECTION, "1; mode=block"); header(Http::HEADER_X_XSS_PROTECTION, "1; mode=block");
header(Http::HEADER_X_CONTENT_TYPE_OPTIONS, "nosniff"); header(Http::HEADER_X_CONTENT_TYPE_OPTIONS, "nosniff");
header(Http::HEADER_CONTENT_SECURITY_POLICY, "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; object-src 'none';");
if (m_isClickjackingProtectionEnabled) {
header(Http::HEADER_X_FRAME_OPTIONS, "SAMEORIGIN");
header(Http::HEADER_CONTENT_SECURITY_POLICY, "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; object-src 'none';");
}
return response(); return response();
} }

3
src/webui/webapplication.h

@ -142,4 +142,7 @@ private:
}; };
QMap<QString, TranslatedFile> m_translatedFiles; QMap<QString, TranslatedFile> m_translatedFiles;
QString m_currentLocale; QString m_currentLocale;
// security related
bool m_isClickjackingProtectionEnabled;
}; };

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

@ -458,6 +458,11 @@
<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>
</fieldset> </fieldset>
<div class="formRow">
<input type="checkbox" id="clickjacking_protection_checkbox" />
<label for="clickjacking_protection_checkbox">QBT_TR(Enable clickjacking protection)QBT_TR[CONTEXT=OptionsDialog]</label>
</div>
</fieldset> </fieldset>
<fieldset class="settings"> <fieldset class="settings">
@ -1022,6 +1027,9 @@
$('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();
// Security
$('clickjacking_protection_checkbox').setProperty('checked', pref.web_ui_clickjacking_protection_enabled);
// Update my dynamic domain name // Update my dynamic domain name
$('use_dyndns_checkbox').setProperty('checked', pref.dyndns_enabled); $('use_dyndns_checkbox').setProperty('checked', pref.dyndns_enabled);
$('dyndns_select').setProperty('value', pref.dyndns_service); $('dyndns_select').setProperty('value', pref.dyndns_service);
@ -1313,6 +1321,8 @@
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_clickjacking_protection_enabled', $('clickjacking_protection_checkbox').getProperty('checked'));
// Update my dynamic domain name // Update my dynamic domain name
settings.set('dyndns_enabled', $('use_dyndns_checkbox').getProperty('checked')); settings.set('dyndns_enabled', $('use_dyndns_checkbox').getProperty('checked'));
settings.set('dyndns_service', $('dyndns_select').getProperty('value')); settings.set('dyndns_service', $('dyndns_select').getProperty('value'));

Loading…
Cancel
Save