mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-29 05:42:33 +00:00
Compare commits
1 Commits
feat/plugi
...
nightly-bu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29d4513694 |
@@ -32,15 +32,13 @@ static void append_and_translate(ExPolygons &dst, const ExPolygons &src, const P
|
|||||||
for (; dst_idx < dst.size(); ++dst_idx)
|
for (; dst_idx < dst.size(); ++dst_idx)
|
||||||
dst[dst_idx].translate(instance_shift);
|
dst[dst_idx].translate(instance_shift);
|
||||||
}
|
}
|
||||||
// BBS: generate brim area by objs
|
// Orca: Translate the brim area into print coordinates and store it per instance.
|
||||||
static void append_and_translate(ExPolygons& dst, const ExPolygons& src,
|
static void append_and_translate(const ExPolygons& src, const PrintInstance& instance,
|
||||||
const PrintInstance& instance, size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap) {
|
size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap) {
|
||||||
ExPolygons srcShifted = src;
|
ExPolygons srcShifted = src;
|
||||||
Point instance_shift = instance.shift_without_plate_offset();
|
Point instance_shift = instance.shift_without_plate_offset();
|
||||||
for (size_t src_idx = 0; src_idx < srcShifted.size(); ++src_idx)
|
for (ExPolygon& expoly : srcShifted)
|
||||||
srcShifted[src_idx].translate(instance_shift);
|
expoly.translate(instance_shift);
|
||||||
srcShifted = diff_ex(srcShifted, dst);
|
|
||||||
//expolygons_append(dst, temp2);
|
|
||||||
expolygons_append(brimAreaMap[{ instance.print_object->id(), instance_idx }], std::move(srcShifted));
|
expolygons_append(brimAreaMap[{ instance.print_object->id(), instance_idx }], std::move(srcShifted));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,7 +570,7 @@ static ExPolygons outer_inner_brim_area(const Print& print,
|
|||||||
for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) {
|
for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) {
|
||||||
const PrintInstance& instance = object->instances()[instance_idx];
|
const PrintInstance& instance = object->instances()[instance_idx];
|
||||||
if (!brim_area_object.empty())
|
if (!brim_area_object.empty())
|
||||||
append_and_translate(brim_area, brim_area_object, instance, instance_idx, brimAreaMap);
|
append_and_translate(brim_area_object, instance, instance_idx, brimAreaMap);
|
||||||
append_and_translate(no_brim_area, no_brim_area_object, instance);
|
append_and_translate(no_brim_area, no_brim_area_object, instance);
|
||||||
append_and_translate(holes, holes_object, instance);
|
append_and_translate(holes, holes_object, instance);
|
||||||
append_and_translate(objectIslands, objectIsland, instance);
|
append_and_translate(objectIslands, objectIsland, instance);
|
||||||
@@ -875,6 +873,14 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_
|
|||||||
ExPolygons islands_area_ex = outer_inner_brim_area(print,
|
ExPolygons islands_area_ex = outer_inner_brim_area(print,
|
||||||
float(flow.scaled_spacing()), brimAreaMap, objPrintVec, printExtruders);
|
float(flow.scaled_spacing()), brimAreaMap, objPrintVec, printExtruders);
|
||||||
|
|
||||||
|
if (!print.config().combine_brims) {
|
||||||
|
ExPolygons claimed_area;
|
||||||
|
for (auto& [_, areas] : brimAreaMap) {
|
||||||
|
areas = diff_ex(areas, claimed_area);
|
||||||
|
expolygons_append(claimed_area, areas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// BBS: Find boundingbox of the first layer
|
// BBS: Find boundingbox of the first layer
|
||||||
for (const ObjectID printObjID : print.print_object_ids()) {
|
for (const ObjectID printObjID : print.print_object_ids()) {
|
||||||
BoundingBox bbx;
|
BoundingBox bbx;
|
||||||
|
|||||||
@@ -7,16 +7,8 @@
|
|||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <future>
|
|
||||||
#include <slic3r/GUI/BindDialog.hpp>
|
|
||||||
#include <slic3r/GUI/GUI_App.hpp>
|
|
||||||
#include <slic3r/plugin/PluginFsUtils.hpp>
|
|
||||||
#include <slic3r/plugin/PluginManager.hpp>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <wx/event.h>
|
|
||||||
#include <wx/msgdlg.h>
|
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
@@ -86,21 +78,24 @@ bool is_inside_allowed_root(const boost::filesystem::path& candidate, const boos
|
|||||||
// ScopedPluginAuditContext
|
// ScopedPluginAuditContext
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
thread_local std::string PluginAuditManager::m_current_plugin_key = "";
|
thread_local std::string PluginAuditManager::m_current_plugin_key = "";
|
||||||
thread_local std::string PluginAuditManager::m_current_capability_name = "";
|
thread_local std::string PluginAuditManager::m_current_capability_name = "";
|
||||||
|
thread_local PluginAuditManager::AuditMode PluginAuditManager::m_audit_mode = PluginAuditManager::AuditMode::Loading;
|
||||||
thread_local std::vector<boost::filesystem::path> PluginAuditManager::m_scoped_allowed_roots;
|
thread_local std::vector<boost::filesystem::path> PluginAuditManager::m_scoped_allowed_roots;
|
||||||
thread_local bool PluginAuditManager::m_audit_denial_pending = false;
|
|
||||||
thread_local bool PluginAuditManager::m_has_last_violation = false;
|
thread_local bool PluginAuditManager::m_has_last_violation = false;
|
||||||
thread_local AuditViolation PluginAuditManager::m_last_violation;
|
thread_local AuditViolation PluginAuditManager::m_last_violation;
|
||||||
|
|
||||||
ScopedPluginAuditContext::ScopedPluginAuditContext(const std::string& plugin_key,
|
ScopedPluginAuditContext::ScopedPluginAuditContext(const std::string& plugin_key,
|
||||||
const std::string& capability_name)
|
const std::string& capability_name,
|
||||||
|
PluginAuditManager::AuditMode mode)
|
||||||
: m_previous_id(PluginAuditManager::instance().current_plugin())
|
: m_previous_id(PluginAuditManager::instance().current_plugin())
|
||||||
, m_previous_capability(PluginAuditManager::instance().current_capability())
|
, m_previous_capability(PluginAuditManager::instance().current_capability())
|
||||||
|
, m_previous_mode(PluginAuditManager::instance().audit_mode())
|
||||||
, m_previous_scoped_roots(PluginAuditManager::m_scoped_allowed_roots)
|
, m_previous_scoped_roots(PluginAuditManager::m_scoped_allowed_roots)
|
||||||
{
|
{
|
||||||
PluginAuditManager::instance().set_current_plugin(plugin_key);
|
PluginAuditManager::instance().set_current_plugin(plugin_key);
|
||||||
PluginAuditManager::instance().set_current_capability(capability_name);
|
PluginAuditManager::instance().set_current_capability(capability_name);
|
||||||
|
PluginAuditManager::instance().set_audit_mode(mode);
|
||||||
PluginAuditManager::m_scoped_allowed_roots.clear();
|
PluginAuditManager::m_scoped_allowed_roots.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +103,7 @@ ScopedPluginAuditContext::~ScopedPluginAuditContext()
|
|||||||
{
|
{
|
||||||
PluginAuditManager::instance().set_current_plugin(m_previous_id);
|
PluginAuditManager::instance().set_current_plugin(m_previous_id);
|
||||||
PluginAuditManager::instance().set_current_capability(m_previous_capability);
|
PluginAuditManager::instance().set_current_capability(m_previous_capability);
|
||||||
|
PluginAuditManager::instance().set_audit_mode(m_previous_mode);
|
||||||
PluginAuditManager::m_scoped_allowed_roots = std::move(m_previous_scoped_roots);
|
PluginAuditManager::m_scoped_allowed_roots = std::move(m_previous_scoped_roots);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,6 +198,14 @@ bool PluginAuditManager::is_denied_filename(const boost::filesystem::path& candi
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Audit mode
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
void PluginAuditManager::set_audit_mode(AuditMode mode) { m_audit_mode = mode; }
|
||||||
|
|
||||||
|
PluginAuditManager::AuditMode PluginAuditManager::audit_mode() const { return m_audit_mode; }
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Policy checks
|
// Policy checks
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -215,15 +219,21 @@ AuditDecision PluginAuditManager::check_path_access(const boost::filesystem::pat
|
|||||||
if (plugin_key.empty())
|
if (plugin_key.empty())
|
||||||
return {true, ""}; // not running inside a plugin context
|
return {true, ""}; // not running inside a plugin context
|
||||||
|
|
||||||
// Denied filenames are checked before the allowed roots. The app config and the cloud
|
// Denied filenames are checked first, above both the Loading exemption below and the
|
||||||
// refresh token live directly inside data_dir(), which is a global allowed root, so a deny
|
// allowed roots. The app config and the cloud refresh token live directly inside
|
||||||
// placed any lower would be unreachable.
|
// data_dir(), which is a global allowed root, and no scope ever sets Enforcing — so a
|
||||||
|
// deny placed any lower would be unreachable for reads.
|
||||||
if (is_denied_filename(path)) {
|
if (is_denied_filename(path)) {
|
||||||
BOOST_LOG_TRIVIAL(warning) << "[AUDIT] block path=" << path.string() << " is_write=" << is_write
|
BOOST_LOG_TRIVIAL(warning) << "[AUDIT] block path=" << path.string() << " is_write=" << is_write
|
||||||
<< " plugin=" << plugin_key << " reason=denied filename";
|
<< " plugin=" << plugin_key << " reason=denied filename";
|
||||||
return {false, "denied filename"};
|
return {false, "denied filename"};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// During import/loading, only block writes. Python must be able to read
|
||||||
|
// stdlib modules and the plugin file itself during import.
|
||||||
|
if (m_audit_mode == AuditMode::Loading && !is_write)
|
||||||
|
return {true, ""};
|
||||||
|
|
||||||
namespace fs = boost::filesystem;
|
namespace fs = boost::filesystem;
|
||||||
fs::path candidate = path;
|
fs::path candidate = path;
|
||||||
|
|
||||||
@@ -251,6 +261,7 @@ AuditDecision PluginAuditManager::check_path_access(const boost::filesystem::pat
|
|||||||
}
|
}
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << "[AUDIT] block path=" << candidate.string() << " is_write=" << is_write
|
BOOST_LOG_TRIVIAL(warning) << "[AUDIT] block path=" << candidate.string() << " is_write=" << is_write
|
||||||
|
<< " audit_mode=" << (m_audit_mode == AuditMode::Loading ? "Loading" : "Enforcing")
|
||||||
<< " plugin=" << plugin_key;
|
<< " plugin=" << plugin_key;
|
||||||
return {false, "outside allowed root"};
|
return {false, "outside allowed root"};
|
||||||
}
|
}
|
||||||
@@ -258,99 +269,19 @@ AuditDecision PluginAuditManager::check_path_access(const boost::filesystem::pat
|
|||||||
AuditDecision PluginAuditManager::check_open(const std::string& path_str, const std::string& mode)
|
AuditDecision PluginAuditManager::check_open(const std::string& path_str, const std::string& mode)
|
||||||
{
|
{
|
||||||
const bool is_write = mode.find('w') != std::string::npos || mode.find('a') != std::string::npos ||
|
const bool is_write = mode.find('w') != std::string::npos || mode.find('a') != std::string::npos ||
|
||||||
mode.find('+') != std::string::npos || mode.find('x') != std::string::npos;
|
mode.find('+') != std::string::npos;
|
||||||
return check_path_access(boost::filesystem::path(path_str), is_write);
|
return check_path_access(boost::filesystem::path(path_str), is_write);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PluginAuditManager::request_filesystem_read_permissions(const std::string& plugin_key,
|
|
||||||
const std::vector<std::string>& paths)
|
|
||||||
{
|
|
||||||
if (plugin_key.empty() || paths.empty())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
PluginDescriptor descriptor;
|
|
||||||
if (!PluginManager::instance().try_get_plugin_descriptor(plugin_key, descriptor) || descriptor.plugin_root.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
PluginInstallState state;
|
|
||||||
read_install_state(boost::filesystem::path(descriptor.plugin_root), state);
|
|
||||||
|
|
||||||
std::vector<std::string> missing;
|
|
||||||
for (const std::string& path : paths) {
|
|
||||||
if (std::find(state.permissions.fs_read.begin(), state.permissions.fs_read.end(), path) == state.permissions.fs_read.end())
|
|
||||||
missing.push_back(path);
|
|
||||||
}
|
|
||||||
if (missing.empty())
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (wxTheApp == nullptr || GUI::wxGetApp().is_closing())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
auto show_dialog = [&descriptor, &missing]() {
|
|
||||||
wxString requested_paths;
|
|
||||||
for (const std::string& path : missing)
|
|
||||||
requested_paths += wxString::FromUTF8(path.c_str()) + "\n";
|
|
||||||
|
|
||||||
wxMessageDialog dialog(
|
|
||||||
nullptr,
|
|
||||||
wxString::Format("Plugin \"%s\" requests filesystem read access to:\n%s",
|
|
||||||
wxString::FromUTF8(descriptor.name.c_str()), requested_paths),
|
|
||||||
"Plugin permissions",
|
|
||||||
wxYES_NO | wxICON_WARNING);
|
|
||||||
return dialog.ShowModal() == wxID_YES;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool granted = false;
|
|
||||||
if (wxIsMainThread()) {
|
|
||||||
granted = show_dialog();
|
|
||||||
} else {
|
|
||||||
auto result = std::make_shared<std::promise<bool>>();
|
|
||||||
auto future = result->get_future();
|
|
||||||
GUI::wxGetApp().CallAfter([result, descriptor_name = descriptor.name, missing]() {
|
|
||||||
wxString requested_paths;
|
|
||||||
for (const std::string& path : missing)
|
|
||||||
requested_paths += wxString::FromUTF8(path.c_str()) + "\n";
|
|
||||||
|
|
||||||
wxMessageDialog dialog(
|
|
||||||
nullptr,
|
|
||||||
wxString::Format("Plugin \"%s\" requests filesystem read access to:\n%s",
|
|
||||||
wxString::FromUTF8(descriptor_name.c_str()), requested_paths),
|
|
||||||
"Plugin permissions",
|
|
||||||
wxYES_NO | wxICON_WARNING);
|
|
||||||
result->set_value(dialog.ShowModal() == wxID_YES);
|
|
||||||
});
|
|
||||||
granted = future.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!granted)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (state.plugin_name.empty()) {
|
|
||||||
state.installed_from = descriptor.is_cloud_plugin() ? "cloud" : "local";
|
|
||||||
state.installed_version = !descriptor.installed_version.empty() ? descriptor.installed_version : descriptor.version;
|
|
||||||
state.plugin_name = descriptor.name;
|
|
||||||
state.cloud_uuid = descriptor.cloud_uuid();
|
|
||||||
state.enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.permissions.fs_read.insert(state.permissions.fs_read.end(), missing.begin(), missing.end());
|
|
||||||
return write_install_state(boost::filesystem::path(descriptor.plugin_root), state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PluginAuditManager::report_violation(const AuditViolation& violation)
|
void PluginAuditManager::report_violation(const AuditViolation& violation)
|
||||||
{
|
{
|
||||||
m_last_violation = violation;
|
m_last_violation = violation;
|
||||||
m_has_last_violation = true;
|
m_has_last_violation = true;
|
||||||
m_audit_denial_pending = true;
|
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << "[AUDIT BLOCKED] plugin=" << violation.plugin_key << " event=" << violation.event_name
|
BOOST_LOG_TRIVIAL(warning) << "[AUDIT BLOCKED] plugin=" << violation.plugin_key << " event=" << violation.event_name
|
||||||
<< " path=" << violation.path.string() << " reason=" << violation.reason;
|
<< " path=" << violation.path.string() << " reason=" << violation.reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PluginAuditManager::audit_denial_pending() const { return m_audit_denial_pending; }
|
|
||||||
|
|
||||||
void PluginAuditManager::clear_audit_denial() { m_audit_denial_pending = false; }
|
|
||||||
|
|
||||||
void PluginAuditManager::clear_last_violation()
|
void PluginAuditManager::clear_last_violation()
|
||||||
{
|
{
|
||||||
m_has_last_violation = false;
|
m_has_last_violation = false;
|
||||||
@@ -370,106 +301,27 @@ bool PluginAuditManager::last_violation(AuditViolation& violation) const
|
|||||||
// The C-level audit hook
|
// The C-level audit hook
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace PluginAuditDetail {
|
namespace {
|
||||||
|
|
||||||
PyObject* tuple_item(PyObject* args, Py_ssize_t index)
|
// Records a blocked event and raises PermissionError in the calling interpreter. Returns -1
|
||||||
{
|
// so an event branch can `return report_denied(...)` directly.
|
||||||
if (!args || !PyTuple_Check(args) || index < 0 || index >= PyTuple_GET_SIZE(args))
|
|
||||||
return nullptr;
|
|
||||||
return PyTuple_GET_ITEM(args, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string python_path(PyObject* object)
|
|
||||||
{
|
|
||||||
if (!object)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
PyObject* path_object = PyOS_FSPath(object);
|
|
||||||
if (!path_object) {
|
|
||||||
PyErr_Clear();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* path = PyUnicode_Check(path_object) ? PyUnicode_AsUTF8(path_object) : PyBytes_AsString(path_object);
|
|
||||||
std::string result = path ? path : "";
|
|
||||||
Py_DECREF(path_object);
|
|
||||||
if (!path)
|
|
||||||
PyErr_Clear();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void add_filesystem_path(std::vector<boost::filesystem::path>& paths, PyObject* object)
|
|
||||||
{
|
|
||||||
const std::string path = python_path(object);
|
|
||||||
if (!path.empty())
|
|
||||||
paths.emplace_back(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<boost::filesystem::path> filesystem_paths(const std::string& event_name, PyObject* args)
|
|
||||||
{
|
|
||||||
std::vector<boost::filesystem::path> paths;
|
|
||||||
|
|
||||||
if (event_name == "open") {
|
|
||||||
add_filesystem_path(paths, tuple_item(args, 0));
|
|
||||||
} else if (event_name == "os.rename") {
|
|
||||||
add_filesystem_path(paths, tuple_item(args, 0));
|
|
||||||
add_filesystem_path(paths, tuple_item(args, 1));
|
|
||||||
} else if (event_name == "os.remove") {
|
|
||||||
add_filesystem_path(paths, tuple_item(args, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool has_filesystem_permission(const PluginInstallState& state, const boost::filesystem::path& path)
|
|
||||||
{
|
|
||||||
return std::find(state.permissions.fs_read.begin(), state.permissions.fs_read.end(), path.string()) !=
|
|
||||||
state.permissions.fs_read.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool persist_filesystem_permission(const std::string& plugin_key,
|
|
||||||
PluginInstallState& state,
|
|
||||||
const boost::filesystem::path& path)
|
|
||||||
{
|
|
||||||
PluginDescriptor descriptor;
|
|
||||||
if (!PluginManager::instance().try_get_plugin_descriptor(plugin_key, descriptor) || descriptor.plugin_root.empty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// A sandbox plugin may not have a sidecar yet. Seed the small amount of install metadata
|
|
||||||
// needed by the writer so clicking Yes still creates the JSON file.
|
|
||||||
if (state.plugin_name.empty()) {
|
|
||||||
state.installed_from = descriptor.is_cloud_plugin() ? "cloud" : "local";
|
|
||||||
state.installed_version = !descriptor.installed_version.empty() ? descriptor.installed_version : descriptor.version;
|
|
||||||
state.plugin_name = descriptor.name;
|
|
||||||
state.cloud_uuid = descriptor.cloud_uuid();
|
|
||||||
state.enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string path_string = path.string();
|
|
||||||
if (std::find(state.permissions.fs_read.begin(), state.permissions.fs_read.end(), path_string) ==
|
|
||||||
state.permissions.fs_read.end())
|
|
||||||
state.permissions.fs_read.push_back(path_string);
|
|
||||||
return write_install_state(boost::filesystem::path(descriptor.plugin_root), state);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Records a blocked event and raises PermissionError in the calling interpreter.
|
|
||||||
int report_denied(PluginAuditManager& mgr,
|
int report_denied(PluginAuditManager& mgr,
|
||||||
const std::string& event_name,
|
const std::string& event_name,
|
||||||
const boost::filesystem::path& target,
|
const boost::filesystem::path& path,
|
||||||
const AuditDecision& decision)
|
const AuditDecision& decision)
|
||||||
{
|
{
|
||||||
AuditViolation violation;
|
AuditViolation violation;
|
||||||
violation.plugin_key = mgr.current_plugin();
|
violation.plugin_key = mgr.current_plugin();
|
||||||
violation.event_name = event_name;
|
violation.event_name = event_name;
|
||||||
violation.path = target;
|
violation.path = path;
|
||||||
violation.reason = decision.reason;
|
violation.reason = decision.reason;
|
||||||
mgr.report_violation(violation);
|
mgr.report_violation(violation);
|
||||||
|
|
||||||
PyErr_SetString(PyExc_PermissionError, "Plugin attempted an audited operation without permission");
|
PyErr_SetString(PyExc_PermissionError, "Plugin attempted to access a blocked file path");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace PluginAuditDetail
|
} // namespace
|
||||||
|
|
||||||
int PluginAuditManager::audit_hook(const char* event, PyObject* args, void* user_data)
|
int PluginAuditManager::audit_hook(const char* event, PyObject* args, void* user_data)
|
||||||
{
|
{
|
||||||
@@ -484,53 +336,73 @@ int PluginAuditManager::audit_hook(const char* event, PyObject* args, void* user
|
|||||||
BOOST_LOG_TRIVIAL(debug) << "[AUDIT EVENT] " << event_name;
|
BOOST_LOG_TRIVIAL(debug) << "[AUDIT EVENT] " << event_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mgr->current_plugin().empty())
|
// extensive list of audit events can be found at https://docs.python.org/3/library/audit_events.html
|
||||||
return 0;
|
|
||||||
|
|
||||||
PluginInstallState state;
|
// --- open event ---
|
||||||
const bool have_install_state = PluginManager::instance().get_install_state(mgr->current_plugin(), state);
|
if (event_name == "open") {
|
||||||
if (!have_install_state) {
|
const char* path_cstr = nullptr;
|
||||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " Failed to get install state for " << mgr->current_plugin();
|
const char* mode_cstr = nullptr;
|
||||||
|
int flags = 0;
|
||||||
|
|
||||||
|
// open(path, mode, flags) — path may be str, bytes, or int fd
|
||||||
|
if (!PyArg_ParseTuple(args, "s|si", &path_cstr, &mode_cstr, &flags)) {
|
||||||
|
PyErr_Clear(); // couldn't parse; allow
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string path_str(path_cstr ? path_cstr : "");
|
||||||
|
std::string mode_str(mode_cstr ? mode_cstr : "r");
|
||||||
|
|
||||||
|
AuditDecision decision = mgr->check_open(path_str, mode_str);
|
||||||
|
if (!decision.allowed)
|
||||||
|
return report_denied(*mgr, event_name, path_str, decision);
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string plugin_name = state.plugin_name.empty() ? mgr->current_plugin() : state.plugin_name;
|
// --- os.rename event (raised by os.rename and os.replace) ---
|
||||||
const std::vector<boost::filesystem::path> paths = PluginAuditDetail::filesystem_paths(event_name, args);
|
if (event_name == "os.rename") {
|
||||||
for (const auto& path : paths) {
|
const char* src_cstr = nullptr;
|
||||||
if (PluginAuditDetail::has_filesystem_permission(state, path))
|
const char* dst_cstr = nullptr;
|
||||||
continue;
|
PyObject* src_dir_fd = nullptr;
|
||||||
|
PyObject* dst_dir_fd = nullptr;
|
||||||
|
|
||||||
wxMessageDialog dialog(
|
// os.rename(src, dst, src_dir_fd, dst_dir_fd) — paths may be str, bytes, or int fd.
|
||||||
nullptr,
|
// The dir_fd arguments are unused, but must be accepted for the tuple to parse.
|
||||||
wxString::Format(_L("Plugin \"%s\" is requesting filesystem access to:\n%s"),
|
if (!PyArg_ParseTuple(args, "ss|OO", &src_cstr, &dst_cstr, &src_dir_fd, &dst_dir_fd)) {
|
||||||
wxString::FromUTF8(plugin_name.c_str()),
|
PyErr_Clear(); // couldn't parse; allow
|
||||||
wxString::FromUTF8(path.string().c_str())),
|
return 0;
|
||||||
_L("Plugin permission request"),
|
}
|
||||||
wxYES_NO | wxICON_WARNING);
|
|
||||||
if (dialog.ShowModal() == wxID_YES &&
|
|
||||||
PluginAuditDetail::persist_filesystem_permission(mgr->current_plugin(), state, path))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
return PluginAuditDetail::report_denied(
|
// A rename writes at both ends, so either end being denied blocks the call.
|
||||||
*mgr, event_name, path, {false, "filesystem permission required"});
|
for (const char* path_cstr : {src_cstr, dst_cstr}) {
|
||||||
|
std::string path_str(path_cstr ? path_cstr : "");
|
||||||
|
AuditDecision decision = mgr->check_path_access(path_str, /* is_write */ true);
|
||||||
|
if (!decision.allowed)
|
||||||
|
return report_denied(*mgr, event_name, path_str, decision);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!paths.empty())
|
// --- os.remove event (raised by os.remove and os.unlink) ---
|
||||||
return 0;
|
if (event_name == "os.remove") {
|
||||||
|
const char* path_cstr = nullptr;
|
||||||
|
PyObject* dir_fd = nullptr;
|
||||||
|
|
||||||
wxMessageDialog dialog(
|
// os.remove(path, dir_fd) — path may be str, bytes, or int fd
|
||||||
nullptr,
|
if (!PyArg_ParseTuple(args, "s|O", &path_cstr, &dir_fd)) {
|
||||||
wxString::Format(
|
PyErr_Clear(); // couldn't parse; allow
|
||||||
_L("Plugin \"%s\" is requesting permission for the Python audit event \"%s\".\n\n"
|
return 0;
|
||||||
"This operation does not expose a filesystem path to the audit hook."),
|
}
|
||||||
wxString::FromUTF8(plugin_name.c_str()),
|
|
||||||
wxString::FromUTF8(event_name.c_str())),
|
|
||||||
_L("Plugin permission request"),
|
|
||||||
wxYES_NO | wxICON_WARNING);
|
|
||||||
if (dialog.ShowModal() == wxID_YES)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return PluginAuditDetail::report_denied(
|
std::string path_str(path_cstr ? path_cstr : "");
|
||||||
*mgr, event_name, boost::filesystem::path(), {false, "audit permission required"});
|
AuditDecision decision = mgr->check_path_access(path_str, /* is_write */ true);
|
||||||
|
if (!decision.allowed)
|
||||||
|
return report_denied(*mgr, event_name, path_str, decision);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown event — allow by default
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PluginAuditManager::install_hook()
|
void PluginAuditManager::install_hook()
|
||||||
@@ -541,10 +413,10 @@ void PluginAuditManager::install_hook()
|
|||||||
}
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << "[AUDIT] CPython audit hook installed successfully";
|
BOOST_LOG_TRIVIAL(info) << "[AUDIT] CPython audit hook installed successfully";
|
||||||
|
|
||||||
// data_dir() is the only globally-allowed root during plugin execution.
|
// data_dir() is the only globally-allowed root during enforced plugin execution.
|
||||||
// The executable directory and resources directory are intentionally NOT allowed
|
// The executable directory and resources directory are intentionally NOT allowed
|
||||||
// here: plugins must not access outside data_dir() (G-code plugins additionally get
|
// here: plugins must not write outside data_dir() (G-code plugins additionally get
|
||||||
// the temp G-code folder via a scoped root).
|
// the temp G-code folder via a scoped root). Reads remain permissive in Loading mode.
|
||||||
add_global_allowed_root(data_dir());
|
add_global_allowed_root(data_dir());
|
||||||
|
|
||||||
// The user's app config and cloud credentials live directly inside data_dir(), so the
|
// The user's app config and cloud credentials live directly inside data_dir(), so the
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
#define slic3r_PluginAuditManager_hpp_
|
#define slic3r_PluginAuditManager_hpp_
|
||||||
|
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -54,8 +53,8 @@ public:
|
|||||||
void add_scoped_allowed_root(const boost::filesystem::path& root);
|
void add_scoped_allowed_root(const boost::filesystem::path& root);
|
||||||
|
|
||||||
// --- denied-filenames registry ---
|
// --- denied-filenames registry ---
|
||||||
// Filenames a plugin may never touch, in any directory, regardless of the enclosing allowed
|
// Filenames a plugin may never touch, in any directory, regardless of audit mode or
|
||||||
// root. A candidate is denied when its filename starts with a
|
// enclosing allowed root. A candidate is denied when its filename starts with a
|
||||||
// registered name, so .bak/.tmp companions are covered by the same entry.
|
// registered name, so .bak/.tmp companions are covered by the same entry.
|
||||||
//
|
//
|
||||||
// The comparison is case-insensitive on every platform, unlike the _WIN32-only iequals
|
// The comparison is case-insensitive on every platform, unlike the _WIN32-only iequals
|
||||||
@@ -74,23 +73,30 @@ public:
|
|||||||
// 8.3 short name is out of scope (see the design doc). This blocks direct access only.
|
// 8.3 short name is out of scope (see the design doc). This blocks direct access only.
|
||||||
bool is_denied_filename(const boost::filesystem::path& candidate) const;
|
bool is_denied_filename(const boost::filesystem::path& candidate) const;
|
||||||
|
|
||||||
|
// --- enforcement mode ---
|
||||||
|
enum class AuditMode {
|
||||||
|
// Import/loading phase: allow reads anywhere, only block writes
|
||||||
|
// outside allowed roots. Python needs to read stdlib modules
|
||||||
|
// during import and those are not inside plugin directories.
|
||||||
|
Loading,
|
||||||
|
|
||||||
|
// Execution phase: block both reads and writes outside allowed
|
||||||
|
// roots, plus subprocess/socket/ctypes.
|
||||||
|
Enforcing,
|
||||||
|
};
|
||||||
|
|
||||||
|
void set_audit_mode(AuditMode mode);
|
||||||
|
AuditMode audit_mode() const;
|
||||||
|
|
||||||
// --- policy checks ---
|
// --- policy checks ---
|
||||||
// Shared core for every audited filesystem event. The deny list is consulted above the
|
// Shared core for every audited filesystem event. The deny list is consulted above the
|
||||||
// allowed roots, so a denied filename is blocked even when the file sits inside data_dir(),
|
// Loading-mode read exemption and above the allowed roots, so a denied filename is
|
||||||
// which is itself a global allowed root.
|
// blocked even though every scope currently runs in Loading and the files in question
|
||||||
|
// sit inside data_dir(), which is itself a global allowed root.
|
||||||
AuditDecision check_path_access(const boost::filesystem::path& candidate, bool is_write);
|
AuditDecision check_path_access(const boost::filesystem::path& candidate, bool is_write);
|
||||||
AuditDecision check_open(const std::string& path, const std::string& mode);
|
AuditDecision check_open(const std::string& path, const std::string& mode);
|
||||||
|
|
||||||
// Ask the user to grant the requested filesystem-read paths. The request may originate on a
|
|
||||||
// plugin load worker, so the implementation marshals the modal dialog to the wx main thread.
|
|
||||||
// Returns true only when every missing path was granted and persisted; denial aborts the plugin
|
|
||||||
// load without adding a permission.
|
|
||||||
bool request_filesystem_read_permissions(const std::string& plugin_key,
|
|
||||||
const std::vector<std::string>& paths);
|
|
||||||
|
|
||||||
void report_violation(const AuditViolation& violation);
|
void report_violation(const AuditViolation& violation);
|
||||||
bool audit_denial_pending() const;
|
|
||||||
void clear_audit_denial();
|
|
||||||
void clear_last_violation();
|
void clear_last_violation();
|
||||||
bool last_violation(AuditViolation& violation) const;
|
bool last_violation(AuditViolation& violation) const;
|
||||||
|
|
||||||
@@ -103,12 +109,12 @@ private:
|
|||||||
|
|
||||||
static int audit_hook(const char* event, PyObject* args, void* user_data);
|
static int audit_hook(const char* event, PyObject* args, void* user_data);
|
||||||
|
|
||||||
static thread_local std::string m_current_plugin_key;
|
static thread_local std::string m_current_plugin_key;
|
||||||
static thread_local std::string m_current_capability_name;
|
static thread_local std::string m_current_capability_name;
|
||||||
|
static thread_local AuditMode m_audit_mode;
|
||||||
static thread_local std::vector<boost::filesystem::path> m_scoped_allowed_roots;
|
static thread_local std::vector<boost::filesystem::path> m_scoped_allowed_roots;
|
||||||
static thread_local bool m_audit_denial_pending;
|
static thread_local bool m_has_last_violation;
|
||||||
static thread_local bool m_has_last_violation;
|
static thread_local AuditViolation m_last_violation;
|
||||||
static thread_local AuditViolation m_last_violation;
|
|
||||||
|
|
||||||
// mutable: is_denied_filename() is a const query that must lock.
|
// mutable: is_denied_filename() is a const query that must lock.
|
||||||
mutable std::mutex m_mutex;
|
mutable std::mutex m_mutex;
|
||||||
@@ -124,7 +130,8 @@ class ScopedPluginAuditContext
|
|||||||
public:
|
public:
|
||||||
explicit ScopedPluginAuditContext(
|
explicit ScopedPluginAuditContext(
|
||||||
const std::string& plugin_key,
|
const std::string& plugin_key,
|
||||||
const std::string& capability_name = {});
|
const std::string& capability_name = {},
|
||||||
|
PluginAuditManager::AuditMode mode = PluginAuditManager::AuditMode::Loading);
|
||||||
|
|
||||||
~ScopedPluginAuditContext();
|
~ScopedPluginAuditContext();
|
||||||
|
|
||||||
@@ -134,6 +141,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
std::string m_previous_id;
|
std::string m_previous_id;
|
||||||
std::string m_previous_capability;
|
std::string m_previous_capability;
|
||||||
|
PluginAuditManager::AuditMode m_previous_mode;
|
||||||
std::vector<boost::filesystem::path> m_previous_scoped_roots;
|
std::vector<boost::filesystem::path> m_previous_scoped_roots;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -785,23 +785,6 @@ bool read_install_state(const boost::filesystem::path& plugin_dir, PluginInstall
|
|||||||
parsed.plugin_name = state["plugin_name"].get<std::string>();
|
parsed.plugin_name = state["plugin_name"].get<std::string>();
|
||||||
if (state.contains("cloud_uuid") && state["cloud_uuid"].is_string())
|
if (state.contains("cloud_uuid") && state["cloud_uuid"].is_string())
|
||||||
parsed.cloud_uuid = state["cloud_uuid"].get<std::string>();
|
parsed.cloud_uuid = state["cloud_uuid"].get<std::string>();
|
||||||
|
|
||||||
if (state.contains("permissions") && state["permissions"].is_object()) {
|
|
||||||
const auto& permissions = state["permissions"];
|
|
||||||
if (permissions.contains("networking") && permissions["networking"].is_array())
|
|
||||||
for (const auto& host : permissions["networking"])
|
|
||||||
if (host.is_string())
|
|
||||||
parsed.permissions.networking.push_back(host.get<std::string>());
|
|
||||||
if (permissions.contains("fs_read") && permissions["fs_read"].is_array())
|
|
||||||
for (const auto& path : permissions["fs_read"])
|
|
||||||
if (path.is_string())
|
|
||||||
parsed.permissions.fs_read.push_back(path.get<std::string>());
|
|
||||||
if (permissions.contains("fs_write") && permissions["fs_write"].is_array())
|
|
||||||
for (const auto& path : permissions["fs_write"])
|
|
||||||
if (path.is_string())
|
|
||||||
parsed.permissions.fs_write.push_back(path.get<std::string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.contains("enabled") && state["enabled"].is_boolean())
|
if (state.contains("enabled") && state["enabled"].is_boolean())
|
||||||
parsed.enabled = state["enabled"].get<bool>();
|
parsed.enabled = state["enabled"].get<bool>();
|
||||||
|
|
||||||
@@ -837,12 +820,6 @@ bool write_install_state(const boost::filesystem::path& plugin_dir, const Plugin
|
|||||||
if (!state.cloud_uuid.empty())
|
if (!state.cloud_uuid.empty())
|
||||||
json["cloud_uuid"] = state.cloud_uuid;
|
json["cloud_uuid"] = state.cloud_uuid;
|
||||||
|
|
||||||
json["permissions"] = {
|
|
||||||
{"networking", state.permissions.networking},
|
|
||||||
{"fs_read", state.permissions.fs_read},
|
|
||||||
{"fs_write", state.permissions.fs_write},
|
|
||||||
};
|
|
||||||
|
|
||||||
nlohmann::json capabilities = nlohmann::json::array();
|
nlohmann::json capabilities = nlohmann::json::array();
|
||||||
for (const auto& [name, enabled] : state.capabilities)
|
for (const auto& [name, enabled] : state.capabilities)
|
||||||
capabilities.push_back(nlohmann::json{{name, enabled}});
|
capabilities.push_back(nlohmann::json{{name, enabled}});
|
||||||
@@ -860,9 +837,6 @@ bool write_install_state(const boost::filesystem::path& plugin_dir, const Plugin
|
|||||||
const std::vector<std::pair<std::string, bool>>& capabilities)
|
const std::vector<std::pair<std::string, bool>>& capabilities)
|
||||||
{
|
{
|
||||||
PluginInstallState state;
|
PluginInstallState state;
|
||||||
// Loading a plugin updates its lifecycle/capability state, but must retain permissions granted
|
|
||||||
// during register_capabilities() or by a previous runtime audit prompt.
|
|
||||||
read_install_state(plugin_dir, state);
|
|
||||||
state.installed_from = entry.is_cloud_plugin() ? "cloud" : "local";
|
state.installed_from = entry.is_cloud_plugin() ? "cloud" : "local";
|
||||||
// Prefer the descriptor's recorded installed_version (the version fetched from the cloud
|
// Prefer the descriptor's recorded installed_version (the version fetched from the cloud
|
||||||
// at install time, preserved across sidecar re-writes) so a stale manifest/PEP723 header
|
// at install time, preserved across sidecar re-writes) so a stale manifest/PEP723 header
|
||||||
@@ -879,16 +853,10 @@ bool write_install_state(const boost::filesystem::path& plugin_dir, const Plugin
|
|||||||
bool write_install_state(const boost::filesystem::path& plugin_dir, const PluginDescriptor& entry)
|
bool write_install_state(const boost::filesystem::path& plugin_dir, const PluginDescriptor& entry)
|
||||||
{
|
{
|
||||||
// Install-time writer: the package is not loaded, so its capabilities are not known yet and the
|
// Install-time writer: the package is not loaded, so its capabilities are not known yet and the
|
||||||
// sidecar is (re)initialized to "auto-load, nothing disabled". This intentionally resets
|
// sidecar is (re)initialized to "auto-load, nothing disabled". PluginManager writes the real
|
||||||
// permissions on a fresh install/reinstall. PluginManager writes the real per-capability flags
|
// per-capability flags once the package is loaded, via the (dir, entry, enabled, capabilities)
|
||||||
// while preserving permissions once the package is loaded, via the overload above.
|
// overload.
|
||||||
PluginInstallState state;
|
return write_install_state(plugin_dir, entry, true, {});
|
||||||
state.installed_from = entry.is_cloud_plugin() ? "cloud" : "local";
|
|
||||||
state.installed_version = !entry.installed_version.empty() ? entry.installed_version : entry.version;
|
|
||||||
state.plugin_name = entry.name;
|
|
||||||
state.cloud_uuid = entry.cloud_uuid();
|
|
||||||
state.enabled = true;
|
|
||||||
return write_install_state(plugin_dir, state);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool read_python_plugin_metadata(const boost::filesystem::path& py_path, PluginDescriptor& descriptor, std::string& error)
|
bool read_python_plugin_metadata(const boost::filesystem::path& py_path, PluginDescriptor& descriptor, std::string& error)
|
||||||
|
|||||||
@@ -81,21 +81,11 @@ inline nlohmann::json py_to_json(const pybind11::handle& o)
|
|||||||
return py::str(o).cast<std::string>(); // fallback: str()
|
return py::str(o).cast<std::string>(); // fallback: str()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PluginPermissions
|
|
||||||
{
|
|
||||||
std::vector<std::string> networking;
|
|
||||||
std::vector<std::string> fs_read;
|
|
||||||
std::vector<std::string> fs_write;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PluginInstallState {
|
struct PluginInstallState {
|
||||||
std::string installed_from; // "local" | "cloud"
|
std::string installed_from; // "local" | "cloud"
|
||||||
std::string installed_version;
|
std::string installed_version;
|
||||||
std::string plugin_name;
|
std::string plugin_name;
|
||||||
std::string cloud_uuid; // empty for local
|
std::string cloud_uuid; // empty for local
|
||||||
|
|
||||||
PluginPermissions permissions;
|
|
||||||
|
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
std::vector<std::pair<std::string, bool>> capabilities; // name -> enabled, ordered
|
std::vector<std::pair<std::string, bool>> capabilities; // name -> enabled, ordered
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ bool load(const PluginDescriptor& descriptor,
|
|||||||
// (while the active plugin key is set), then instantiates each registered capability and caches
|
// (while the active plugin key is set), then instantiates each registered capability and caches
|
||||||
// its get_name(). Returns one entry per capability.
|
// its get_name(). Returns one entry per capability.
|
||||||
std::string bridge_error;
|
std::string bridge_error;
|
||||||
auto capabilities_found = bridge.finalize_plugin_capture(descriptor.entry_path, descriptor.plugin_key, bridge_error);
|
auto capabilities_found = bridge.finalize_plugin_capture(descriptor.entry_path, bridge_error);
|
||||||
if (!bridge_error.empty()) {
|
if (!bridge_error.empty()) {
|
||||||
capabilities_found.clear();
|
capabilities_found.clear();
|
||||||
error = "Plugin registration failed: " + bridge_error;
|
error = "Plugin registration failed: " + bridge_error;
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <slic3r/plugin/PluginConfig.hpp>
|
#include <slic3r/plugin/PluginConfig.hpp>
|
||||||
#include <slic3r/plugin/PluginDescriptor.hpp>
|
|
||||||
#include <slic3r/plugin/PluginLoader.hpp>
|
#include <slic3r/plugin/PluginLoader.hpp>
|
||||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||||
#include <slic3r/plugin/pluginTypes/script/ScriptPluginCapability.hpp>
|
#include <slic3r/plugin/pluginTypes/script/ScriptPluginCapability.hpp>
|
||||||
@@ -555,20 +554,6 @@ std::shared_ptr<PluginCapabilityInterface> PluginManager::get_plugin_capability(
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PluginManager::get_install_state(const std::string& plugin_key, PluginInstallState& install_state)
|
|
||||||
{
|
|
||||||
PluginDescriptor descriptor;
|
|
||||||
if (!try_get_plugin_descriptor(plugin_key, descriptor)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!read_install_state(boost::filesystem::path(descriptor.plugin_root), install_state)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Lifecycle ───────────────────────────────────────────────────────────────────────────────
|
// ── Lifecycle ───────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
bool PluginManager::is_plugin_loaded(const std::string& plugin_key) const
|
bool PluginManager::is_plugin_loaded(const std::string& plugin_key) const
|
||||||
@@ -857,8 +842,6 @@ void PluginManager::load_plugin_impl(const std::string& plugin_key, bool skip_de
|
|||||||
if (!plugin_loader::load(descriptor, skip_deps, capabilities_to_enable, registry_precheck, plugin, error)) {
|
if (!plugin_loader::load(descriptor, skip_deps, capabilities_to_enable, registry_precheck, plugin, error)) {
|
||||||
if (error == LOAD_CANCELLED)
|
if (error == LOAD_CANCELLED)
|
||||||
return; // cancelled: nothing materialized survives, and no error is recorded
|
return; // cancelled: nothing materialized survives, and no error is recorded
|
||||||
if (error.rfind("Plugin registration failed:", 0) == 0)
|
|
||||||
mark_plugin_install_state_disabled(plugin_key);
|
|
||||||
fail(std::move(error));
|
fail(std::move(error));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1084,31 +1067,6 @@ void PluginManager::write_loaded_plugin_install_state(const std::string& plugin_
|
|||||||
write_install_state(boost::filesystem::path(descriptor.plugin_root), descriptor, /*enabled=*/true, capabilities);
|
write_install_state(boost::filesystem::path(descriptor.plugin_root), descriptor, /*enabled=*/true, capabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PluginManager::mark_plugin_install_state_disabled(const std::string& plugin_key)
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> state_lock(m_install_state_mutex);
|
|
||||||
|
|
||||||
PluginDescriptor descriptor;
|
|
||||||
if (!try_get_plugin_descriptor(plugin_key, descriptor) || descriptor.plugin_root.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const boost::filesystem::path root(descriptor.plugin_root);
|
|
||||||
PluginInstallState state;
|
|
||||||
if (!read_install_state(root, state)) {
|
|
||||||
state.installed_from = descriptor.is_cloud_plugin() ? "cloud" : "local";
|
|
||||||
state.installed_version = !descriptor.installed_version.empty() ? descriptor.installed_version : descriptor.version;
|
|
||||||
state.plugin_name = descriptor.name;
|
|
||||||
state.cloud_uuid = descriptor.cloud_uuid();
|
|
||||||
}
|
|
||||||
state.enabled = false;
|
|
||||||
if (!write_install_state(root, state))
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(m_mutex);
|
|
||||||
if (Plugin* plugin = find_plugin_locked(plugin_key))
|
|
||||||
plugin->descriptor.enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Callbacks ───────────────────────────────────────────────────────────────────────────────
|
// ── Callbacks ───────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
void PluginManager::subscribe_on_load_callback(PluginLifecycleCompleteFn fn)
|
void PluginManager::subscribe_on_load_callback(PluginLifecycleCompleteFn fn)
|
||||||
|
|||||||
@@ -150,8 +150,6 @@ public:
|
|||||||
PluginCapabilityType type = PluginCapabilityType::Unknown,
|
PluginCapabilityType type = PluginCapabilityType::Unknown,
|
||||||
bool only_enabled = true) const;
|
bool only_enabled = true) const;
|
||||||
|
|
||||||
bool get_install_state(const std::string& plugin_key, PluginInstallState& install_state);
|
|
||||||
|
|
||||||
void load_plugin(const std::string& plugin_key, bool skip_deps = false, std::vector<std::string> capabilities_to_enable = {});
|
void load_plugin(const std::string& plugin_key, bool skip_deps = false, std::vector<std::string> capabilities_to_enable = {});
|
||||||
bool unload_plugin(const std::string& plugin_key);
|
bool unload_plugin(const std::string& plugin_key);
|
||||||
void unload_all_plugins();
|
void unload_all_plugins();
|
||||||
@@ -248,7 +246,6 @@ private:
|
|||||||
|
|
||||||
// Writes the sidecar for a loaded plugin (enabled=true plus the current per-capability flags).
|
// Writes the sidecar for a loaded plugin (enabled=true plus the current per-capability flags).
|
||||||
void write_loaded_plugin_install_state(const std::string& plugin_key);
|
void write_loaded_plugin_install_state(const std::string& plugin_key);
|
||||||
void mark_plugin_install_state_disabled(const std::string& plugin_key);
|
|
||||||
|
|
||||||
bool finalize_cloud_plugin_removal(const PluginDescriptor& plugin, bool keep_local, std::string& error);
|
bool finalize_cloud_plugin_removal(const PluginDescriptor& plugin, bool keep_local, std::string& error);
|
||||||
bool delete_installed_plugin_package(const PluginDescriptor& plugin, std::string& error);
|
bool delete_installed_plugin_package(const PluginDescriptor& plugin, std::string& error);
|
||||||
|
|||||||
@@ -26,30 +26,25 @@
|
|||||||
try { \
|
try { \
|
||||||
override_call; \
|
override_call; \
|
||||||
} catch (pybind11::error_already_set & err) { \
|
} catch (pybind11::error_already_set & err) { \
|
||||||
const bool _orca_audit_denial = ::Slic3r::PluginAuditManager::instance().audit_denial_pending(); \
|
|
||||||
if (_orca_audit_denial) \
|
|
||||||
::Slic3r::PluginAuditManager::instance().clear_current_plugin(); \
|
|
||||||
::Slic3r::log_python_exception_keep(err); \
|
::Slic3r::log_python_exception_keep(err); \
|
||||||
if (_orca_audit_denial) \
|
|
||||||
::Slic3r::PluginAuditManager::instance().clear_audit_denial(); \
|
|
||||||
throw; \
|
throw; \
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opens the plugin's filesystem audit scope for the duration of a C++ -> Python call, and publishes
|
// Opens the plugin's filesystem audit scope for the duration of a C++ -> Python call, and publishes
|
||||||
// the calling capability's cached name so host APIs invoked from Python can tell which capability
|
// the calling capability's cached name so host APIs invoked from Python can tell which capability
|
||||||
// they are serving. No-op without an audit plugin key. Declares a local `_orca_audit_scope`.
|
// they are serving. No-op without an audit plugin key. Declares a local `_orca_audit_scope`.
|
||||||
#define ORCA_PY_AUDIT_SCOPE() \
|
#define ORCA_PY_AUDIT_SCOPE(mode) \
|
||||||
std::optional<::Slic3r::ScopedPluginAuditContext> _orca_audit_scope; \
|
std::optional<::Slic3r::ScopedPluginAuditContext> _orca_audit_scope; \
|
||||||
if (const std::string& _orca_audit_key = this->audit_plugin_key(); !_orca_audit_key.empty()) \
|
if (const std::string& _orca_audit_key = this->audit_plugin_key(); !_orca_audit_key.empty()) \
|
||||||
_orca_audit_scope.emplace(_orca_audit_key, this->name())
|
_orca_audit_scope.emplace(_orca_audit_key, this->name(), mode)
|
||||||
|
|
||||||
#define ORCA_PY_OVERRIDE_AUDITED(audit_setup, override_macro, ret, base, name, ...) \
|
#define ORCA_PY_OVERRIDE_AUDITED(mode, audit_setup, override_macro, ret, base, name, ...) \
|
||||||
do { \
|
do { \
|
||||||
::Slic3r::PluginCapabilityInterface::RefCounter _orca_ref_counter(*this); \
|
::Slic3r::PluginCapabilityInterface::RefCounter _orca_ref_counter(*this); \
|
||||||
::Slic3r::PythonGILState _orca_python_gil; \
|
::Slic3r::PythonGILState _orca_python_gil; \
|
||||||
if (!_orca_python_gil) \
|
if (!_orca_python_gil) \
|
||||||
throw std::runtime_error("Python interpreter is shutting down"); \
|
throw std::runtime_error("Python interpreter is shutting down"); \
|
||||||
ORCA_PY_AUDIT_SCOPE(); \
|
ORCA_PY_AUDIT_SCOPE(mode); \
|
||||||
if (_orca_audit_scope) \
|
if (_orca_audit_scope) \
|
||||||
audit_setup(); \
|
audit_setup(); \
|
||||||
ORCA_PY_LOGGED_OVERRIDE_BODY(override_macro(ret, base, name, ##__VA_ARGS__)); \
|
ORCA_PY_LOGGED_OVERRIDE_BODY(override_macro(ret, base, name, ##__VA_ARGS__)); \
|
||||||
@@ -63,7 +58,7 @@ public:
|
|||||||
|
|
||||||
std::string get_name() const override
|
std::string get_name() const override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE_PURE, std::string, Base, get_name);
|
ORCA_PY_OVERRIDE_AUDITED(::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, std::string, Base, get_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config UI hooks. Available on every capability type, so they live here rather than in
|
// Config UI hooks. Available on every capability type, so they live here rather than in
|
||||||
@@ -71,6 +66,7 @@ public:
|
|||||||
bool has_config_ui() const override
|
bool has_config_ui() const override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
|
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||||
[] {},
|
[] {},
|
||||||
PYBIND11_OVERRIDE,
|
PYBIND11_OVERRIDE,
|
||||||
bool,
|
bool,
|
||||||
@@ -81,6 +77,7 @@ public:
|
|||||||
std::string get_config_ui() const override
|
std::string get_config_ui() const override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
|
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||||
[] {},
|
[] {},
|
||||||
PYBIND11_OVERRIDE,
|
PYBIND11_OVERRIDE,
|
||||||
std::string,
|
std::string,
|
||||||
@@ -97,7 +94,7 @@ public:
|
|||||||
// mistake), both fall back to the base's empty object rather than writing `"cap_config": null`.
|
// mistake), both fall back to the base's empty object rather than writing `"cap_config": null`.
|
||||||
nlohmann::json get_default_config() const override
|
nlohmann::json get_default_config() const override
|
||||||
{
|
{
|
||||||
ORCA_PY_AUDIT_SCOPE();
|
ORCA_PY_AUDIT_SCOPE(::Slic3r::PluginAuditManager::AuditMode::Loading);
|
||||||
try {
|
try {
|
||||||
pybind11::gil_scoped_acquire gil;
|
pybind11::gil_scoped_acquire gil;
|
||||||
pybind11::function override = pybind11::get_override(static_cast<const Base*>(this), "get_default_config");
|
pybind11::function override = pybind11::get_override(static_cast<const Base*>(this), "get_default_config");
|
||||||
@@ -120,17 +117,17 @@ public:
|
|||||||
|
|
||||||
void on_load() override
|
void on_load() override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, void, Base, on_load);
|
ORCA_PY_OVERRIDE_AUDITED(::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE, void, Base, on_load);
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_unload() override
|
void on_unload() override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, void, Base, on_unload);
|
ORCA_PY_OVERRIDE_AUDITED(::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE, void, Base, on_unload);
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_cancelled() override
|
void on_cancelled() override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, void, Base, on_cancelled);
|
ORCA_PY_OVERRIDE_AUDITED(::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE, void, Base, on_cancelled);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -142,7 +139,7 @@ public:
|
|||||||
PluginCapabilityType get_type() const override
|
PluginCapabilityType get_type() const override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE, PluginCapabilityType, PluginCapabilityInterface,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE, PluginCapabilityType, PluginCapabilityInterface,
|
||||||
get_type);
|
get_type);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "PythonPluginBridge.hpp"
|
#include "PythonPluginBridge.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -40,7 +39,6 @@ thread_local std::string g_active_plugin_key;
|
|||||||
std::mutex g_registry_mutex;
|
std::mutex g_registry_mutex;
|
||||||
std::unordered_map<std::string, std::vector<py::object>> g_pending_capabilities;
|
std::unordered_map<std::string, std::vector<py::object>> g_pending_capabilities;
|
||||||
std::unordered_map<std::string, py::object> g_pending_package;
|
std::unordered_map<std::string, py::object> g_pending_package;
|
||||||
std::unordered_map<std::string, std::vector<std::string>> g_pending_fs_read_permissions;
|
|
||||||
struct PluginInstanceHandle
|
struct PluginInstanceHandle
|
||||||
{
|
{
|
||||||
// The C++ plugin interface points into a Python object. Keep both alive through one
|
// The C++ plugin interface points into a Python object. Keep both alive through one
|
||||||
@@ -78,8 +76,6 @@ void discard_pending_capture_without_python(const std::string& plugin_key)
|
|||||||
(void) package->second.release();
|
(void) package->second.release();
|
||||||
g_pending_package.erase(package);
|
g_pending_package.erase(package);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_pending_fs_read_permissions.erase(plugin_key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -104,38 +100,34 @@ void PythonPluginBridge::begin_plugin_capture(const std::string& plugin_key)
|
|||||||
// for this same entry path.
|
// for this same entry path.
|
||||||
g_pending_capabilities.erase(plugin_key);
|
g_pending_capabilities.erase(plugin_key);
|
||||||
g_pending_package.erase(plugin_key);
|
g_pending_package.erase(plugin_key);
|
||||||
g_pending_fs_read_permissions.erase(plugin_key);
|
|
||||||
}
|
}
|
||||||
// From now until finalize/cancel, @orca.plugin and register_capability() calls made by
|
// From now until finalize/cancel, @orca.plugin and register_capability() calls made by
|
||||||
// Python code on this thread are attributed to this plugin.
|
// Python code on this thread are attributed to this plugin.
|
||||||
g_active_plugin_key = plugin_key;
|
g_active_plugin_key = plugin_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(const std::string& capture_key,
|
std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(const std::string& plugin_key, std::string& error)
|
||||||
const std::string& plugin_key,
|
|
||||||
std::string& error)
|
|
||||||
{
|
{
|
||||||
PythonGILState gil;
|
PythonGILState gil;
|
||||||
if (!gil) {
|
if (!gil) {
|
||||||
error = "Python interpreter is shutting down";
|
error = "Python interpreter is shutting down";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << "Finalizing Python plugin capture for key " << capture_key;
|
BOOST_LOG_TRIVIAL(info) << "Finalizing Python plugin capture for key " << plugin_key;
|
||||||
|
|
||||||
// Phase 1: run the package class's register_capabilities() while the active key is
|
// Phase 1: run the package class's register_capabilities() while the active key is
|
||||||
// still set. That method is expected to call orca.register_capability() once per
|
// still set. That method is expected to call orca.register_capability() once per
|
||||||
// capability class, and register_capability() needs g_active_plugin_key to know which
|
// capability class, and register_capability() needs g_active_plugin_key to know which
|
||||||
// pending bucket to append to.
|
// pending bucket to append to.
|
||||||
{
|
{
|
||||||
auto clear_active_key = [&capture_key]() {
|
auto clear_active_key = [&plugin_key]() {
|
||||||
if (g_active_plugin_key == capture_key)
|
if (g_active_plugin_key == plugin_key)
|
||||||
g_active_plugin_key.clear();
|
g_active_plugin_key.clear();
|
||||||
};
|
};
|
||||||
auto discard_pending_for_key = [&capture_key]() {
|
auto discard_pending_for_key = [&plugin_key]() {
|
||||||
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
||||||
g_pending_capabilities.erase(capture_key);
|
g_pending_capabilities.erase(plugin_key);
|
||||||
g_pending_package.erase(capture_key);
|
g_pending_package.erase(plugin_key);
|
||||||
g_pending_fs_read_permissions.erase(capture_key);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -145,7 +137,7 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
|||||||
py::object package_cls;
|
py::object package_cls;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
||||||
auto it = g_pending_package.find(capture_key);
|
auto it = g_pending_package.find(plugin_key);
|
||||||
if (it != g_pending_package.end()) {
|
if (it != g_pending_package.end()) {
|
||||||
package_cls = it->second;
|
package_cls = it->second;
|
||||||
g_pending_package.erase(it);
|
g_pending_package.erase(it);
|
||||||
@@ -164,33 +156,6 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
|||||||
// are kept.
|
// are kept.
|
||||||
py::object package = package_cls();
|
py::object package = package_cls();
|
||||||
package.attr("register_capabilities")();
|
package.attr("register_capabilities")();
|
||||||
|
|
||||||
// Permission declarations are collected while register_capabilities() runs so the
|
|
||||||
// plugin can describe its needs without touching wx from the Python load worker. Ask
|
|
||||||
// only after registration returns, before capability instances are materialized.
|
|
||||||
std::vector<std::string> fs_read_permissions;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
|
||||||
auto it = g_pending_fs_read_permissions.find(capture_key);
|
|
||||||
if (it != g_pending_fs_read_permissions.end()) {
|
|
||||||
fs_read_permissions = std::move(it->second);
|
|
||||||
g_pending_fs_read_permissions.erase(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!fs_read_permissions.empty()) {
|
|
||||||
bool granted = false;
|
|
||||||
{
|
|
||||||
py::gil_scoped_release release;
|
|
||||||
granted = PluginAuditManager::instance().request_filesystem_read_permissions(plugin_key, fs_read_permissions);
|
|
||||||
}
|
|
||||||
if (!granted) {
|
|
||||||
error = "Plugin filesystem read permission request denied";
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << error << " for key " << plugin_key;
|
|
||||||
discard_pending_for_key();
|
|
||||||
clear_active_key();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (py::error_already_set& err) {
|
} catch (py::error_already_set& err) {
|
||||||
log_python_exception_keep(err);
|
log_python_exception_keep(err);
|
||||||
error = err.what();
|
error = err.what();
|
||||||
@@ -214,7 +179,7 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
|||||||
std::vector<py::object> classes;
|
std::vector<py::object> classes;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
||||||
auto it = g_pending_capabilities.find(capture_key);
|
auto it = g_pending_capabilities.find(plugin_key);
|
||||||
if (it != g_pending_capabilities.end()) {
|
if (it != g_pending_capabilities.end()) {
|
||||||
classes = std::move(it->second);
|
classes = std::move(it->second);
|
||||||
g_pending_capabilities.erase(it);
|
g_pending_capabilities.erase(it);
|
||||||
@@ -223,10 +188,10 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
|||||||
|
|
||||||
// Registration is complete. Later register_capability() calls should fail instead of
|
// Registration is complete. Later register_capability() calls should fail instead of
|
||||||
// accidentally attaching themselves to this plugin.
|
// accidentally attaching themselves to this plugin.
|
||||||
if (g_active_plugin_key == capture_key)
|
if (g_active_plugin_key == plugin_key)
|
||||||
g_active_plugin_key.clear();
|
g_active_plugin_key.clear();
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "Collected " << classes.size() << " registered capability class(es) for key " << capture_key;
|
BOOST_LOG_TRIVIAL(info) << "Collected " << classes.size() << " registered capability class(es) for key " << plugin_key;
|
||||||
|
|
||||||
std::vector<CapturedCapability> capabilities;
|
std::vector<CapturedCapability> capabilities;
|
||||||
capabilities.reserve(classes.size());
|
capabilities.reserve(classes.size());
|
||||||
@@ -238,7 +203,7 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
|||||||
py::object instance = cls();
|
py::object instance = cls();
|
||||||
if (!py::isinstance<PluginCapabilityInterface>(instance)) {
|
if (!py::isinstance<PluginCapabilityInterface>(instance)) {
|
||||||
error = "Registered capability must inherit from a PluginCapability base";
|
error = "Registered capability must inherit from a PluginCapability base";
|
||||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture failed type check for key " << capture_key
|
BOOST_LOG_TRIVIAL(error) << "Python plugin capture failed type check for key " << plugin_key
|
||||||
<< " error=" << error;
|
<< " error=" << error;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -246,7 +211,7 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
|||||||
auto capability_iface = instance.cast<std::shared_ptr<PluginCapabilityInterface>>();
|
auto capability_iface = instance.cast<std::shared_ptr<PluginCapabilityInterface>>();
|
||||||
if (!capability_iface) {
|
if (!capability_iface) {
|
||||||
error = "Failed to cast Python capability to PluginCapabilityInterface";
|
error = "Failed to cast Python capability to PluginCapabilityInterface";
|
||||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture failed cast for key " << capture_key
|
BOOST_LOG_TRIVIAL(error) << "Python plugin capture failed cast for key " << plugin_key
|
||||||
<< " error=" << error;
|
<< " error=" << error;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -260,7 +225,7 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
|||||||
// here is a hard error that rejects the whole plugin capture.
|
// here is a hard error that rejects the whole plugin capture.
|
||||||
if (name.find(';') != std::string::npos) {
|
if (name.find(';') != std::string::npos) {
|
||||||
error = "Capability name must not contain ';': " + name;
|
error = "Capability name must not contain ';': " + name;
|
||||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture rejected capability for key " << capture_key
|
BOOST_LOG_TRIVIAL(error) << "Python plugin capture rejected capability for key " << plugin_key
|
||||||
<< " error=" << error;
|
<< " error=" << error;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -280,12 +245,12 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
|||||||
// log the traceback here. GIL is held for the duration of finalize_plugin_capture.
|
// log the traceback here. GIL is held for the duration of finalize_plugin_capture.
|
||||||
log_python_exception_keep(err);
|
log_python_exception_keep(err);
|
||||||
error = err.what();
|
error = err.what();
|
||||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture raised Python exception for key " << capture_key
|
BOOST_LOG_TRIVIAL(error) << "Python plugin capture raised Python exception for key " << plugin_key
|
||||||
<< " error=" << error;
|
<< " error=" << error;
|
||||||
return {};
|
return {};
|
||||||
} catch (const std::exception& ex) {
|
} catch (const std::exception& ex) {
|
||||||
error = ex.what();
|
error = ex.what();
|
||||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture raised exception for key " << capture_key
|
BOOST_LOG_TRIVIAL(error) << "Python plugin capture raised exception for key " << plugin_key
|
||||||
<< " error=" << error;
|
<< " error=" << error;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -313,7 +278,6 @@ void PythonPluginBridge::cancel_plugin_capture(const std::string& plugin_key)
|
|||||||
// may already have registered under this key.
|
// may already have registered under this key.
|
||||||
g_pending_capabilities.erase(plugin_key);
|
g_pending_capabilities.erase(plugin_key);
|
||||||
g_pending_package.erase(plugin_key);
|
g_pending_package.erase(plugin_key);
|
||||||
g_pending_fs_read_permissions.erase(plugin_key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_active_plugin_key == plugin_key)
|
if (g_active_plugin_key == plugin_key)
|
||||||
@@ -340,7 +304,6 @@ void PythonPluginBridge::clear_pending_captures()
|
|||||||
(void) pkg.release();
|
(void) pkg.release();
|
||||||
}
|
}
|
||||||
g_pending_package.clear();
|
g_pending_package.clear();
|
||||||
g_pending_fs_read_permissions.clear();
|
|
||||||
g_active_plugin_key.clear();
|
g_active_plugin_key.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -349,7 +312,6 @@ void PythonPluginBridge::clear_pending_captures()
|
|||||||
BOOST_LOG_TRIVIAL(info) << "Clearing " << g_pending_capabilities.size() << " pending Python plugin capture(s)";
|
BOOST_LOG_TRIVIAL(info) << "Clearing " << g_pending_capabilities.size() << " pending Python plugin capture(s)";
|
||||||
g_pending_capabilities.clear();
|
g_pending_capabilities.clear();
|
||||||
g_pending_package.clear();
|
g_pending_package.clear();
|
||||||
g_pending_fs_read_permissions.clear();
|
|
||||||
g_active_plugin_key.clear();
|
g_active_plugin_key.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,27 +448,6 @@ void bind_python_api(pybind11::module_& m)
|
|||||||
},
|
},
|
||||||
R"pbdoc(Register a PluginCapability subclass while OrcaSlicer loads your module.)pbdoc");
|
R"pbdoc(Register a PluginCapability subclass while OrcaSlicer loads your module.)pbdoc");
|
||||||
|
|
||||||
m.def(
|
|
||||||
"request_permissions",
|
|
||||||
[](const std::vector<std::string>& fs_read) {
|
|
||||||
if (g_active_plugin_key.empty())
|
|
||||||
throw py::value_error("request_permissions() called outside plugin discovery context");
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
|
||||||
auto& requested = g_pending_fs_read_permissions[g_active_plugin_key];
|
|
||||||
for (const std::string& path : fs_read) {
|
|
||||||
if (path.empty())
|
|
||||||
throw py::value_error("request_permissions(fs_read=...) does not accept empty paths");
|
|
||||||
if (std::find(requested.begin(), requested.end(), path) == requested.end())
|
|
||||||
requested.push_back(path);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
py::arg("fs_read") = std::vector<std::string>{},
|
|
||||||
R"pbdoc(Request filesystem read permissions while OrcaSlicer loads your module.
|
|
||||||
|
|
||||||
The host presents the request to the user after register_capabilities() returns. Denying the
|
|
||||||
request aborts the plugin load.)pbdoc");
|
|
||||||
|
|
||||||
m.def("plugin", [](py::object cls) {
|
m.def("plugin", [](py::object cls) {
|
||||||
if (g_active_plugin_key.empty())
|
if (g_active_plugin_key.empty())
|
||||||
throw py::value_error("@orca.plugin used outside plugin discovery context");
|
throw py::value_error("@orca.plugin used outside plugin discovery context");
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public:
|
|||||||
// Returns one CapturedCapability per capability, or an empty vector on failure
|
// Returns one CapturedCapability per capability, or an empty vector on failure
|
||||||
// (error message populated).
|
// (error message populated).
|
||||||
std::vector<CapturedCapability> finalize_plugin_capture(
|
std::vector<CapturedCapability> finalize_plugin_capture(
|
||||||
const std::string& capture_key, const std::string& plugin_key, std::string& error);
|
const std::string& plugin_key, std::string& error);
|
||||||
|
|
||||||
// Clear any pending registrations for the key. Safe to call when import fails.
|
// Clear any pending registrations for the key. Safe to call when import fails.
|
||||||
void cancel_plugin_capture(const std::string& plugin_key);
|
void cancel_plugin_capture(const std::string& plugin_key);
|
||||||
|
|||||||
@@ -16,206 +16,206 @@ public:
|
|||||||
AgentInfo get_agent_info() override
|
AgentInfo get_agent_info() override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, AgentInfo, PrinterAgentPluginCapability,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, AgentInfo, PrinterAgentPluginCapability,
|
||||||
get_agent_info);
|
get_agent_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override
|
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, connect_printer, dev_id,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, connect_printer, dev_id,
|
||||||
dev_ip, username, password, use_ssl);
|
dev_ip, username, password, use_ssl);
|
||||||
}
|
}
|
||||||
|
|
||||||
int disconnect_printer() override
|
int disconnect_printer() override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, disconnect_printer);
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, disconnect_printer);
|
||||||
}
|
}
|
||||||
|
|
||||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override
|
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, send_message, dev_id,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, send_message, dev_id,
|
||||||
json_str, qos, flag);
|
json_str, qos, flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override
|
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, send_message_to_printer,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, send_message_to_printer,
|
||||||
dev_id, json_str, qos, flag);
|
dev_id, json_str, qos, flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool start_discovery(bool start, bool sending) override
|
bool start_discovery(bool start, bool sending) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, bool, PrinterAgentPluginCapability, start_discovery, start,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, bool, PrinterAgentPluginCapability, start_discovery, start,
|
||||||
sending);
|
sending);
|
||||||
}
|
}
|
||||||
|
|
||||||
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override
|
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, bind_detect, dev_ip,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, bind_detect, dev_ip,
|
||||||
sec_link, detect);
|
sec_link, detect);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_user_selected_machine() override
|
std::string get_user_selected_machine() override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, std::string, PrinterAgentPluginCapability,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, std::string, PrinterAgentPluginCapability,
|
||||||
get_user_selected_machine);
|
get_user_selected_machine);
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_user_selected_machine(std::string dev_id) override
|
int set_user_selected_machine(std::string dev_id) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability,
|
||||||
set_user_selected_machine, dev_id);
|
set_user_selected_machine, dev_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override
|
int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability,
|
||||||
start_send_gcode_to_sdcard, params, update_fn, cancel_fn, wait_fn);
|
start_send_gcode_to_sdcard, params, update_fn, cancel_fn, wait_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override
|
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, start_local_print,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, start_local_print,
|
||||||
params, update_fn, cancel_fn);
|
params, update_fn, cancel_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
FilamentSyncMode get_filament_sync_mode() const override
|
FilamentSyncMode get_filament_sync_mode() const override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, FilamentSyncMode, PrinterAgentPluginCapability,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, FilamentSyncMode, PrinterAgentPluginCapability,
|
||||||
get_filament_sync_mode);
|
get_filament_sync_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fetch_filament_info(std::string dev_id) override
|
bool fetch_filament_info(std::string dev_id) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, bool, PrinterAgentPluginCapability, fetch_filament_info, dev_id);
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, bool, PrinterAgentPluginCapability, fetch_filament_info, dev_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
int check_cert() override
|
int check_cert() override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, check_cert);
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, check_cert);
|
||||||
}
|
}
|
||||||
|
|
||||||
void install_device_cert(std::string dev_id, bool lan_only) override
|
void install_device_cert(std::string dev_id, bool lan_only) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, void, PrinterAgentPluginCapability, install_device_cert, dev_id,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, void, PrinterAgentPluginCapability, install_device_cert, dev_id,
|
||||||
lan_only);
|
lan_only);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ping_bind(std::string ping_code) override
|
int ping_bind(std::string ping_code) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, ping_bind, ping_code);
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, ping_bind, ping_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override
|
int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, bind, dev_ip, dev_id,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, bind, dev_ip, dev_id,
|
||||||
dev_model, sec_link, timezone, improved, update_fn);
|
dev_model, sec_link, timezone, improved, update_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
int unbind(std::string dev_id) override
|
int unbind(std::string dev_id) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, unbind, dev_id);
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, unbind, dev_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override
|
int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, start_print, params,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, start_print, params,
|
||||||
update_fn, cancel_fn, wait_fn);
|
update_fn, cancel_fn, wait_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
int start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override
|
int start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability,
|
||||||
start_local_print_with_record, params, update_fn, cancel_fn, wait_fn);
|
start_local_print_with_record, params, update_fn, cancel_fn, wait_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override
|
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, start_sdcard_print, params,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, start_sdcard_print, params,
|
||||||
update_fn, cancel_fn);
|
update_fn, cancel_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override
|
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, get_hms_snapshot, dev_id,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, get_hms_snapshot, dev_id,
|
||||||
file_name, callback);
|
file_name, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_server_callback(OnServerErrFn fn) override
|
int set_server_callback(OnServerErrFn fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_server_callback, fn);
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_server_callback, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn) override
|
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_ssdp_msg_fn, fn);
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_ssdp_msg_fn, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_on_printer_connected_fn(OnPrinterConnectedFn fn) override
|
int set_on_printer_connected_fn(OnPrinterConnectedFn fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_printer_connected_fn,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_printer_connected_fn,
|
||||||
fn);
|
fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn) override
|
int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_subscribe_failure_fn,
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_subscribe_failure_fn,
|
||||||
fn);
|
fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_on_message_fn(OnMessageFn fn) override
|
int set_on_message_fn(OnMessageFn fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_message_fn, fn);
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_message_fn, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_on_user_message_fn(OnMessageFn fn) override
|
int set_on_user_message_fn(OnMessageFn fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_user_message_fn, fn);
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_user_message_fn, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_on_local_connect_fn(OnLocalConnectedFn fn) override
|
int set_on_local_connect_fn(OnLocalConnectedFn fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_local_connect_fn, fn);
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_local_connect_fn, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_on_local_message_fn(OnMessageFn fn) override
|
int set_on_local_message_fn(OnMessageFn fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_local_message_fn, fn);
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_local_message_fn, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_queue_on_main_fn(QueueOnMainFn fn) override
|
int set_queue_on_main_fn(QueueOnMainFn fn) override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_queue_on_main_fn, fn);
|
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_queue_on_main_fn, fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// request_bind_ticket returns its ticket through a std::string* out-param, which pybind11
|
// request_bind_ticket returns its ticket through a std::string* out-param, which pybind11
|
||||||
@@ -223,7 +223,7 @@ public:
|
|||||||
// returns a (result, ticket) tuple, which we unpack into the int result and the out-param.
|
// returns a (result, ticket) tuple, which we unpack into the int result and the out-param.
|
||||||
int request_bind_ticket(std::string* ticket) override
|
int request_bind_ticket(std::string* ticket) override
|
||||||
{
|
{
|
||||||
ORCA_PY_AUDIT_SCOPE();
|
ORCA_PY_AUDIT_SCOPE(::Slic3r::PluginAuditManager::AuditMode::Loading);
|
||||||
::Slic3r::PluginCapabilityInterface::RefCounter _orca_ref_counter(*this);
|
::Slic3r::PluginCapabilityInterface::RefCounter _orca_ref_counter(*this);
|
||||||
::Slic3r::PythonGILState gil;
|
::Slic3r::PythonGILState gil;
|
||||||
if (!gil)
|
if (!gil)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public:
|
|||||||
ExecutionResult execute() override
|
ExecutionResult execute() override
|
||||||
{
|
{
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
|
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||||
[] {},
|
[] {},
|
||||||
PYBIND11_OVERRIDE_PURE,
|
PYBIND11_OVERRIDE_PURE,
|
||||||
ExecutionResult,
|
ExecutionResult,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public:
|
|||||||
using PyPluginCommonTrampoline<SlicingPipelinePluginCapability>::PyPluginCommonTrampoline;
|
using PyPluginCommonTrampoline<SlicingPipelinePluginCapability>::PyPluginCommonTrampoline;
|
||||||
ExecutionResult execute(SlicingPipelineContext& ctx) override {
|
ExecutionResult execute(SlicingPipelineContext& ctx) override {
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
|
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||||
[&]{
|
[&]{
|
||||||
// At Step.psGCodePostProcess the plugin edits the exported G-code file, which lives
|
// At Step.psGCodePostProcess the plugin edits the exported G-code file, which lives
|
||||||
// outside data_dir() (a temp/output folder), so writing to it would otherwise be
|
// outside data_dir() (a temp/output folder), so writing to it would otherwise be
|
||||||
|
|||||||
@@ -153,6 +153,24 @@ TEST_CASE("Object brims are generated per instance", "[SkirtBrim]")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Uncombined neighboring brims precede their respective objects", "[SkirtBrim]")
|
||||||
|
{
|
||||||
|
Print print;
|
||||||
|
Model model;
|
||||||
|
place_two_cubes_apart(0, {
|
||||||
|
{ "skirt_loops", 0 },
|
||||||
|
{ "brim_type", "outer_only" },
|
||||||
|
{ "brim_width", 5 },
|
||||||
|
{ "combine_brims", 0 },
|
||||||
|
}, print, model);
|
||||||
|
print.process();
|
||||||
|
|
||||||
|
REQUIRE(print.skirt_brim_groups().size() == 1);
|
||||||
|
REQUIRE(print.skirt_brim_groups().front().brims.size() == 2);
|
||||||
|
CHECK(role_sequence(gcode(print), { "brim", "perimeter" }) ==
|
||||||
|
std::vector<std::string>{ "brim", "perimeter", "brim", "perimeter" });
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Combine brims merges neighboring object instances", "[SkirtBrim]")
|
TEST_CASE("Combine brims merges neighboring object instances", "[SkirtBrim]")
|
||||||
{
|
{
|
||||||
Print print;
|
Print print;
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ TEST_CASE("Plugin audit denies app config and token filenames anywhere", "[audit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Plugin audit deny beats allowed roots", "[audit]")
|
TEST_CASE("Plugin audit deny beats allowed roots and the Loading read exemption", "[audit]")
|
||||||
{
|
{
|
||||||
ScopedDataDir data_dir_guard("plugin-audit-deny");
|
ScopedDataDir data_dir_guard("plugin-audit-deny");
|
||||||
seed_denied_names();
|
seed_denied_names();
|
||||||
@@ -91,8 +91,8 @@ TEST_CASE("Plugin audit deny beats allowed roots", "[audit]")
|
|||||||
// config and the token would otherwise be reachable simply by living inside it.
|
// config and the token would otherwise be reachable simply by living inside it.
|
||||||
mgr.add_global_allowed_root(data_dir());
|
mgr.add_global_allowed_root(data_dir());
|
||||||
|
|
||||||
// Enter a plugin context. The deny must hold even inside a globally allowed root.
|
// Enter a plugin context. The deny must hold in Loading mode, which every scope runs in.
|
||||||
ScopedPluginAuditContext ctx("test_plugin", "");
|
ScopedPluginAuditContext ctx("test_plugin", "", PluginAuditManager::AuditMode::Loading);
|
||||||
|
|
||||||
const fs::path conf = fs::path(data_dir()) / (SLIC3R_APP_KEY ".conf");
|
const fs::path conf = fs::path(data_dir()) / (SLIC3R_APP_KEY ".conf");
|
||||||
const fs::path token = fs::path(data_dir()) / secret_constants::USER_SECRET_FILENAME;
|
const fs::path token = fs::path(data_dir()) / secret_constants::USER_SECRET_FILENAME;
|
||||||
@@ -103,13 +103,6 @@ TEST_CASE("Plugin audit deny beats allowed roots", "[audit]")
|
|||||||
CHECK(decision.allowed);
|
CHECK(decision.allowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("a file outside the allowed root is blocked for reads as well as writes")
|
|
||||||
{
|
|
||||||
AuditDecision decision = mgr.check_open((fs::path(data_dir()).parent_path() / "outside.txt").string(), "r");
|
|
||||||
CHECK_FALSE(decision.allowed);
|
|
||||||
CHECK(decision.reason == "outside allowed root");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("writing the app config is blocked despite data_dir() being allowed")
|
SECTION("writing the app config is blocked despite data_dir() being allowed")
|
||||||
{
|
{
|
||||||
AuditDecision decision = mgr.check_open(conf.string(), "w");
|
AuditDecision decision = mgr.check_open(conf.string(), "w");
|
||||||
@@ -117,14 +110,16 @@ TEST_CASE("Plugin audit deny beats allowed roots", "[audit]")
|
|||||||
CHECK(decision.reason == "denied filename");
|
CHECK(decision.reason == "denied filename");
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("reading the app config is blocked despite the allowed root")
|
SECTION("reading the app config is blocked even though Loading exempts reads")
|
||||||
{
|
{
|
||||||
|
// Without the deny, a read in Loading mode short-circuits to allow. The deny sits above
|
||||||
|
// that exemption, so this must still be blocked.
|
||||||
AuditDecision decision = mgr.check_open(conf.string(), "r");
|
AuditDecision decision = mgr.check_open(conf.string(), "r");
|
||||||
CHECK_FALSE(decision.allowed);
|
CHECK_FALSE(decision.allowed);
|
||||||
CHECK(decision.reason == "denied filename");
|
CHECK(decision.reason == "denied filename");
|
||||||
}
|
}
|
||||||
|
|
||||||
SECTION("reading the cloud refresh token is blocked")
|
SECTION("reading the cloud refresh token is blocked in Loading mode")
|
||||||
{
|
{
|
||||||
AuditDecision decision = mgr.check_open(token.string(), "r");
|
AuditDecision decision = mgr.check_open(token.string(), "r");
|
||||||
CHECK_FALSE(decision.allowed);
|
CHECK_FALSE(decision.allowed);
|
||||||
@@ -155,7 +150,7 @@ TEST_CASE("Plugin audit deny beats a plugin's own scoped root", "[audit]")
|
|||||||
const fs::path plugin_dir = fs::path(data_dir()) / "plugins" / "test_plugin";
|
const fs::path plugin_dir = fs::path(data_dir()) / "plugins" / "test_plugin";
|
||||||
fs::create_directories(plugin_dir);
|
fs::create_directories(plugin_dir);
|
||||||
|
|
||||||
ScopedPluginAuditContext ctx("test_plugin", "");
|
ScopedPluginAuditContext ctx("test_plugin", "", PluginAuditManager::AuditMode::Loading);
|
||||||
mgr.add_scoped_allowed_root(plugin_dir);
|
mgr.add_scoped_allowed_root(plugin_dir);
|
||||||
|
|
||||||
SECTION("the plugin's own non-denied file opens for read and write")
|
SECTION("the plugin's own non-denied file opens for read and write")
|
||||||
|
|||||||
@@ -112,18 +112,6 @@ TEST_CASE("install-state sidecar is the source of truth for a cloud plugin's ins
|
|||||||
REQUIRE(read_install_state(plugin_dir, state));
|
REQUIRE(read_install_state(plugin_dir, state));
|
||||||
CHECK(state.installed_version == "1.2.0");
|
CHECK(state.installed_version == "1.2.0");
|
||||||
|
|
||||||
state.permissions.networking = {"api.example.com", "192.168.45.6"};
|
|
||||||
state.permissions.fs_read = {"/path/to/read"};
|
|
||||||
state.permissions.fs_write = {"/path/to/write"};
|
|
||||||
REQUIRE(write_install_state(plugin_dir, state));
|
|
||||||
|
|
||||||
// Permission data is persisted in the same sidecar as the installation metadata.
|
|
||||||
PluginInstallState persisted;
|
|
||||||
REQUIRE(read_install_state(plugin_dir, persisted));
|
|
||||||
CHECK(persisted.permissions.networking == state.permissions.networking);
|
|
||||||
CHECK(persisted.permissions.fs_read == state.permissions.fs_read);
|
|
||||||
CHECK(persisted.permissions.fs_write == state.permissions.fs_write);
|
|
||||||
|
|
||||||
// Reading the sidecar back onto a freshly-scanned descriptor (whose header version is still
|
// Reading the sidecar back onto a freshly-scanned descriptor (whose header version is still
|
||||||
// 1.0.0) must surface the cloud-installed 1.2.0. This is what lets update_cloud_metadata compare
|
// 1.0.0) must surface the cloud-installed 1.2.0. This is what lets update_cloud_metadata compare
|
||||||
// the cloud's latest version against the installed version instead of the stale header, so an
|
// the cloud's latest version against the installed version instead of the stale header, so an
|
||||||
@@ -132,4 +120,4 @@ TEST_CASE("install-state sidecar is the source of truth for a cloud plugin's ins
|
|||||||
scanned.version = "1.0.0"; // as parsed from the unchanged PEP723 header
|
scanned.version = "1.0.0"; // as parsed from the unchanged PEP723 header
|
||||||
read_install_state(plugin_dir, scanned);
|
read_install_state(plugin_dir, scanned);
|
||||||
CHECK(scanned.installed_version == "1.2.0");
|
CHECK(scanned.installed_version == "1.2.0");
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user