Lua: Ask user before allowing to fetch

Change-Id: I58318598015a24689de19ae00bce65f004091e6b
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Marcus Tillmanns
2024-05-28 12:37:31 +02:00
parent e078d05f51
commit cf1aa1d1bb
4 changed files with 221 additions and 27 deletions

View File

@@ -42,6 +42,12 @@ add_qtc_plugin(Lua
# generateqtbindings.cpp # Use this if you need to generate some code.
)
qt_add_resources(Lua lua_images_rcc
PREFIX "/lua"
FILES
images/settingscategory_lua.png
images/settingscategory_lua@2x.png
)
set_source_files_properties(luauibindings.cpp PROPERTY SKIP_AUTOMOC ON PROPERTY SKIP_AUTOGEN ON)

View File

@@ -3,11 +3,22 @@
#include "../luaengine.h"
#include "../luaqttypes.h"
#include "../luatr.h"
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
#include <utils/aspects.h>
#include <utils/infobar.h>
#include <utils/layoutbuilder.h>
#include <utils/networkaccessmanager.h>
#include <utils/stylehelper.h>
#include <QApplication>
#include <QCheckBox>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMessageBox>
#include <QMetaEnum>
#include <QNetworkAccessManager>
#include <QNetworkReply>
@@ -37,8 +48,91 @@ static QString opToString(QNetworkAccessManager::Operation op)
void addFetchModule()
{
LuaEngine::registerProvider(
"Fetch", [](sol::state_view lua) -> sol::object {
class Module : Utils::AspectContainer
{
Utils::StringListAspect pluginsAllowedToFetch{this};
Utils::StringListAspect pluginsNotAllowedToFetch{this};
class LuaOptionsPage : public Core::IOptionsPage
{
public:
LuaOptionsPage(Module *module)
{
setId("BB.Lua.Fetch");
setDisplayName(Tr::tr("Network access"));
setCategory("ZY.Lua");
setDisplayCategory("Lua");
setCategoryIconPath(":/lua/images/settingscategory_lua.png");
setSettingsProvider(
[module] { return static_cast<Utils::AspectContainer *>(module); });
}
};
LuaOptionsPage settingsPage{this};
public:
Module()
{
setSettingsGroup("Lua.Fetch");
pluginsAllowedToFetch.setSettingsKey("pluginsAllowedToFetch");
pluginsAllowedToFetch.setLabelText("Plugins allowed to fetch data from the internet");
pluginsAllowedToFetch.setToolTip(
"List of plugins that are allowed to fetch data from the internet");
pluginsAllowedToFetch.setUiAllowAdding(false);
pluginsAllowedToFetch.setUiAllowEditing(false);
pluginsNotAllowedToFetch.setSettingsKey("pluginsNotAllowedToFetch");
pluginsNotAllowedToFetch.setLabelText(
"Plugins not allowed to fetch data from the internet");
pluginsNotAllowedToFetch.setToolTip(
"List of plugins that are not allowed to fetch data from the internet");
pluginsNotAllowedToFetch.setUiAllowAdding(false);
pluginsNotAllowedToFetch.setUiAllowEditing(false);
setLayouter([this] {
using namespace Layouting;
// clang-format off
return Form {
pluginsAllowedToFetch, br,
pluginsNotAllowedToFetch, br,
};
// clang-format on
});
readSettings();
}
~Module() { writeSettings(); }
enum class IsAllowed { Yes, No, NeedsToAsk };
IsAllowed isAllowedToFetch(const QString &pluginName) const
{
if (pluginsAllowedToFetch().contains(pluginName))
return IsAllowed::Yes;
if (pluginsNotAllowedToFetch().contains(pluginName))
return IsAllowed::No;
return IsAllowed::NeedsToAsk;
}
void setAllowedToFetch(const QString &pluginName, IsAllowed allowed)
{
if (allowed == IsAllowed::Yes)
pluginsAllowedToFetch.appendValue(pluginName);
else if (allowed == IsAllowed::No)
pluginsNotAllowedToFetch.appendValue(pluginName);
if (allowed == IsAllowed::Yes)
pluginsNotAllowedToFetch.removeValue(pluginName);
else if (allowed == IsAllowed::No)
pluginsAllowedToFetch.removeValue(pluginName);
}
};
std::shared_ptr<Module> module = std::make_shared<Module>();
LuaEngine::registerProvider("Fetch", [mod = std::move(module)](sol::state_view lua) -> sol::object {
const ScriptPluginSpec *pluginSpec = lua.get<ScriptPluginSpec *>("PluginSpec");
sol::table async = lua.script("return require('async')", "_fetch_").get<sol::table>();
@@ -60,12 +154,99 @@ void addFetchModule()
.arg(r->error());
});
fetch["fetch_cb"] = [guard = pluginSpec->connectionGuard.get()](
auto checkPermission = [mod,
pluginName = pluginSpec->name,
guard = pluginSpec->connectionGuard.get()](
QString url,
std::function<void()> fetch,
std::function<void()> notAllowed) {
auto isAllowed = mod->isAllowedToFetch(pluginName);
if (isAllowed == Module::IsAllowed::Yes) {
fetch();
return;
}
if (isAllowed == Module::IsAllowed::No) {
notAllowed();
return;
}
if (QApplication::activeModalWidget()) {
// We are already showing a modal dialog,
// so we have to use a QMessageBox instead of the info bar
auto msgBox = new QMessageBox(
QMessageBox::Question,
Tr::tr("Allow Internet access"),
Tr::tr("The plugin \"%1\" would like to fetch from the following url:\n%2")
.arg(pluginName)
.arg(url),
QMessageBox::Yes | QMessageBox::No,
Core::ICore::dialogParent());
msgBox->setCheckBox(new QCheckBox(Tr::tr("Remember choice")));
QObject::connect(
msgBox, &QMessageBox::accepted, guard, [mod, fetch, pluginName, msgBox]() {
if (msgBox->checkBox()->isChecked())
mod->setAllowedToFetch(pluginName, Module::IsAllowed::Yes);
fetch();
});
QObject::connect(
msgBox, &QMessageBox::rejected, guard, [mod, notAllowed, pluginName, msgBox]() {
if (msgBox->checkBox()->isChecked())
mod->setAllowedToFetch(pluginName, Module::IsAllowed::No);
notAllowed();
});
msgBox->show();
return;
}
Utils::InfoBarEntry entry{
Utils::Id::fromString("Fetch" + pluginName),
Tr::tr("The plugin \"%1\" would like to fetch data from the internet. Do "
"you want to allow this?")
.arg(pluginName)};
entry.setDetailsWidgetCreator([pluginName, url] {
const QString markdown = Tr::tr("The plugin \"**%1**\" would like to fetch "
"from the following url:\n\n")
.arg(pluginName)
+ QString("* [%3](%3)").arg(url);
QLabel *list = new QLabel();
list->setTextFormat(Qt::TextFormat::MarkdownText);
list->setText(markdown);
list->setMargin(Utils::StyleHelper::SpacingTokens::ExPaddingGapS);
return list;
});
entry.addCustomButton(Tr::tr("Always Allow"), [mod, pluginName, fetch]() {
mod->setAllowedToFetch(pluginName, Module::IsAllowed::Yes);
Core::ICore::infoBar()->removeInfo(Utils::Id::fromString("Fetch" + pluginName));
fetch();
});
entry.addCustomButton(Tr::tr("Allow once"), [pluginName, fetch]() {
Core::ICore::infoBar()->removeInfo(Utils::Id::fromString("Fetch" + pluginName));
fetch();
});
entry.setCancelButtonInfo(Tr::tr("Deny"), [mod, notAllowed, pluginName]() {
Core::ICore::infoBar()->removeInfo(Utils::Id::fromString("Fetch" + pluginName));
mod->setAllowedToFetch(pluginName, Module::IsAllowed::No);
notAllowed();
});
Core::ICore::infoBar()->addInfo(entry);
};
fetch["fetch_cb"] = [checkPermission,
pluginName = pluginSpec->name,
guard = pluginSpec->connectionGuard.get(),
mod](
const sol::table &options,
const sol::function &callback,
const sol::this_state &thisState) {
auto url = options.get<QString>("url");
auto actualFetch = [guard, url, options, callback, thisState]() {
auto method = (options.get_or<QString>("method", "GET")).toLower();
auto headers = options.get_or<sol::table>("headers", {});
auto data = options.get_or<QString>("body", {});
@@ -129,6 +310,13 @@ void addFetchModule()
}
};
checkPermission(url, actualFetch, [callback, pluginName]() {
callback(Tr::tr("Fetching is not allowed for the plugin \"%1\" (You can edit "
"permissions in Preferences => Lua)")
.arg(pluginName));
});
};
fetch["fetch"] = wrap(fetch["fetch_cb"]);
return fetch;

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB