mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-05 16:46:06 +02:00
Added simple protection for avatar URL
This commit is contained in:
parent
634dc64f8f
commit
225578febe
15 changed files with 367 additions and 3 deletions
178
interface/resources/qml/hifi/dialogs/security/ScriptSecurity.qml
Normal file
178
interface/resources/qml/hifi/dialogs/security/ScriptSecurity.qml
Normal file
|
@ -0,0 +1,178 @@
|
|||
//
|
||||
// ScriptPermissions.cpp
|
||||
// libraries/script-engine/src/ScriptPermissions.cpp
|
||||
//
|
||||
// Created by dr Karol Suprynowicz on 2024/03/24.
|
||||
// Copyright 2024 Overte e.V.
|
||||
//
|
||||
// Based on EntityScriptQMLWhitelist.qml
|
||||
// Created by Kalila L. on 2019.12.05 | realities.dev | somnilibertas@gmail.com
|
||||
// Copyright 2019 Kalila L.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
// Security settings for the script engines
|
||||
|
||||
import Hifi 1.0 as Hifi
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.12
|
||||
import stylesUit 1.0 as HifiStylesUit
|
||||
import controlsUit 1.0 as HiFiControls
|
||||
import PerformanceEnums 1.0
|
||||
import "../../../windows"
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: parentBody;
|
||||
|
||||
function getWhitelistAsText() {
|
||||
var whitelist = Settings.getValue("private/scriptPermissionGetAvatarURLSafeURLs");
|
||||
var arrayWhitelist = whitelist.split(",").join("\n");
|
||||
return arrayWhitelist;
|
||||
}
|
||||
|
||||
function setWhitelistAsText(whitelistText) {
|
||||
Settings.setValue("private/scriptPermissionGetAvatarURLSafeURLs", whitelistText.text);
|
||||
|
||||
var originalSetString = whitelistText.text;
|
||||
var originalSet = originalSetString.split(' ').join('');
|
||||
|
||||
var check = Settings.getValue("private/scriptPermissionGetAvatarURLSafeURLs");
|
||||
var arrayCheck = check.split(",").join("\n");
|
||||
|
||||
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/scriptPermissionGetAvatarURLEnable", enabled);
|
||||
console.info("Toggling Protect Avatar URLs to:", enabled);
|
||||
}
|
||||
|
||||
function initCheckbox() {
|
||||
var check = Settings.getValue("private/scriptPermissionGetAvatarURLEnable", true);
|
||||
|
||||
if (check) {
|
||||
whitelistEnabled.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
anchors.fill: parent
|
||||
width: parent.width;
|
||||
height: 120;
|
||||
color: "#80010203";
|
||||
|
||||
HifiStylesUit.RalewayRegular {
|
||||
id: titleText;
|
||||
text: "Protect Avatar URLs"
|
||||
// Text size
|
||||
size: 24;
|
||||
// Style
|
||||
color: "white";
|
||||
elide: Text.ElideRight;
|
||||
// Anchors
|
||||
anchors.top: parent.top;
|
||||
anchors.left: parent.left;
|
||||
anchors.leftMargin: 20;
|
||||
anchors.right: parent.right;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3305,7 +3305,7 @@ void Application::initializeUi() {
|
|||
|
||||
// END PULL SAFEURLS FROM INTERFACE.JSON Settings
|
||||
|
||||
if (AUTHORIZED_EXTERNAL_QML_SOURCE.isParentOf(url)) {
|
||||
if (QUrl(NetworkingConstants::OVERTE_COMMUNITY_APPLICATIONS).isParentOf(url)) {
|
||||
return true;
|
||||
} else {
|
||||
for (const auto& str : safeURLS) {
|
||||
|
|
|
@ -323,6 +323,19 @@ Menu::Menu() {
|
|||
}
|
||||
});
|
||||
|
||||
// Settings > Script Security
|
||||
action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::ScriptSecurity);
|
||||
connect(action, &QAction::triggered, [] {
|
||||
auto tablet = DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system");
|
||||
auto hmd = DependencyManager::get<HMDScriptingInterface>();
|
||||
|
||||
tablet->pushOntoStack("hifi/dialogs/security/ScriptSecurity.qml");
|
||||
|
||||
if (!hmd->getShouldShowTablet()) {
|
||||
hmd->toggleShouldShowTablet();
|
||||
}
|
||||
});
|
||||
|
||||
// Settings > Developer Menu
|
||||
addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menu", 0, false, this, SLOT(toggleDeveloperMenus()));
|
||||
|
||||
|
|
|
@ -190,6 +190,7 @@ namespace MenuOption {
|
|||
const QString RunTimingTests = "Run Timing Tests";
|
||||
const QString ScriptedMotorControl = "Enable Scripted Motor Control";
|
||||
const QString EntityScriptQMLWhitelist = "Entity Script / QML Whitelist";
|
||||
const QString ScriptSecurity = "Script Security";
|
||||
const QString ShowTrackedObjects = "Show Tracked Objects";
|
||||
const QString SelfieCamera = "Selfie";
|
||||
const QString SendWrongDSConnectVersion = "Send wrong DS connect version";
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
#include "MovingEntitiesOperator.h"
|
||||
#include "SceneScriptingInterface.h"
|
||||
#include "WarningsSuppression.h"
|
||||
#include "ScriptPermissions.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -2236,6 +2237,9 @@ AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString&
|
|||
return attachment;
|
||||
}
|
||||
|
||||
bool MyAvatar::isMyAvatarURLProtected() const {
|
||||
return !ScriptPermissions::isCurrentScriptAllowed(ScriptPermissions::Permission::SCRIPT_PERMISSION_GET_AVATAR_URL);
|
||||
}
|
||||
|
||||
int MyAvatar::parseDataFromBuffer(const QByteArray& buffer) {
|
||||
qCDebug(interfaceapp) << "Error: ignoring update packet for MyAvatar"
|
||||
|
|
|
@ -2683,6 +2683,7 @@ private:
|
|||
void setEnableDrawAverageFacing(bool drawAverage) { _drawAverageFacingEnabled = drawAverage; }
|
||||
bool getEnableDrawAverageFacing() const { return _drawAverageFacingEnabled; }
|
||||
virtual bool isMyAvatar() const override { return true; }
|
||||
virtual bool isMyAvatarURLProtected() const override;
|
||||
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
|
||||
virtual glm::vec3 getSkeletonPosition() const override;
|
||||
int _skeletonModelChangeCount { 0 };
|
||||
|
|
|
@ -2106,6 +2106,18 @@ const QUrl& AvatarData::getSkeletonModelURL() const {
|
|||
}
|
||||
}
|
||||
|
||||
QString AvatarData::getSkeletonModelURLFromScript() const {
|
||||
if (isMyAvatar()) {
|
||||
if (!isMyAvatarURLProtected()) {
|
||||
return _skeletonModelURL.toString();
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
};
|
||||
|
||||
QByteArray AvatarData::packSkeletonData() const {
|
||||
// Send an avatar trait packet with the skeleton data before the mesh is loaded
|
||||
int avatarDataSize = 0;
|
||||
|
|
|
@ -1355,7 +1355,7 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE virtual void detachAll(const QString& modelURL, const QString& jointName = QString());
|
||||
|
||||
QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); }
|
||||
QString getSkeletonModelURLFromScript() const;
|
||||
void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); }
|
||||
|
||||
void setOwningAvatarMixer(const QWeakPointer<Node>& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; }
|
||||
|
|
|
@ -204,7 +204,15 @@ bool ScriptAvatarData::getLookAtSnappingEnabled() const {
|
|||
//
|
||||
QString ScriptAvatarData::getSkeletonModelURLFromScript() const {
|
||||
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
|
||||
return sharedAvatarData->getSkeletonModelURLFromScript();
|
||||
if (sharedAvatarData->isMyAvatar()) {
|
||||
if (sharedAvatarData->isMyAvatarURLProtected()) {
|
||||
return QString();
|
||||
} else {
|
||||
return sharedAvatarData->getSkeletonModelURLFromScript();
|
||||
}
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
|
|
|
@ -58,6 +58,8 @@ namespace NetworkingConstants {
|
|||
const QString HF_PUBLIC_CDN_URL = "";
|
||||
const QString HF_MARKETPLACE_CDN_HOSTNAME = "";
|
||||
const QString OVERTE_CONTENT_CDN_URL = "https://content.overte.org/";
|
||||
const QString OVERTE_COMMUNITY_APPLICATIONS = { "https://more.overte.org/applications" };
|
||||
const QString OVERTE_TUTORIAL_SCRIPTS = { "https://more.overte.org/tutorial" };
|
||||
|
||||
#if USE_STABLE_GLOBAL_SERVICES
|
||||
const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.overte.org";
|
||||
|
|
|
@ -542,6 +542,10 @@ QString ScriptManager::getFilename() const {
|
|||
return lastPart;
|
||||
}
|
||||
|
||||
QString ScriptManager::getAbsoluteFilename() const {
|
||||
return _fileNameString;
|
||||
}
|
||||
|
||||
bool ScriptManager::hasValidScriptSuffix(const QString& scriptFileName) {
|
||||
QFileInfo fileInfo(scriptFileName);
|
||||
QString scriptSuffixToLower = fileInfo.completeSuffix().toLower();
|
||||
|
|
|
@ -430,6 +430,13 @@ public:
|
|||
*/
|
||||
QString getFilename() const;
|
||||
|
||||
/**
|
||||
* @brief Get the filename of the running script, with absolute path.
|
||||
*
|
||||
* @return QString Filename
|
||||
*/
|
||||
QString getAbsoluteFilename() const;
|
||||
|
||||
/**
|
||||
* @brief Underlying scripting engine
|
||||
*
|
||||
|
|
102
libraries/script-engine/src/ScriptPermissions.cpp
Normal file
102
libraries/script-engine/src/ScriptPermissions.cpp
Normal file
|
@ -0,0 +1,102 @@
|
|||
//
|
||||
// ScriptPermissions.cpp
|
||||
// libraries/script-engine/src/ScriptPermissions.cpp
|
||||
//
|
||||
// Created by dr Karol Suprynowicz on 2024/03/24.
|
||||
// Copyright 2024 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "ScriptPermissions.h"
|
||||
|
||||
#include <array>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include "ScriptEngine.h"
|
||||
#include "ScriptManager.h"
|
||||
#include "Scriptable.h"
|
||||
|
||||
static const bool PERMISSIONS_DEBUG_ENABLED = false;
|
||||
|
||||
extern const std::array<QString, static_cast<int>(ScriptPermissions::Permission::SCRIPT_PERMISSIONS_SIZE)> scriptPermissionNames {
|
||||
"Permission to get user's avatar URL" //SCRIPT_PERMISSION_GET_AVATAR_URL
|
||||
};
|
||||
|
||||
extern const std::array<QString, static_cast<int>(ScriptPermissions::Permission::SCRIPT_PERMISSIONS_SIZE)> scriptPermissionSettingKeyNames {
|
||||
"private/scriptPermissionGetAvatarURLSafeURLs" //SCRIPT_PERMISSION_GET_AVATAR_URL
|
||||
};
|
||||
|
||||
extern const std::array<QString, static_cast<int>(ScriptPermissions::Permission::SCRIPT_PERMISSIONS_SIZE)> scriptPermissionSettingEnableKeyNames {
|
||||
"private/scriptPermissionGetAvatarURLEnable" //SCRIPT_PERMISSION_GET_AVATAR_URL
|
||||
};
|
||||
|
||||
extern const std::array<bool, static_cast<int>(ScriptPermissions::Permission::SCRIPT_PERMISSIONS_SIZE)> scriptPermissionSettingEnableDefaultValues {
|
||||
true //SCRIPT_PERMISSION_GET_AVATAR_URL
|
||||
};
|
||||
|
||||
bool ScriptPermissions::isCurrentScriptAllowed(ScriptPermissions::Permission permission) {
|
||||
if (permission >= ScriptPermissions::Permission::SCRIPT_PERMISSIONS_SIZE) {
|
||||
return false;
|
||||
}
|
||||
int permissionIndex = static_cast<int>(permission);
|
||||
// Check if the permission checking is active
|
||||
Setting::Handle<bool> isCheckingEnabled(scriptPermissionSettingEnableKeyNames[permissionIndex], scriptPermissionSettingEnableDefaultValues[permissionIndex]);
|
||||
// Get the script manager:
|
||||
auto engine = Scriptable::engine();
|
||||
if (!engine) {
|
||||
qDebug() << "ScriptPermissions::isCurrentScriptAllowed called outside script engine for permission: " << scriptPermissionNames[permissionIndex];
|
||||
return false;
|
||||
}
|
||||
auto manager = engine->manager();
|
||||
if (!manager) {
|
||||
qDebug() << "ScriptPermissions::isCurrentScriptAllowed called from script engine with no script manager for permission: " << scriptPermissionNames[permissionIndex];
|
||||
return false;
|
||||
}
|
||||
std::vector<QString> urlsToCheck;
|
||||
QString scriptURL = manager->getAbsoluteFilename();
|
||||
if (scriptURL.startsWith("about:Entities")) {
|
||||
// This is entity script manager, we need to find the file name of the current script instead
|
||||
scriptURL = Scriptable::context()->currentFileName();
|
||||
urlsToCheck.push_back(scriptURL);
|
||||
if (PERMISSIONS_DEBUG_ENABLED) {
|
||||
qDebug() << "ScriptPermissions::isCurrentScriptAllowed: filename: " << scriptURL;
|
||||
}
|
||||
auto parentContext = Scriptable::context()->parentContext();
|
||||
while (parentContext) {
|
||||
urlsToCheck.push_back(parentContext->currentFileName());
|
||||
if (PERMISSIONS_DEBUG_ENABLED) {
|
||||
qDebug() << "ScriptPermissions::isCurrentScriptAllowed: parent filename: " << parentContext->currentFileName();
|
||||
}
|
||||
parentContext = parentContext->parentContext();
|
||||
}
|
||||
} else {
|
||||
urlsToCheck.push_back(scriptURL);
|
||||
}
|
||||
// Check if the script is allowed:
|
||||
QList<QString> safeURLPrefixes = { "file:///", "qrc:/", NetworkingConstants::OVERTE_COMMUNITY_APPLICATIONS,
|
||||
NetworkingConstants::OVERTE_TUTORIAL_SCRIPTS/*, "about:console"*/};
|
||||
Setting::Handle<QString> allowedURLsSetting(scriptPermissionSettingKeyNames[permissionIndex]);
|
||||
QList<QString> allowedURLs = allowedURLsSetting.get().split("\n");
|
||||
|
||||
for (auto entry : allowedURLs) {
|
||||
safeURLPrefixes.push_back(entry);
|
||||
}
|
||||
|
||||
for (const auto& str : safeURLPrefixes) {
|
||||
if (!str.isEmpty() && scriptURL.startsWith(str)) {
|
||||
if (PERMISSIONS_DEBUG_ENABLED) {
|
||||
qDebug() << "ScriptPermissions::isCurrentScriptAllowed: " << scriptPermissionNames[permissionIndex]
|
||||
<< " for script " << scriptURL << " accepted with rule: " << str;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (PERMISSIONS_DEBUG_ENABLED) {
|
||||
qDebug() << "ScriptPermissions::isCurrentScriptAllowed: " << scriptPermissionNames[permissionIndex] << " for script "
|
||||
<< scriptURL << " rejected.";
|
||||
}
|
||||
return false;
|
||||
}
|
31
libraries/script-engine/src/ScriptPermissions.h
Normal file
31
libraries/script-engine/src/ScriptPermissions.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// ScriptPermissions.h
|
||||
// libraries/script-engine/src/ScriptPermissions.h
|
||||
//
|
||||
// Created by dr Karol Suprynowicz on 2024/03/24.
|
||||
// Copyright 2024 Overte e.V.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "SettingHandle.h"
|
||||
#include "DependencyManager.h"
|
||||
|
||||
class ScriptPermissions {
|
||||
public:
|
||||
enum class Permission {
|
||||
SCRIPT_PERMISSION_GET_AVATAR_URL,
|
||||
SCRIPT_PERMISSIONS_SIZE
|
||||
};
|
||||
|
||||
static bool isCurrentScriptAllowed(Permission permission);
|
||||
//TODO: add a function to request permission through a popup
|
||||
};
|
||||
|
||||
// TODO: add ScriptPermissionsScriptingInterface, where script can check if they have permissions
|
||||
// and request permissions through a tablet popup.
|
|
@ -47,6 +47,7 @@ public:
|
|||
virtual void setParentID(const QUuid& parentID);
|
||||
|
||||
virtual bool isMyAvatar() const { return false; }
|
||||
virtual bool isMyAvatarURLProtected() const { return false; } // This needs to be here because both MyAvatar and AvatarData inherit from MyAvatar
|
||||
|
||||
virtual quint16 getParentJointIndex() const { return _parentJointIndex; }
|
||||
virtual void setParentJointIndex(quint16 parentJointIndex);
|
||||
|
|
Loading…
Reference in a new issue