Merge pull request #112 from kasenvr/fix/whitelist-qml-adds

Whitelist disabled by default, now has a toggle
This commit is contained in:
kasenvr 2020-01-27 13:19:30 -05:00 committed by GitHub
commit 900e66ac01
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 183 additions and 141 deletions

View file

@ -8,7 +8,7 @@
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
// Security Settings for the Entity Script Whitelist // Security Settings for the Entity Script QML Whitelist
import Hifi 1.0 as Hifi import Hifi 1.0 as Hifi
import QtQuick 2.8 import QtQuick 2.8
@ -21,145 +21,186 @@ import "../../../windows"
Rectangle { Rectangle {
id: parentBody;
function getWhitelistAsText() {
var whitelist = Settings.getValue("private/settingsSafeURLS");
var arrayWhitelist = whitelist.split(",");
var whitelistText = arrayWhitelist.join("\n");
return whitelistText;
}
function setWhitelistAsText(whitelistText) {
Settings.setValue("private/settingsSafeURLS", whitelistText.text);
var originalSetString = whitelistText.text;
var originalSet = originalSetString.split(' ').join('');
var check = Settings.getValue("private/settingsSafeURLS");
var arrayCheck = check.split(",");
var textCheck = arrayCheck.join("\n");
if(textCheck == originalSet) {
setWhitelistSuccess(true);
} else {
setWhitelistSuccess(false);
}
}
function setWhitelistSuccess(success) {
if(success) {
notificationText.text = "Successfully saved settings.";
} else {
notificationText.text = "Error! Settings not saved.";
}
}
anchors.fill: parent
width: parent.width;
height: 120;
color: "#80010203";
HifiStylesUit.RalewayRegular { function getWhitelistAsText() {
id: titleText; var whitelist = Settings.getValue("private/settingsSafeURLS");
text: "Entity Script Whitelist" var arrayWhitelist = whitelist.split(",").join("\n");
// Text size return arrayWhitelist;
size: 24; }
// Style
color: "white"; function setWhitelistAsText(whitelistText) {
elide: Text.ElideRight; Settings.setValue("private/settingsSafeURLS", whitelistText.text);
// Anchors
anchors.top: parent.top; var originalSetString = whitelistText.text;
anchors.left: parent.left; var originalSet = originalSetString.split(' ').join('');
anchors.leftMargin: 20;
anchors.right: parent.right; var check = Settings.getValue("private/settingsSafeURLS");
anchors.rightMargin: 20; var arrayCheck = check.split(",").join("\n");
height: 60;
} setWhitelistSuccess(arrayCheck === originalSet);
}
function setWhitelistSuccess(success) {
if (success) {
notificationText.text = "Successfully saved settings.";
} else {
notificationText.text = "Error! Settings not saved.";
}
}
function toggleWhitelist(enabled) {
Settings.setValue("private/whitelistEnabled", enabled);
console.info("Toggling Whitelist to:", enabled);
}
function initCheckbox() {
var check = Settings.getValue("private/whitelistEnabled", false);
if (check) {
whitelistEnabled.toggle();
}
}
Rectangle {
id: textAreaRectangle; anchors.fill: parent
color: "black";
width: parent.width; width: parent.width;
height: 250; height: 120;
anchors.top: titleText.bottom; color: "#80010203";
ScrollView {
id: textAreaScrollView
anchors.fill: parent;
width: parent.width
height: parent.height
contentWidth: parent.width
contentHeight: parent.height
clip: false;
TextArea {
id: whitelistTextArea
text: getWhitelistAsText();
onTextChanged: notificationText.text = "";
width: parent.width;
height: parent.height;
font.family: "Ubuntu";
font.pointSize: 12;
color: "white";
}
}
Button {
id: saveChanges
anchors.topMargin: 5;
anchors.leftMargin: 20;
anchors.rightMargin: 20;
x: textAreaRectangle.x + textAreaRectangle.width - width - 15;
y: textAreaRectangle.y + textAreaRectangle.height - height;
contentItem: Text {
text: saveChanges.text
font.family: "Ubuntu";
font.pointSize: 12;
opacity: enabled ? 1.0 : 0.3
color: "black"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
text: "Save Changes"
onClicked: setWhitelistAsText(whitelistTextArea)
HifiStylesUit.RalewayRegular {
id: notificationText;
text: ""
// Text size
size: 14;
// Style
color: "white";
elide: Text.ElideLeft;
// Anchors
anchors.right: parent.right;
anchors.rightMargin: 130;
}
}
HifiStylesUit.RalewayRegular { HifiStylesUit.RalewayRegular {
id: descriptionText; id: titleText;
text: "Separate your URLs by line, not commas. Example: text: "Entity Script / QML Whitelist"
https://google.com/
https://bing.com/
https://mydomain.here/
\nEnsure there are no spaces or whitespace.
\nFor QML files, you can only whitelist each file individually
ending with '.qml'."
// Text size // Text size
size: 16; size: 24;
// Style // Style
color: "white"; color: "white";
elide: Text.ElideRight; elide: Text.ElideRight;
// Anchors // Anchors
anchors.top: parent.bottom; anchors.top: parent.top;
anchors.topMargin: 90;
anchors.left: parent.left; anchors.left: parent.left;
anchors.leftMargin: 20; anchors.leftMargin: 20;
anchors.right: parent.right; anchors.right: parent.right;
anchors.rightMargin: 20; anchors.rightMargin: 20;
height: 60;
CheckBox {
Component.onCompleted: {
initCheckbox();
}
id: whitelistEnabled;
anchors.right: parent.right;
anchors.top: parent.top;
anchors.topMargin: 10;
onToggled: {
toggleWhitelist(whitelistEnabled.checked)
}
Label {
text: "Enabled"
color: "white"
font.pixelSize: 18;
anchors.right: parent.left;
anchors.top: parent.top;
anchors.topMargin: 10;
}
}
} }
}
} Rectangle {
id: textAreaRectangle;
color: "black";
width: parent.width;
height: 250;
anchors.top: titleText.bottom;
ScrollView {
id: textAreaScrollView
anchors.fill: parent;
width: parent.width
height: parent.height
contentWidth: parent.width
contentHeight: parent.height
clip: false;
TextArea {
id: whitelistTextArea
text: getWhitelistAsText();
onTextChanged: notificationText.text = "";
width: parent.width;
height: parent.height;
font.family: "Ubuntu";
font.pointSize: 12;
color: "white";
}
}
Button {
id: saveChanges
anchors.topMargin: 5;
anchors.leftMargin: 20;
anchors.rightMargin: 20;
x: textAreaRectangle.x + textAreaRectangle.width - width - 15;
y: textAreaRectangle.y + textAreaRectangle.height - height;
contentItem: Text {
text: saveChanges.text
font.family: "Ubuntu";
font.pointSize: 12;
opacity: enabled ? 1.0 : 0.3
color: "black"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
text: "Save Changes"
onClicked: setWhitelistAsText(whitelistTextArea)
HifiStylesUit.RalewayRegular {
id: notificationText;
text: ""
// Text size
size: 16;
// Style
color: "white";
elide: Text.ElideLeft;
// Anchors
anchors.right: parent.left;
anchors.rightMargin: 10;
}
}
HifiStylesUit.RalewayRegular {
id: descriptionText;
text:
"The whitelist checks scripts and QML as they are loaded.<br/>
Therefore, if a script is cached or has no reason to load again,<br/>
removing it from the whitelist will have no effect until<br/>
it is reloaded.<br/>
Separate your whitelisted domains by line, not commas. e.g.
<blockquote>
<b>https://google.com/</b><br/>
<b>hifi://the-spot/</b><br/>
<b>127.0.0.1</b><br/>
<b>https://mydomain.here/</b>
</blockquote>
Ensure there are no spaces or whitespace.<br/><br/>
For QML files, you can only whitelist each file individually<br/>
ending with '.qml'."
// Text size
size: 16;
// Style
color: "white";
elide: Text.ElideRight;
textFormat: Text.RichText;
// Anchors
anchors.top: parent.bottom;
anchors.topMargin: 90;
anchors.left: parent.left;
anchors.leftMargin: 20;
anchors.right: parent.right;
anchors.rightMargin: 20;
}
}
}

View file

@ -287,7 +287,7 @@ Menu::Menu() {
} }
}); });
// Settings > Entity Script Whitelist // Settings > Entity Script / QML Whitelist
action = addActionToQMenuAndActionHash(settingsMenu, "Entity Script / QML Whitelist"); action = addActionToQMenuAndActionHash(settingsMenu, "Entity Script / QML Whitelist");
connect(action, &QAction::triggered, [] { connect(action, &QAction::triggered, [] {
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system"); auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");

View file

@ -2369,34 +2369,35 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
QList<QString> safeURLPrefixes = { "file:///", "atp:", "cache:" }; QList<QString> safeURLPrefixes = { "file:///", "atp:", "cache:" };
safeURLPrefixes += qEnvironmentVariable("EXTRA_WHITELIST").trimmed().split(QRegExp("\\s*,\\s*"), QString::SkipEmptyParts); safeURLPrefixes += qEnvironmentVariable("EXTRA_WHITELIST").trimmed().split(QRegExp("\\s*,\\s*"), QString::SkipEmptyParts);
// IF WHITELIST IS DISABLED IN SETTINGS // Entity Script Whitelist toggle check.
bool whitelistEnabled = Setting::Handle<bool>("private/whitelistEnabled", true).get(); Setting::Handle<bool> whitelistEnabled {"private/whitelistEnabled", false };
if (!whitelistEnabled) {
if (!whitelistEnabled.get()) {
passList = true; passList = true;
} }
// PULL SAFEURLS FROM INTERFACE.JSON Settings // Pull SAFEURLS from the Interface.JSON settings.
QVariant raw = Setting::Handle<QVariant>("private/settingsSafeURLS").get(); QVariant raw = Setting::Handle<QVariant>("private/settingsSafeURLS").get();
QStringList settingsSafeURLS = raw.toString().trimmed().split(QRegExp("\\s*[,\r\n]+\\s*"), QString::SkipEmptyParts); QStringList settingsSafeURLS = raw.toString().trimmed().split(QRegExp("\\s*[,\r\n]+\\s*"), QString::SkipEmptyParts);
safeURLPrefixes += settingsSafeURLS; safeURLPrefixes += settingsSafeURLS;
// END PULL SAFEURLS FROM INTERFACE.JSON Settings // END Pull SAFEURLS from the Interface.JSON settings.
// GET CURRENT DOMAIN WHITELIST BYPASS, IN CASE AN ENTIRE DOMAIN IS WHITELISTED // Get current domain whitelist bypass, in case an entire domain is whitelisted.
QString currentDomain = DependencyManager::get<AddressManager>()->getDomainURL().host(); QString currentDomain = DependencyManager::get<AddressManager>()->getDomainURL().host();
QString domainSafeIP = nodeList->getDomainHandler().getHostname(); QString domainSafeIP = nodeList->getDomainHandler().getHostname();
QString domainSafeURL = URL_SCHEME_HIFI + "://" + currentDomain; QString domainSafeURL = URL_SCHEME_HIFI + "://" + currentDomain;
for (const auto& str : safeURLPrefixes) { for (const auto& str : safeURLPrefixes) {
if (domainSafeURL.startsWith(str) || domainSafeIP.startsWith(str)) { if (domainSafeURL.startsWith(str) || domainSafeIP.startsWith(str)) {
qCDebug(scriptengine) << whitelistPrefix << "Whitelist Bypassed. Current Domain Host: " qCDebug(scriptengine) << whitelistPrefix << "Whitelist Bypassed, entire domain is whitelisted. Current Domain Host: "
<< nodeList->getDomainHandler().getHostname() << nodeList->getDomainHandler().getHostname()
<< "Current Domain: " << currentDomain; << "Current Domain: " << currentDomain;
passList = true; passList = true;
} }
} }
// END CURRENT DOMAIN WHITELIST BYPASS // END bypass whitelist based on current domain.
// START CHECKING AGAINST THE WHITELIST // Start processing scripts through the whitelist.
if (ScriptEngine::getContext() == "entity_server") { // If running on the server, do not engage whitelist. if (ScriptEngine::getContext() == "entity_server") { // If running on the server, do not engage whitelist.
passList = true; passList = true;
} else if (!passList) { // If waved through, do not engage whitelist. } else if (!passList) { // If waved through, do not engage whitelist.
@ -2406,11 +2407,11 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
if (!str.isEmpty() && scriptOrURL.startsWith(str)) { if (!str.isEmpty() && scriptOrURL.startsWith(str)) {
passList = true; passList = true;
qCDebug(scriptengine) << whitelistPrefix << "Script approved."; qCDebug(scriptengine) << whitelistPrefix << "Script approved.";
break; // bail early since we found a match break; // Bail early since we found a match.
} }
} }
} }
// END CHECKING AGAINST THE WHITELIST // END processing of scripts through the whitelist.
if (!passList) { // If the entity failed to pass for any reason, it's blocked and an error is thrown. if (!passList) { // If the entity failed to pass for any reason, it's blocked and an error is thrown.
qCDebug(scriptengine) << whitelistPrefix << "(disabled entity script)" << entityID.toString() << scriptOrURL; qCDebug(scriptengine) << whitelistPrefix << "(disabled entity script)" << entityID.toString() << scriptOrURL;