mirror of
https://github.com/overte-org/overte.git
synced 2025-04-06 12:52:49 +02:00
Merge pull request #887 from overte-org/feature/script_security
Added simple protection for avatar URL
This commit is contained in:
commit
edb8cc55b4
22 changed files with 429 additions and 11 deletions
|
@ -109,7 +109,7 @@ public class PermissionChecker extends Activity {
|
|||
JSONObject obj = new JSONObject();
|
||||
try {
|
||||
obj.put("firstRun",false);
|
||||
obj.put("Avatar/fullAvatarURL", avatarPaths[which]);
|
||||
obj.put(SETTINGS_FULL_PRIVATE_GROUP_NAME + "/Avatar/fullAvatarURL", avatarPaths[which]);
|
||||
File directory = new File(pathForJson);
|
||||
|
||||
if(!directory.exists()) directory.mkdirs();
|
||||
|
|
152
interface/resources/qml/hifi/dialogs/security/ScriptSecurity.qml
Normal file
152
interface/resources/qml/hifi/dialogs/security/ScriptSecurity.qml
Normal file
|
@ -0,0 +1,152 @@
|
|||
//
|
||||
// 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.replace(",", "\n");
|
||||
return arrayWhitelist;
|
||||
}
|
||||
|
||||
function setWhitelistAsText(whitelistText) {
|
||||
Settings.setValue("private/scriptPermissionGetAvatarURLSafeURLs", whitelistText.text);
|
||||
notificationText.text = "Whitelist saved.";
|
||||
}
|
||||
|
||||
function setAvatarProtection(enabled) {
|
||||
Settings.setValue("private/scriptPermissionGetAvatarURLEnable", enabled);
|
||||
console.info("Setting Protect Avatar URLs to:", enabled);
|
||||
}
|
||||
|
||||
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 {
|
||||
id: whitelistEnabled;
|
||||
|
||||
checked: Settings.getValue("private/scriptPermissionGetAvatarURLEnable", true);
|
||||
|
||||
anchors.right: parent.right;
|
||||
anchors.top: parent.top;
|
||||
anchors.topMargin: 10;
|
||||
onToggled: {
|
||||
setAvatarProtection(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) {
|
||||
|
|
|
@ -41,6 +41,15 @@
|
|||
#include <QtQuick/QQuickWindow>
|
||||
#include <memory>
|
||||
#include "WarningsSuppression.h"
|
||||
#include "ScriptPermissions.h"
|
||||
|
||||
QVariantMap AvatarBookmarks::getBookmarks() {
|
||||
if (ScriptPermissions::isCurrentScriptAllowed(ScriptPermissions::Permission::SCRIPT_PERMISSION_GET_AVATAR_URL)) {
|
||||
return _bookmarks;
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void addAvatarEntities(const QVariantList& avatarEntities) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -123,6 +132,12 @@ AvatarBookmarks::AvatarBookmarks() {
|
|||
}
|
||||
|
||||
void AvatarBookmarks::addBookmark(const QString& bookmarkName) {
|
||||
if (ScriptPermissions::isCurrentScriptAllowed(ScriptPermissions::Permission::SCRIPT_PERMISSION_GET_AVATAR_URL)) {
|
||||
addBookmarkInternal(bookmarkName);
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarBookmarks::addBookmarkInternal(const QString& bookmarkName) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
BLOCKING_INVOKE_METHOD(this, "addBookmark", Q_ARG(QString, bookmarkName));
|
||||
return;
|
||||
|
@ -134,6 +149,12 @@ void AvatarBookmarks::addBookmark(const QString& bookmarkName) {
|
|||
}
|
||||
|
||||
void AvatarBookmarks::saveBookmark(const QString& bookmarkName) {
|
||||
if (ScriptPermissions::isCurrentScriptAllowed(ScriptPermissions::Permission::SCRIPT_PERMISSION_GET_AVATAR_URL)) {
|
||||
saveBookmarkInternal(bookmarkName);
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarBookmarks::saveBookmarkInternal(const QString& bookmarkName) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
BLOCKING_INVOKE_METHOD(this, "saveBookmark", Q_ARG(QString, bookmarkName));
|
||||
return;
|
||||
|
@ -145,6 +166,12 @@ void AvatarBookmarks::saveBookmark(const QString& bookmarkName) {
|
|||
}
|
||||
|
||||
void AvatarBookmarks::removeBookmark(const QString& bookmarkName) {
|
||||
if (ScriptPermissions::isCurrentScriptAllowed(ScriptPermissions::Permission::SCRIPT_PERMISSION_GET_AVATAR_URL)) {
|
||||
removeBookmarkInternal(bookmarkName);
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarBookmarks::removeBookmarkInternal(const QString& bookmarkName) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
BLOCKING_INVOKE_METHOD(this, "removeBookmark", Q_ARG(QString, bookmarkName));
|
||||
return;
|
||||
|
@ -200,6 +227,12 @@ void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) {
|
|||
*/
|
||||
|
||||
void AvatarBookmarks::loadBookmark(const QString& bookmarkName) {
|
||||
if (ScriptPermissions::isCurrentScriptAllowed(ScriptPermissions::Permission::SCRIPT_PERMISSION_GET_AVATAR_URL)) {
|
||||
loadBookmarkInternal(bookmarkName);
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarBookmarks::loadBookmarkInternal(const QString& bookmarkName) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
BLOCKING_INVOKE_METHOD(this, "loadBookmark", Q_ARG(QString, bookmarkName));
|
||||
return;
|
||||
|
@ -268,6 +301,15 @@ void AvatarBookmarks::readFromFile() {
|
|||
}
|
||||
|
||||
QVariantMap AvatarBookmarks::getBookmark(const QString &bookmarkName)
|
||||
{
|
||||
if (ScriptPermissions::isCurrentScriptAllowed(ScriptPermissions::Permission::SCRIPT_PERMISSION_GET_AVATAR_URL)) {
|
||||
return getBookmarkInternal(bookmarkName);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap AvatarBookmarks::getBookmarkInternal(const QString &bookmarkName)
|
||||
{
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QVariantMap result;
|
||||
|
|
|
@ -100,7 +100,7 @@ public slots:
|
|||
* print("- " + key + " " + bookmarks[key].avatarUrl);
|
||||
* };
|
||||
*/
|
||||
QVariantMap getBookmarks() { return _bookmarks; }
|
||||
QVariantMap getBookmarks();
|
||||
|
||||
signals:
|
||||
/*@jsdoc
|
||||
|
@ -147,6 +147,11 @@ protected slots:
|
|||
void deleteBookmark() override;
|
||||
|
||||
private:
|
||||
QVariantMap getBookmarkInternal(const QString &bookmarkName);
|
||||
void addBookmarkInternal(const QString& bookmarkName);
|
||||
void saveBookmarkInternal(const QString& bookmarkName);
|
||||
void loadBookmarkInternal(const QString& bookmarkName);
|
||||
void removeBookmarkInternal(const QString& bookmarkName);
|
||||
const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json";
|
||||
const QString ENTRY_AVATAR_URL = "avatarUrl";
|
||||
const QString ENTRY_AVATAR_ICON = "avatarIcon";
|
||||
|
|
|
@ -258,10 +258,11 @@ void CrashRecoveryHandler::handleCrash(CrashRecoveryHandler::Action action) {
|
|||
// Display name and avatar
|
||||
settings.beginGroup(AVATAR_GROUP);
|
||||
displayName = settings.value(DISPLAY_NAME_KEY).toString();
|
||||
fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl();
|
||||
fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString();
|
||||
settings.endGroup();
|
||||
|
||||
fullAvatarURL = settings.value(SETTINGS_FULL_PRIVATE_GROUP_NAME + "/" + AVATAR_GROUP + "/" + FULL_AVATAR_URL_KEY).toUrl();
|
||||
|
||||
// Tutorial complete
|
||||
tutorialComplete = settings.value(TUTORIAL_COMPLETE_FLAG_KEY).toBool();
|
||||
}
|
||||
|
@ -280,12 +281,12 @@ void CrashRecoveryHandler::handleCrash(CrashRecoveryHandler::Action action) {
|
|||
// Display name and avatar
|
||||
settings.beginGroup(AVATAR_GROUP);
|
||||
settings.setValue(DISPLAY_NAME_KEY, displayName);
|
||||
settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL);
|
||||
settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName);
|
||||
settings.endGroup();
|
||||
|
||||
settings.setValue(SETTINGS_FULL_PRIVATE_GROUP_NAME + "/" + AVATAR_GROUP + "/" + FULL_AVATAR_URL_KEY, fullAvatarURL);
|
||||
|
||||
// Tutorial complete
|
||||
settings.setValue(TUTORIAL_COMPLETE_FLAG_KEY, tutorialComplete);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -226,7 +227,7 @@ MyAvatar::MyAvatar(QThread* thread) :
|
|||
_yawSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "yawSpeed", _yawSpeed),
|
||||
_hmdYawSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "hmdYawSpeed", _hmdYawSpeed),
|
||||
_pitchSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "pitchSpeed", _pitchSpeed),
|
||||
_fullAvatarURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "fullAvatarURL",
|
||||
_fullAvatarURLSetting(QStringList() << SETTINGS_FULL_PRIVATE_GROUP_NAME << AVATAR_SETTINGS_GROUP_NAME << "fullAvatarURL",
|
||||
AvatarData::defaultFullAvatarModelUrl()),
|
||||
_fullAvatarModelNameSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "fullAvatarModelName", _fullAvatarModelName),
|
||||
_animGraphURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "animGraphURL", QUrl("")),
|
||||
|
@ -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 };
|
||||
|
|
|
@ -21,6 +21,9 @@ SettingsScriptingInterface* SettingsScriptingInterface::getInstance() {
|
|||
}
|
||||
|
||||
QVariant SettingsScriptingInterface::getValue(const QString& setting) {
|
||||
if (_restrictPrivateValues && setting.startsWith(SETTINGS_FULL_PRIVATE_GROUP_NAME + "/")) {
|
||||
return {""};
|
||||
}
|
||||
QVariant value = Setting::Handle<QVariant>(setting).get();
|
||||
if (!value.isValid()) {
|
||||
value = "";
|
||||
|
@ -29,6 +32,9 @@ QVariant SettingsScriptingInterface::getValue(const QString& setting) {
|
|||
}
|
||||
|
||||
QVariant SettingsScriptingInterface::getValue(const QString& setting, const QVariant& defaultValue) {
|
||||
if (_restrictPrivateValues && setting.startsWith(SETTINGS_FULL_PRIVATE_GROUP_NAME + "/")) {
|
||||
return {""};
|
||||
}
|
||||
QVariant value = Setting::Handle<QVariant>(setting, defaultValue).get();
|
||||
if (!value.isValid()) {
|
||||
value = "";
|
||||
|
@ -40,7 +46,7 @@ void SettingsScriptingInterface::setValue(const QString& setting, const QVariant
|
|||
if (getValue(setting) == value) {
|
||||
return;
|
||||
}
|
||||
if (setting.startsWith("private/")) {
|
||||
if (setting.startsWith("private/") || setting.startsWith(SETTINGS_FULL_PRIVATE_GROUP_NAME + "/")) {
|
||||
if (_restrictPrivateValues) {
|
||||
qWarning() << "SettingsScriptingInterface::setValue -- restricted write: " << setting << value;
|
||||
return;
|
||||
|
|
|
@ -2106,6 +2106,14 @@ const QUrl& AvatarData::getSkeletonModelURL() const {
|
|||
}
|
||||
}
|
||||
|
||||
QString AvatarData::getSkeletonModelURLFromScript() const {
|
||||
if (isMyAvatar() && !isMyAvatarURLProtected()) {
|
||||
return _skeletonModelURL.toString();
|
||||
}
|
||||
|
||||
return QString();
|
||||
};
|
||||
|
||||
QByteArray AvatarData::packSkeletonData() const {
|
||||
// Send an avatar trait packet with the skeleton data before the mesh is loaded
|
||||
int avatarDataSize = 0;
|
||||
|
|
|
@ -610,6 +610,8 @@ public:
|
|||
AvatarData();
|
||||
virtual ~AvatarData();
|
||||
|
||||
virtual bool isMyAvatarURLProtected() const { return false; } // This needs to be here because both MyAvatar and AvatarData inherit from MyAvatar
|
||||
|
||||
static const QUrl& defaultFullAvatarModelUrl();
|
||||
|
||||
const QUuid getSessionUUID() const { return getID(); }
|
||||
|
@ -1355,7 +1357,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,11 @@ bool ScriptAvatarData::getLookAtSnappingEnabled() const {
|
|||
//
|
||||
QString ScriptAvatarData::getSkeletonModelURLFromScript() const {
|
||||
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
|
||||
return sharedAvatarData->getSkeletonModelURLFromScript();
|
||||
if (sharedAvatarData->isMyAvatar() && !sharedAvatarData->isMyAvatarURLProtected()) {
|
||||
return sharedAvatarData->getSkeletonModelURLFromScript();
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
|
|
124
libraries/script-engine/src/ScriptPermissions.cpp
Normal file
124
libraries/script-engine/src/ScriptPermissions.cpp
Normal file
|
@ -0,0 +1,124 @@
|
|||
//
|
||||
// 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]);
|
||||
if (!isCheckingEnabled.get()) {
|
||||
return true;
|
||||
}
|
||||
// Get the script manager:
|
||||
auto engine = Scriptable::engine();
|
||||
if (!engine) {
|
||||
// When this happens it means that function was called from QML or C++ and should always be allowed
|
||||
if (PERMISSIONS_DEBUG_ENABLED) {
|
||||
qDebug() << "ScriptPermissions::isCurrentScriptAllowed called outside script engine for permission: "
|
||||
<< scriptPermissionNames[permissionIndex];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
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 this is an entity script manager, we need to find the file name of the current script instead
|
||||
if (!scriptURL.startsWith("about:Entities")) {
|
||||
urlsToCheck.push_back(scriptURL);
|
||||
}
|
||||
|
||||
auto currentURL = Scriptable::context()->currentFileName();
|
||||
if (!currentURL.isEmpty() && currentURL != scriptURL) {
|
||||
urlsToCheck.push_back(currentURL);
|
||||
}
|
||||
|
||||
if (PERMISSIONS_DEBUG_ENABLED) {
|
||||
qDebug() << "ScriptPermissions::isCurrentScriptAllowed: filename: " << scriptURL;
|
||||
}
|
||||
auto parentContext = Scriptable::context()->parentContext();
|
||||
while (parentContext) {
|
||||
QString parentFilename = parentContext->currentFileName();
|
||||
if (!parentFilename.isEmpty()) {
|
||||
urlsToCheck.push_back(parentContext->currentFileName());
|
||||
if (PERMISSIONS_DEBUG_ENABLED) {
|
||||
qDebug() << "ScriptPermissions::isCurrentScriptAllowed: parent filename: " << parentContext->currentFileName();
|
||||
}
|
||||
}
|
||||
parentContext = parentContext->parentContext();
|
||||
}
|
||||
|
||||
// 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 (auto urlToCheck : urlsToCheck) {
|
||||
bool urlIsAllowed = false;
|
||||
for (const auto& str : safeURLPrefixes) {
|
||||
if (!str.isEmpty() && urlToCheck.startsWith(str)) {
|
||||
urlIsAllowed = true;
|
||||
if (PERMISSIONS_DEBUG_ENABLED) {
|
||||
qDebug() << "ScriptPermissions::isCurrentScriptAllowed: " << scriptPermissionNames[permissionIndex]
|
||||
<< " for script " << urlToCheck << " accepted with rule: " << str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!urlIsAllowed) {
|
||||
if (PERMISSIONS_DEBUG_ENABLED) {
|
||||
qDebug() << "ScriptPermissions::isCurrentScriptAllowed: " << scriptPermissionNames[permissionIndex]
|
||||
<< " for script " << urlToCheck << " rejected.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
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.
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
Q_LOGGING_CATEGORY(settings_handle, "settings.handle")
|
||||
|
||||
const QString SETTINGS_FULL_PRIVATE_GROUP_NAME = "fullPrivate";
|
||||
|
||||
const QString Settings::firstRun { "firstRun" };
|
||||
|
||||
|
||||
|
|
|
@ -31,6 +31,15 @@
|
|||
|
||||
Q_DECLARE_LOGGING_CATEGORY(settings_handle)
|
||||
|
||||
/**
|
||||
* @brief Name of the fully private settings group
|
||||
*
|
||||
* Settings in this group will be protected from reading and writing from script engines.
|
||||
*
|
||||
*/
|
||||
|
||||
extern const QString SETTINGS_FULL_PRIVATE_GROUP_NAME;
|
||||
|
||||
/**
|
||||
* @brief QSettings analog
|
||||
*
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
"Avatar/dominantHand": "right",
|
||||
"Avatar/flyingHMD": false,
|
||||
"Avatar/fullAvatarModelName": "Default",
|
||||
"Avatar/fullAvatarURL": "",
|
||||
"fullPrivate/Avatar/fullAvatarURL": "",
|
||||
"Avatar/headPitch": 0,
|
||||
"Avatar/pitchSpeed": 75,
|
||||
"Avatar/scale": 1,
|
||||
|
|
Loading…
Reference in a new issue