aseprite/src/app/script/security.cpp
2019-12-11 12:22:01 -03:00

209 lines
5.3 KiB
C++

// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// 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 {
#ifdef ENABLE_UI
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
#endif // ENABLE_UI
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