mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-01 10:13:22 +00:00
Add security dialog to access files from scripts
This commit is contained in:
parent
aed23f4751
commit
f6e3aa9fd8
@ -547,6 +547,7 @@
|
||||
<icon part="check_normal" align="left middle" x="2" />
|
||||
<icon part="check_selected" align="left middle" x="2" state="selected" />
|
||||
<icon part="check_disabled" align="left middle" x="2" state="disabled" />
|
||||
<icon part="check_selected" align="left middle" x="2" state="disabled selected" />
|
||||
</style>
|
||||
<style id="radio_button" border="2">
|
||||
<background color="radio_hot_face" state="mouse focus" />
|
||||
|
@ -1105,6 +1105,19 @@ from = From:
|
||||
to = To:
|
||||
tolerance = Tolerance:
|
||||
|
||||
[script_access]
|
||||
title = Security
|
||||
script_label = The following script:
|
||||
file_label = wants to access to this file:
|
||||
command_label = wants to execute the following command:
|
||||
dont_show_for_this_access = Don't show this specific alert again for this script
|
||||
dont_show_for_this_script = Give full trust to this script
|
||||
allow_execute_access = &Allow Execute Access
|
||||
allow_write_access = &Allow Write Access
|
||||
allow_read_access = &Allow Read Access
|
||||
give_full_access = Give Script Full &Access
|
||||
stop_script = &Stop Script
|
||||
|
||||
[select_accelerator]
|
||||
title = Keyboard Shortcut
|
||||
key = Key:
|
||||
|
22
data/widgets/script_access.xml
Normal file
22
data/widgets/script_access.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<!-- Aseprite -->
|
||||
<!-- Copyright (C) 2018 by David Capello -->
|
||||
<gui>
|
||||
<window id="script_access" text="@.title">
|
||||
<vbox>
|
||||
<label text="@.script_label" />
|
||||
<hbox border="4"><link id="script" /></hbox>
|
||||
<label id="file_label" />
|
||||
<hbox border="4"><link id="file" /></hbox>
|
||||
<separator horizontal="true" />
|
||||
<check id="dont_show" text="@.dont_show_for_this_access" />
|
||||
<check id="full" text="@.dont_show_for_this_script" />
|
||||
<separator horizontal="true" />
|
||||
<grid columns="1">
|
||||
<hbox homogeneous="true" cell_align="center bottom horizontal">
|
||||
<button id="allow" closewindow="true" magnet="true" minwidth="60" />
|
||||
<button id="stop" text="@.stop_script" closewindow="true" />
|
||||
</hbox>
|
||||
</grid>
|
||||
</vbox>
|
||||
</window>
|
||||
</gui>
|
@ -168,6 +168,7 @@ if(ENABLE_SCRIPTING)
|
||||
script/pixel_color_object.cpp
|
||||
script/point_class.cpp
|
||||
script/rectangle_class.cpp
|
||||
script/security.cpp
|
||||
script/selection_class.cpp
|
||||
script/site_class.cpp
|
||||
script/size_class.cpp
|
||||
|
@ -88,6 +88,7 @@ private:
|
||||
SaveFileBaseCommand::SaveFileBaseCommand(const char* id, CommandFlags flags)
|
||||
: Command(id, flags)
|
||||
{
|
||||
m_useUI = true;
|
||||
}
|
||||
|
||||
void SaveFileBaseCommand::onLoadParams(const Params& params)
|
||||
@ -109,6 +110,9 @@ void SaveFileBaseCommand::onLoadParams(const Params& params)
|
||||
m_selFrames.clear();
|
||||
m_adjustFramesByFrameTag = false;
|
||||
}
|
||||
|
||||
std::string useUI = params.get("useUI");
|
||||
m_useUI = (useUI.empty() || (useUI == "true"));
|
||||
}
|
||||
|
||||
// Returns true if there is a current sprite to save.
|
||||
@ -139,7 +143,8 @@ std::string SaveFileBaseCommand::saveAsDialog(
|
||||
#ifdef ENABLE_UI
|
||||
again:;
|
||||
base::paths newfilename;
|
||||
if (!app::show_file_selector(
|
||||
if (!m_useUI ||
|
||||
!app::show_file_selector(
|
||||
dlgTitle, filename, exts,
|
||||
FileSelectorType::Save,
|
||||
newfilename))
|
||||
@ -316,7 +321,7 @@ void SaveFileCopyAsCommand::onExecute(Context* context)
|
||||
bool isForTwitter = false;
|
||||
|
||||
#if ENABLE_UI
|
||||
if (context->isUIAvailable()) {
|
||||
if (m_useUI && context->isUIAvailable()) {
|
||||
ExportFileWindow win(doc);
|
||||
bool askOverwrite = true;
|
||||
|
||||
|
@ -44,6 +44,7 @@ namespace app {
|
||||
std::string m_slice;
|
||||
doc::SelectedFrames m_selFrames;
|
||||
bool m_adjustFramesByFrameTag;
|
||||
bool m_useUI;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -17,11 +17,13 @@
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/script/engine.h"
|
||||
#include "app/script/luacpp.h"
|
||||
#include "app/script/security.h"
|
||||
#include "app/site.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/ui/doc_view.h"
|
||||
#include "app/ui/editor/editor.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "base/fs.h"
|
||||
#include "doc/layer.h"
|
||||
#include "ui/alert.h"
|
||||
|
||||
@ -34,7 +36,10 @@ namespace {
|
||||
|
||||
int App_open(lua_State* L)
|
||||
{
|
||||
const char* filename = luaL_checkstring(L, 1);
|
||||
std::string absFn = base::get_absolute_path(luaL_checkstring(L, 1));
|
||||
if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, true))
|
||||
return luaL_error(L, "script doesn't have access to open file %s",
|
||||
absFn.c_str());
|
||||
|
||||
app::Context* ctx = App::instance()->context();
|
||||
Doc* oldDoc = ctx->activeDocument();
|
||||
@ -42,7 +47,7 @@ int App_open(lua_State* L)
|
||||
Command* openCommand =
|
||||
Commands::instance()->byId(CommandId::OpenFile());
|
||||
Params params;
|
||||
params.set("filename", filename);
|
||||
params.set("filename", absFn.c_str());
|
||||
ctx->executeCommand(openCommand, params);
|
||||
|
||||
Doc* newDoc = ctx->activeDocument();
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "app/app.h"
|
||||
#include "app/console.h"
|
||||
#include "app/script/luacpp.h"
|
||||
#include "app/script/security.h"
|
||||
#include "base/chrono.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "base/fs.h"
|
||||
@ -140,7 +141,7 @@ Engine::Engine()
|
||||
lua_register(L, "print", print);
|
||||
|
||||
lua_getglobal(L, "os");
|
||||
for (const char* name : { "execute", "remove", "rename", "exit", "tmpname" }) {
|
||||
for (const char* name : { "remove", "rename", "exit", "tmpname" }) {
|
||||
lua_pushcfunction(L, unsupported);
|
||||
lua_setfield(L, -2, name);
|
||||
}
|
||||
@ -148,6 +149,20 @@ Engine::Engine()
|
||||
lua_setfield(L, -2, "clock");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Wrap io.open()
|
||||
lua_getglobal(L, "io");
|
||||
lua_getfield(L, -1, "open");
|
||||
lua_pushcclosure(L, secure_io_open, 1);
|
||||
lua_setfield(L, -2, "open");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Wrap os.execute()
|
||||
lua_getglobal(L, "os");
|
||||
lua_getfield(L, -1, "execute");
|
||||
lua_pushcclosure(L, secure_os_execute, 1);
|
||||
lua_setfield(L, -2, "execute");
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Generic code used by metatables
|
||||
run_mt_index_code(L);
|
||||
|
||||
@ -270,19 +285,8 @@ bool Engine::evalFile(const std::string& filename)
|
||||
std::ifstream s(FSTREAM_PATH(filename));
|
||||
buf << s.rdbuf();
|
||||
}
|
||||
|
||||
std::string fn = filename;
|
||||
if (fn.size() > 2 &&
|
||||
#ifdef _WIN32
|
||||
fn[1] != ':'
|
||||
#else
|
||||
fn[0] != '/'
|
||||
#endif
|
||||
) {
|
||||
fn = base::join_path(base::get_current_path(), fn);
|
||||
}
|
||||
fn = base::get_canonical_path(fn);
|
||||
return evalCode(buf.str(), "@" + fn);
|
||||
std::string absFilename = base::get_absolute_path(filename);
|
||||
return evalCode(buf.str(), "@" + absFilename);
|
||||
}
|
||||
|
||||
void Engine::onConsolePrint(const char* text)
|
||||
|
@ -37,6 +37,13 @@ namespace app {
|
||||
class Site;
|
||||
namespace script {
|
||||
|
||||
enum class FileAccessMode {
|
||||
Execute = 1,
|
||||
Write = 2,
|
||||
Read = 4,
|
||||
Full = 7
|
||||
};
|
||||
|
||||
class EngineDelegate {
|
||||
public:
|
||||
virtual ~EngineDelegate() { }
|
||||
|
205
src/app/script/security.cpp
Normal file
205
src/app/script/security.cpp
Normal file
@ -0,0 +1,205 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/script/security.h"
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/context.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/ini_file.h"
|
||||
#include "app/launcher.h"
|
||||
#include "app/script/engine.h"
|
||||
#include "app/script/luacpp.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/sha1.h"
|
||||
#include "fmt/format.h"
|
||||
|
||||
#include "script_access.xml.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace app {
|
||||
namespace script {
|
||||
|
||||
namespace {
|
||||
|
||||
// Map from .lua file name -> sha1
|
||||
std::unordered_map<std::string, std::string> g_keys;
|
||||
|
||||
std::string get_key(const std::string& source)
|
||||
{
|
||||
auto it = g_keys.find(source);
|
||||
if (it != g_keys.end())
|
||||
return it->second;
|
||||
else
|
||||
return g_keys[source] = base::convert_to<std::string>(
|
||||
base::Sha1::calculateFromString(source));
|
||||
}
|
||||
|
||||
std::string get_script_filename(lua_State* L)
|
||||
{
|
||||
// Get script name
|
||||
lua_getglobal(L, "debug");
|
||||
lua_getfield(L, -1, "getinfo");
|
||||
lua_remove(L, -2);
|
||||
lua_pushinteger(L, 2);
|
||||
lua_pushstring(L, "S");
|
||||
lua_call(L, 2, 1);
|
||||
lua_getfield(L, -1, "source");
|
||||
const char* source = lua_tostring(L, -1);
|
||||
std::string script;
|
||||
if (source && *source)
|
||||
script = source+1;
|
||||
lua_pop(L, 2);
|
||||
return script;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
int secure_io_open(lua_State* L)
|
||||
{
|
||||
int n = lua_gettop(L);
|
||||
|
||||
std::string absFilename = base::get_absolute_path(lua_tostring(L, 1));
|
||||
|
||||
FileAccessMode mode = FileAccessMode::Read; // Read is the default access
|
||||
if (lua_tostring(L, 2) &&
|
||||
std::strchr(lua_tostring(L, 2), 'w') != nullptr) {
|
||||
mode = FileAccessMode::Write;
|
||||
}
|
||||
|
||||
if (!ask_access(L, absFilename.c_str(), mode, true)) {
|
||||
return luaL_error(L, "the script doesn't have access to file '%s'",
|
||||
absFilename.c_str());
|
||||
}
|
||||
|
||||
lua_pushvalue(L, lua_upvalueindex(1));
|
||||
lua_pushstring(L, absFilename.c_str());
|
||||
for (int i=2; i<=n; ++i)
|
||||
lua_pushvalue(L, i);
|
||||
lua_call(L, n, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int secure_os_execute(lua_State* L)
|
||||
{
|
||||
int n = lua_gettop(L);
|
||||
if (n == 0)
|
||||
return 0;
|
||||
|
||||
const char* cmd = lua_tostring(L, 1);
|
||||
if (!ask_access(L, cmd, FileAccessMode::Execute, false)) {
|
||||
// Stop script
|
||||
return luaL_error(L, "the script doesn't have access to execute the command: '%s'",
|
||||
cmd);
|
||||
}
|
||||
|
||||
lua_pushvalue(L, lua_upvalueindex(1));
|
||||
for (int i=1; i<=n; ++i)
|
||||
lua_pushvalue(L, i);
|
||||
lua_call(L, n, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool ask_access(lua_State* L,
|
||||
const char* filename,
|
||||
const FileAccessMode mode,
|
||||
const bool canOpenFile)
|
||||
{
|
||||
#ifdef ENABLE_UI
|
||||
// Ask for permission to open the file
|
||||
if (App::instance()->context()->isUIAvailable()) {
|
||||
std::string script = get_script_filename(L);
|
||||
if (script.empty()) // No script
|
||||
return luaL_error(L, "no debug information (script filename) to secure io.open() call");
|
||||
|
||||
const char* section = "script_access";
|
||||
std::string key = get_key(script);
|
||||
|
||||
int access = get_config_int(section, key.c_str(), 0);
|
||||
|
||||
// Has the correct access
|
||||
if ((access & int(mode)) == int(mode))
|
||||
return true;
|
||||
|
||||
std::string allowButtonText =
|
||||
mode == FileAccessMode::Execute ?
|
||||
Strings::script_access_allow_execute_access():
|
||||
mode == FileAccessMode::Write ?
|
||||
Strings::script_access_allow_write_access():
|
||||
Strings::script_access_allow_read_access();
|
||||
|
||||
app::gen::ScriptAccess dlg;
|
||||
dlg.script()->setText(script);
|
||||
dlg.fileLabel()->setText(
|
||||
canOpenFile ?
|
||||
Strings::script_access_file_label():
|
||||
Strings::script_access_command_label());
|
||||
dlg.file()->setText(filename);
|
||||
dlg.allow()->setText(allowButtonText);
|
||||
dlg.allow()->processMnemonicFromText();
|
||||
|
||||
dlg.script()->Click.connect(
|
||||
[&dlg]{
|
||||
app::launcher::open_folder(dlg.script()->text());
|
||||
});
|
||||
|
||||
dlg.full()->Click.connect(
|
||||
[&dlg, &allowButtonText](ui::Event&){
|
||||
if (dlg.full()->isSelected()) {
|
||||
dlg.dontShow()->setSelected(true);
|
||||
dlg.dontShow()->setEnabled(false);
|
||||
dlg.allow()->setText(Strings::script_access_give_full_access());
|
||||
dlg.allow()->processMnemonicFromText();
|
||||
dlg.layout();
|
||||
}
|
||||
else {
|
||||
dlg.dontShow()->setEnabled(true);
|
||||
dlg.allow()->setText(allowButtonText);
|
||||
dlg.allow()->processMnemonicFromText();
|
||||
dlg.layout();
|
||||
}
|
||||
});
|
||||
|
||||
if (canOpenFile) {
|
||||
dlg.file()->Click.connect(
|
||||
[&dlg]{
|
||||
std::string fn = dlg.file()->text();
|
||||
if (base::is_file(fn))
|
||||
app::launcher::open_folder(fn);
|
||||
else
|
||||
app::launcher::open_folder(base::get_file_path(fn));
|
||||
});
|
||||
}
|
||||
|
||||
dlg.openWindowInForeground();
|
||||
const bool allow = (dlg.closer() == dlg.allow());
|
||||
|
||||
// Save selected option
|
||||
if (allow && dlg.dontShow()->isSelected()) {
|
||||
if (dlg.full()->isSelected())
|
||||
set_config_int(section, key.c_str(), access | int(FileAccessMode::Full));
|
||||
else
|
||||
set_config_int(section, key.c_str(), access | int(mode));
|
||||
flush_config_file();
|
||||
}
|
||||
|
||||
if (!allow)
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace script
|
||||
} // namespace app
|
31
src/app/script/security.h
Normal file
31
src/app/script/security.h
Normal file
@ -0,0 +1,31 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_SCRIPT_SECURITY_H_INCLUDED
|
||||
#define APP_SCRIPT_SECURITY_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#ifndef ENABLE_SCRIPTING
|
||||
#error ENABLE_SCRIPTING must be defined
|
||||
#endif
|
||||
|
||||
#include "app/script/engine.h"
|
||||
|
||||
namespace app {
|
||||
namespace script {
|
||||
|
||||
int secure_io_open(lua_State* L);
|
||||
int secure_os_execute(lua_State* L);
|
||||
|
||||
bool ask_access(lua_State* L,
|
||||
const char* filename,
|
||||
const FileAccessMode mode,
|
||||
const bool canOpenFile);
|
||||
|
||||
} // namespace script
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -24,10 +24,12 @@
|
||||
#include "app/file/palette_file.h"
|
||||
#include "app/script/engine.h"
|
||||
#include "app/script/luacpp.h"
|
||||
#include "app/script/security.h"
|
||||
#include "app/site.h"
|
||||
#include "app/transaction.h"
|
||||
#include "app/tx.h"
|
||||
#include "app/ui/doc_view.h"
|
||||
#include "base/fs.h"
|
||||
#include "doc/frame_tag.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/mask.h"
|
||||
@ -114,8 +116,9 @@ int Sprite_crop(lua_State* L)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Sprite_saveAs(lua_State* L)
|
||||
int Sprite_saveAs_base(lua_State* L, std::string& absFn)
|
||||
{
|
||||
bool result = false;
|
||||
auto sprite = get_ptr<Sprite>(L, 1);
|
||||
const char* fn = luaL_checkstring(L, 2);
|
||||
if (fn && sprite) {
|
||||
@ -123,35 +126,43 @@ int Sprite_saveAs(lua_State* L)
|
||||
app::Context* appCtx = App::instance()->context();
|
||||
appCtx->setActiveDocument(doc);
|
||||
|
||||
absFn = base::get_absolute_path(fn);
|
||||
if (!ask_access(L, absFn.c_str(), FileAccessMode::Write, true))
|
||||
return luaL_error(L, "script doesn't have access to write file %s",
|
||||
absFn.c_str());
|
||||
|
||||
Command* saveCommand =
|
||||
Commands::instance()->byId(CommandId::SaveFileCopyAs());
|
||||
|
||||
Params params;
|
||||
params.set("filename", fn);
|
||||
appCtx->executeCommand(saveCommand);
|
||||
params.set("filename", absFn.c_str());
|
||||
params.set("useUI", "false");
|
||||
appCtx->executeCommand(saveCommand, params);
|
||||
|
||||
doc->setFilename(fn);
|
||||
result = true;
|
||||
}
|
||||
return 0;
|
||||
lua_pushboolean(L, result);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Sprite_saveAs(lua_State* L)
|
||||
{
|
||||
std::string fn;
|
||||
int res = Sprite_saveAs_base(L, fn);
|
||||
if (!fn.empty()) {
|
||||
auto sprite = get_ptr<Sprite>(L, 1);
|
||||
if (sprite) {
|
||||
Doc* doc = static_cast<Doc*>(sprite->document());
|
||||
doc->setFilename(fn);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int Sprite_saveCopyAs(lua_State* L)
|
||||
{
|
||||
auto sprite = get_ptr<Sprite>(L, 1);
|
||||
const char* fn = luaL_checkstring(L, 2);
|
||||
if (fn && sprite) {
|
||||
Doc* doc = static_cast<Doc*>(sprite->document());
|
||||
app::Context* appCtx = App::instance()->context();
|
||||
appCtx->setActiveDocument(doc);
|
||||
|
||||
Command* saveCommand =
|
||||
Commands::instance()->byId(CommandId::SaveFileCopyAs());
|
||||
|
||||
Params params;
|
||||
params.set("filename", fn);
|
||||
appCtx->executeCommand(saveCommand, params);
|
||||
}
|
||||
return 0;
|
||||
std::string fn;
|
||||
return Sprite_saveAs_base(L, fn);
|
||||
}
|
||||
|
||||
int Sprite_loadPalette(lua_State* L)
|
||||
@ -159,8 +170,13 @@ int Sprite_loadPalette(lua_State* L)
|
||||
auto sprite = get_ptr<Sprite>(L, 1);
|
||||
const char* fn = luaL_checkstring(L, 2);
|
||||
if (fn && sprite) {
|
||||
std::string absFn = base::get_absolute_path(fn);
|
||||
if (!ask_access(L, absFn.c_str(), FileAccessMode::Read, true))
|
||||
return luaL_error(L, "script doesn't have access to open file %s",
|
||||
absFn.c_str());
|
||||
|
||||
Doc* doc = static_cast<Doc*>(sprite->document());
|
||||
std::unique_ptr<doc::Palette> palette(load_palette(fn));
|
||||
std::unique_ptr<doc::Palette> palette(load_palette(absFn.c_str()));
|
||||
if (palette) {
|
||||
Tx tx;
|
||||
// TODO Merge this with the code in LoadPaletteCommand
|
||||
|
Loading…
x
Reference in New Issue
Block a user