From cf1aa1d1bb32560618891d2b0290259ec1f2a5a9 Mon Sep 17 00:00:00 2001 From: Marcus Tillmanns Date: Tue, 28 May 2024 12:37:31 +0200 Subject: [PATCH] Lua: Ask user before allowing to fetch Change-Id: I58318598015a24689de19ae00bce65f004091e6b Reviewed-by: David Schulz --- src/plugins/lua/CMakeLists.txt | 6 + src/plugins/lua/bindings/fetch.cpp | 242 ++++++++++++++++-- .../lua/images/settingscategory_lua.png | Bin 0 -> 513 bytes .../lua/images/settingscategory_lua@2x.png | Bin 0 -> 1152 bytes 4 files changed, 221 insertions(+), 27 deletions(-) create mode 100644 src/plugins/lua/images/settingscategory_lua.png create mode 100644 src/plugins/lua/images/settingscategory_lua@2x.png diff --git a/src/plugins/lua/CMakeLists.txt b/src/plugins/lua/CMakeLists.txt index 253d6f06ee6..c7a62e3a8b8 100644 --- a/src/plugins/lua/CMakeLists.txt +++ b/src/plugins/lua/CMakeLists.txt @@ -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) diff --git a/src/plugins/lua/bindings/fetch.cpp b/src/plugins/lua/bindings/fetch.cpp index 35e3255943f..22d1bc64cbd 100644 --- a/src/plugins/lua/bindings/fetch.cpp +++ b/src/plugins/lua/bindings/fetch.cpp @@ -3,11 +3,22 @@ #include "../luaengine.h" #include "../luaqttypes.h" +#include "../luatr.h" +#include +#include + +#include +#include +#include #include +#include +#include +#include #include #include +#include #include #include #include @@ -37,35 +48,205 @@ static QString opToString(QNetworkAccessManager::Operation op) void addFetchModule() { - LuaEngine::registerProvider( - "Fetch", [](sol::state_view lua) -> sol::object { - const ScriptPluginSpec *pluginSpec = lua.get("PluginSpec"); + class Module : Utils::AspectContainer + { + Utils::StringListAspect pluginsAllowedToFetch{this}; + Utils::StringListAspect pluginsNotAllowedToFetch{this}; - sol::table async = lua.script("return require('async')", "_fetch_").get(); - sol::function wrap = async["wrap"]; + 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(module); }); + } + }; - sol::table fetch = lua.create_table(); + LuaOptionsPage settingsPage{this}; - auto networkReplyType = lua.new_usertype( - "QNetworkReply", - "error", - sol::property([](QNetworkReply *self) -> int { return self->error(); }), - "readAll", - [](QNetworkReply *r) { return r->readAll().toStdString(); }, - "__tostring", - [](QNetworkReply *r) { - return QString("QNetworkReply(%1 \"%2\") => %3") - .arg(opToString(r->operation())) - .arg(r->url().toString()) - .arg(r->error()); - }); + public: + Module() + { + setSettingsGroup("Lua.Fetch"); - fetch["fetch_cb"] = [guard = pluginSpec->connectionGuard.get()]( - const sol::table &options, - const sol::function &callback, - const sol::this_state &thisState) { - auto url = options.get("url"); + 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 = std::make_shared(); + + LuaEngine::registerProvider("Fetch", [mod = std::move(module)](sol::state_view lua) -> sol::object { + const ScriptPluginSpec *pluginSpec = lua.get("PluginSpec"); + + sol::table async = lua.script("return require('async')", "_fetch_").get(); + sol::function wrap = async["wrap"]; + + sol::table fetch = lua.create_table(); + + auto networkReplyType = lua.new_usertype( + "QNetworkReply", + "error", + sol::property([](QNetworkReply *self) -> int { return self->error(); }), + "readAll", + [](QNetworkReply *r) { return r->readAll().toStdString(); }, + "__tostring", + [](QNetworkReply *r) { + return QString("QNetworkReply(%1 \"%2\") => %3") + .arg(opToString(r->operation())) + .arg(r->url().toString()) + .arg(r->error()); + }); + + auto checkPermission = [mod, + pluginName = pluginSpec->name, + guard = pluginSpec->connectionGuard.get()]( + QString url, + std::function fetch, + std::function 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("url"); + auto actualFetch = [guard, url, options, callback, thisState]() { auto method = (options.get_or("method", "GET")).toLower(); auto headers = options.get_or("headers", {}); auto data = options.get_or("body", {}); @@ -129,10 +310,17 @@ void addFetchModule() } }; - fetch["fetch"] = wrap(fetch["fetch_cb"]); + 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)); + }); + }; - return fetch; - }); + fetch["fetch"] = wrap(fetch["fetch_cb"]); + + return fetch; + }); } } // namespace Lua::Internal diff --git a/src/plugins/lua/images/settingscategory_lua.png b/src/plugins/lua/images/settingscategory_lua.png new file mode 100644 index 0000000000000000000000000000000000000000..7f1b6808feadaeb56b4e924a63d01457230377fd GIT binary patch literal 513 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4h9AWhNCh`Dhvz^oCO|{#S9Gmi6G3l!hU%s z0|Vn`PZ!4!jfuV!Hf9SKiX6A!zdO6*He+fUBE@olx;hg%F;#;i?WPnkQ) z7K^LNGDLGuWWDw9a>rQd2VpCCK&)5n=h)_rra zo2S#wvPfl1bFuRIW046T?@qg1w582AbIN4q6Q$Qqw$9?58t5nbW3wkKo7?d_Ym;UF zG9B=Kx-CrVin6!4(GRD8K}>Af*$kU<-)8UKm@c?(HM@T}Ps0{7$2ipoCUY!5JgnQ? zaO(2aq&1H!ICpL^S*{V4V#*tHVn&))>YRjkpFht!)avoIdF8jCUwChX+*>~PJAeIk z_NTh)Tc)y1p7Q>bz4WVA_s<*Lt{vaK_}}ULY7_p2Ew-`C|7866c3jRy%xi%e_uHVY zdMg&~ejrdkmsQo_%3L<3`Aw`p*n@c(KiTH;huVK#b5MeTck|?zzDH)QRsS2m^Yh|g T#>x|pfa2EE)z4*}Q$iB}s=MY* literal 0 HcmV?d00001 diff --git a/src/plugins/lua/images/settingscategory_lua@2x.png b/src/plugins/lua/images/settingscategory_lua@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a70400124db008e0a38a2cbb512502b9ea6251be GIT binary patch literal 1152 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4h9AWhN8@6(F_a>oCO|{#S9Gmi6G3l!hU%s z0|SeZr;B5VM`!8O+??Q0iR1OpzigUTmhTrSlgu2JU@+5dLta3kwr3B!OPa$K z#b(h|rLA+M?tMscV%pAskJ;t$^>b$!>T{OcS|=M^da&dD-|G=3^L)53Jm(fRn$73( zcH83n>KlKgb$(o6?WSkvC;dD)Vs7jyf$KSHJgv`9=$9`)b@{H^>BXuCzrMO+2H z*^0qz`}OUD2d0Vpvx=x2w=CM)_SozA`kLbG(-&_OZx=4!zbIc;$m54L!;75mw9AZ(a_4R-dAp_iQFwKB z&*Z+_j8^t%|LysFq0!WM`o0-L^4pnq@j7TEY3DH5=634*d$q2p;pcYdPpiex&J&sb zD(1iv^WF1AZQrH%?p{7;Z>odR51UCY`?wd}4fhT~Gg%fKHuCqnH1QR~2JRFU76Y4WZMR>t3 z2CZY0!jtcBFm;%dV{pJFSmZ_tW5BLCY#r@uYC>LzGPuSR#8YiMNsoLBGtqg&@QC0$|`I2kzcs1n21TQU1evtPFv zADAtEJo4wM;>v}Sg}tMHUg8g!Ai3dptb@#YN9*7J{%(B6-JdvVT_#_{r_@y|*4{He z^W5Clo_P)1jEiNAJ94YeH7uN^apNLGjx@u(t75rKGNHash>WTc|pj% zc`P@wRk9fs+o!EfvJ6+ZIy1aZ zV>~8)O~hWLopGxAl#Ht^0nxkxp&gf!y)>_G-?r(Kb85uY^s--H&R(vUT~+gPfAsSI zdj$4v-CvX&UlhC|;{f-ES@P2j^AElH$n3hGcgi`v4a;uI?b-LURo?yeerArUB>_KP ztE{x%GsjYY8uRDIWhSgv-~GNBZ%+AiI*sA)L}t!2evBsaqNhI|y_nXp<1C|4!QVy9 zTVzkKR9EQV%J|~Bu!`%)#>*4>ishfYI{Y)zm?3#x=nO3>!*|&`Y!fQqz4l9qIh%5z zv^pr@msMUG$Ej<|)jVI?FVB^8(DJS@K)Rs9*JIonD&`1Fmv#>Q%Xmv^*WNnR%t>~$h?`b?YHQ>y(;j@-8Qq!(P6 z_Uweuy49Vlzn=+Jk_q@#qvm}2&%7mPOpkx*6*@oX{zmWr>D-rQp8O$SB72(chMd_` PP|@P)>gTe~DWM4fJDV5W literal 0 HcmV?d00001