//// 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 { // SCRIPT_PERMISSION_GET_AVATAR_URL "Permission to get user's avatar URL", // SCRIPT_PERMISSION_BOOKMARKS "Permission to view user's bookmarked locations", // SCRIPT_PERMISSION_KEYBOARD_EVENTS "Permission to emit virtual keyboard events",}; extern const std::array<QString, static_cast<int>(ScriptPermissions::Permission::SCRIPT_PERMISSIONS_SIZE)> scriptPermissionSettingKeyNames { // SCRIPT_PERMISSION_GET_AVATAR_URL "private/scriptPermissionGetAvatarURLSafeURLs", // SCRIPT_PERMISSION_BOOKMARKS "private/scriptPermissionBookmarksSafeURLs", // SCRIPT_PERMISSION_KEYBOARD_EVENTS "private/scriptPermissionKeyboardEventsSafeURLs",}; extern const std::array<QString, static_cast<int>(ScriptPermissions::Permission::SCRIPT_PERMISSIONS_SIZE)> scriptPermissionSettingEnableKeyNames { // SCRIPT_PERMISSION_GET_AVATAR_URL "private/scriptPermissionGetAvatarURLEnable", // SCRIPT_PERMISSION_BOOKMARKS "private/scriptPermissionBookmarksEnable", // SCRIPT_PERMISSION_KEYBOARD_EVENTS "private/scriptPermissionKeyboardEventsEnable",}; extern const std::array<bool, static_cast<int>(ScriptPermissions::Permission::SCRIPT_PERMISSIONS_SIZE)> scriptPermissionSettingEnableDefaultValues { // SCRIPT_PERMISSION_GET_AVATAR_URL true, // SCRIPT_PERMISSION_BOOKMARKS true, // SCRIPT_PERMISSION_KEYBOARD_EVENTS true,}; 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;}
//
// 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 {
// SCRIPT_PERMISSION_GET_AVATAR_URL
"Permission to get user's avatar URL",
// SCRIPT_PERMISSION_BOOKMARKS
"Permission to view user's bookmarked locations",
// SCRIPT_PERMISSION_KEYBOARD_EVENTS
"Permission to emit virtual keyboard events",
};
extern const std::array<QString, static_cast<int>(ScriptPermissions::Permission::SCRIPT_PERMISSIONS_SIZE)> scriptPermissionSettingKeyNames {
// SCRIPT_PERMISSION_GET_AVATAR_URL
"private/scriptPermissionGetAvatarURLSafeURLs",
// SCRIPT_PERMISSION_BOOKMARKS
"private/scriptPermissionBookmarksSafeURLs",
// SCRIPT_PERMISSION_KEYBOARD_EVENTS
"private/scriptPermissionKeyboardEventsSafeURLs",
};
extern const std::array<QString, static_cast<int>(ScriptPermissions::Permission::SCRIPT_PERMISSIONS_SIZE)> scriptPermissionSettingEnableKeyNames {
// SCRIPT_PERMISSION_GET_AVATAR_URL
"private/scriptPermissionGetAvatarURLEnable",
// SCRIPT_PERMISSION_BOOKMARKS
"private/scriptPermissionBookmarksEnable",
// SCRIPT_PERMISSION_KEYBOARD_EVENTS
"private/scriptPermissionKeyboardEventsEnable",
};
extern const std::array<bool, static_cast<int>(ScriptPermissions::Permission::SCRIPT_PERMISSIONS_SIZE)> scriptPermissionSettingEnableDefaultValues {
// SCRIPT_PERMISSION_GET_AVATAR_URL
true,
// SCRIPT_PERMISSION_BOOKMARKS
true,
// SCRIPT_PERMISSION_KEYBOARD_EVENTS
true,
};
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;
}