Store PrusaLink passwords in wxsecretstore

This commit is contained in:
David Kocik 2023-07-24 11:10:08 +02:00
parent fad42fb8f3
commit f624c55f6f
15 changed files with 429 additions and 8 deletions

View File

@ -41,8 +41,8 @@ option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1)
option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1)
option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0)
option(SLIC3R_UBSAN "Enable UBSan on Clang and GCC" 0)
option(SLIC3R_ENABLE_FORMAT_STEP "Enable compilation of STEP file support" ON)
# If SLIC3R_FHS is 1 -> SLIC3R_DESKTOP_INTEGRATION is always 0, othrewise variable.
option(SLIC3R_ENABLE_FORMAT_STEP "Enable compilation of STEP file support" 1)
# If SLIC3R_FHS is 1 -> SLIC3R_DESKTOP_INTEGRATION is always 0, otherwise variable.
CMAKE_DEPENDENT_OPTION(SLIC3R_DESKTOP_INTEGRATION "Allow perfoming desktop integration during runtime" 1 "NOT SLIC3R_FHS" 0)
set(OPENVDB_FIND_MODULE_PATH "" CACHE PATH "Path to OpenVDB installation's find modules.")

47
deps/wxWidgets/wxWidgets.cmake vendored Normal file
View File

@ -0,0 +1,47 @@
set(_wx_toolkit "")
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(_gtk_ver 2)
if (DEP_WX_GTK3)
set(_gtk_ver 3)
endif ()
set(_wx_toolkit "-DwxBUILD_TOOLKIT=gtk${_gtk_ver}")
endif()
set(_unicode_utf8 OFF)
if (UNIX AND NOT APPLE) # wxWidgets will not use char as the underlying type for wxString unless its forced to.
set (_unicode_utf8 ON)
endif()
prusaslicer_add_cmake_project(wxWidgets
URL https://github.com/prusa3d/wxWidgets/archive/78aa2dc0ea7ce99dc19adc1140f74c3e2e3f3a26.zip
URL_HASH SHA256=94b7d972373503e380e5a8b0ca63b1ccb956da4006402298dd89a0c5c7041b1e
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG dep_NanoSVG
CMAKE_ARGS
-DwxBUILD_PRECOMP=ON
${_wx_toolkit}
"-DCMAKE_DEBUG_POSTFIX:STRING="
-DwxBUILD_DEBUG_LEVEL=0
-DwxUSE_MEDIACTRL=OFF
-DwxUSE_DETECT_SM=OFF
-DwxUSE_UNICODE=ON
-DwxUSE_UNICODE_UTF8=${_unicode_utf8}
-DwxUSE_OPENGL=ON
-DwxUSE_LIBPNG=sys
-DwxUSE_ZLIB=sys
-DwxUSE_NANOSVG=sys
-DwxUSE_NANOSVG_EXTERNAL=ON
-DwxUSE_REGEX=OFF
-DwxUSE_LIBXPM=builtin
-DwxUSE_LIBJPEG=sys
-DwxUSE_LIBTIFF=sys
-DwxUSE_EXPAT=sys
-DwxUSE_LIBSDL=OFF
-DwxUSE_XTEST=OFF
-DwxUSE_GLCANVAS_EGL=OFF
-DwxUSE_WEBREQUEST=OFF
-DwxUSE_SECRETSTORE=ON
)
if (MSVC)
add_debug_dep(dep_wxWidgets)
endif ()

View File

@ -1990,6 +1990,8 @@ public:
one_string,
// Close parameter, string value could be one of the list values.
select_close,
// Password, string vaule is hidden by asterisk.
password,
};
static bool is_gui_type_enum_open(const GUIType gui_type)
{ return gui_type == ConfigOptionDef::GUIType::i_enum_open || gui_type == ConfigOptionDef::GUIType::f_enum_open || gui_type == ConfigOptionDef::GUIType::select_open; }

View File

@ -1947,7 +1947,7 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri
}
}
void PresetBundle::export_configbundle(const std::string &path, bool export_system_settings, bool export_physical_printers/* = false*/)
void PresetBundle::export_configbundle(const std::string &path, bool export_system_settings, bool export_physical_printers/* = false*/, std::function<bool(const std::string&, const std::string&, std::string&)> secret_callback)
{
boost::nowide::ofstream c;
c.open(path, std::ios::out | std::ios::trunc);
@ -1974,8 +1974,14 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst
if (export_physical_printers) {
for (const PhysicalPrinter& ph_printer : this->physical_printers) {
c << std::endl << "[physical_printer:" << ph_printer.name << "]" << std::endl;
for (const std::string& opt_key : ph_printer.config.keys())
c << opt_key << " = " << ph_printer.config.opt_serialize(opt_key) << std::endl;
for (const std::string& opt_key : ph_printer.config.keys()) {
std::string opt_val = ph_printer.config.opt_serialize(opt_key);
if (opt_val == "stored") {
secret_callback(ph_printer.name, opt_key, opt_val);
}
c << opt_key << " = " << opt_val << std::endl;
}
}
}

View File

@ -129,7 +129,7 @@ public:
const std::string &path, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule);
// Export a config bundle file containing all the presets and the names of the active presets.
void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false);
void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false, std::function<bool(const std::string&, const std::string&, std::string&)> secret_callback = nullptr);
// Enable / disable the "- default -" preset.
void set_default_suppressed(bool default_suppressed);

View File

@ -393,6 +393,7 @@ void PrintConfigDef::init_common_params()
def = this->add("printhost_password", coString);
def->label = L("Password");
// def->tooltip = L("");
def->gui_type = ConfigOptionDef::GUIType::password;
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
def->set_default_value(new ConfigOptionString(""));

View File

@ -335,6 +335,8 @@ set(SLIC3R_GUI_SOURCES
Utils/WxFontUtils.hpp
Utils/WifiScanner.hpp
Utils/WifiScanner.cpp
Utils/Secrets.hpp
Utils/Secrets.cpp
)
find_package(NanoSVG REQUIRED)

View File

@ -23,6 +23,9 @@
//#include <wx/glcanvas.h>
#include <wx/filename.h>
#include <wx/debug.h>
#if wxUSE_SECRETSTORE
#include <wx/secretstore.h>
#endif
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp>
@ -1789,9 +1792,54 @@ void MainFrame::export_configbundle(bool export_physical_printers /*= false*/)
file = dlg.GetPath();
if (!file.IsEmpty()) {
// Export the config bundle.
bool passwords_to_plain = false;
bool passwords_dialog_shown = false;
// callback function thats going to be passed to preset bundle (so preset bundle doesnt have to include WX secret lib)
std::function<bool(const std::string&, const std::string&, std::string&)> load_password = [&](const std::string& printer_id, const std::string& opt, std::string& out_psswd)->bool{
out_psswd = std::string();
#if wxUSE_SECRETSTORE
// First password prompts user with dialog
if (!passwords_dialog_shown) {
//wxString msg = GUI::format_wxstr(L"%1%\n%2%", _L("Some of the exported printers contains passwords, which are stored in the system password store."), _L("Do you wish to include the passwords to the exported file in the plain text form?"));
wxString msg = _L("Some of the exported printers contains passwords, which are stored in the system password store."
" Do you wish to include the passwords in the plain text form to the exported file?");
MessageDialog dlg_psswd(this, msg, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION);
if (dlg_psswd.ShowModal() == wxID_YES)
passwords_to_plain = true;
passwords_dialog_shown = true;
}
if (!passwords_to_plain)
return false;
wxSecretStore store = wxSecretStore::GetDefault();
wxString errmsg;
if (!store.IsOk(&errmsg)) {
std::string msg = GUI::format("%1% (%2%).", _u8L("Failed to load credentials from the system secret store."), errmsg);
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
// Do not try again. System store is not reachable.
passwords_to_plain = false;
return false;
}
const wxString service = GUI::format_wxstr(L"%1%/PhysicalPrinter/%2%/%3%", SLIC3R_APP_NAME, printer_id, opt);
wxString username;
wxSecretValue password;
if (!store.Load(service, username, password)) {
std::string msg = GUI::format(_u8L("Failed to load credentials from the system secret store for the printer %1%."), printer_id);
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
out_psswd = into_u8(password.GetAsString());
return true;
#else
return false;
#endif // wxUSE_SECRETSTORE
};
wxGetApp().app_config->update_config_dir(get_dir_name(file));
try {
wxGetApp().preset_bundle->export_configbundle(file.ToUTF8().data(), false, export_physical_printers);
wxGetApp().preset_bundle->export_configbundle(file.ToUTF8().data(), false, export_physical_printers, load_password);
} catch (const std::exception &ex) {
show_error(this, ex.what());
}

View File

@ -9,6 +9,7 @@
#include <vector>
#include <string>
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <wx/sizer.h>
#include <wx/stattext.h>
@ -16,6 +17,10 @@
#include <wx/button.h>
#include <wx/statbox.h>
#include <wx/wupdlock.h>
#if wxUSE_SECRETSTORE
#include <wx/secretstore.h>
#endif
#include <wx/clipbrd.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/PrintConfig.hpp"
@ -153,6 +158,78 @@ void PresetForPrinter::on_sys_color_changed()
m_delete_preset_btn->sys_color_changed();
}
namespace {
bool is_secret_store_ok()
{
#if wxUSE_SECRETSTORE
wxSecretStore store = wxSecretStore::GetDefault();
wxString errmsg;
if (!store.IsOk(&errmsg)) {
BOOST_LOG_TRIVIAL(warning) << "wxSecretStore is not supported: " << errmsg;
return false;
}
return true;
#else
return false;
#endif
}
bool save_secret(const std::string& id, const std::string& opt, const std::string& usr, const std::string& psswd)
{
#if wxUSE_SECRETSTORE
wxSecretStore store = wxSecretStore::GetDefault();
wxString errmsg;
if (!store.IsOk(&errmsg)) {
std::string msg = GUI::format("%1% (%2%).", _u8L("This system doesn't support storing passwords securely"), errmsg);
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
const wxString service = GUI::format_wxstr(L"%1%/PhysicalPrinter/%2%/%3%", SLIC3R_APP_NAME, id, opt);
const wxString username = boost::nowide::widen(usr);
const wxSecretValue password(boost::nowide::widen(psswd));
if (!store.Save(service, username, password)) {
std::string msg(_u8L("Failed to save credentials to the system secret store."));
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
return true;
#else
BOOST_LOG_TRIVIAL(error) << "wxUSE_SECRETSTORE not supported. Cannot save password to the system store.";
return false;
#endif // wxUSE_SECRETSTORE
}
bool load_secret(const std::string& id, const std::string& opt, std::string& usr, std::string& psswd)
{
#if wxUSE_SECRETSTORE
wxSecretStore store = wxSecretStore::GetDefault();
wxString errmsg;
if (!store.IsOk(&errmsg)) {
std::string msg = GUI::format("%1% (%2%).", _u8L("This system doesn't support storing passwords securely"), errmsg);
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
const wxString service = GUI::format_wxstr(L"%1%/PhysicalPrinter/%2%/%3%", SLIC3R_APP_NAME, id, opt);
wxString username;
wxSecretValue password;
if (!store.Load(service, username, password)) {
std::string msg(_u8L("Failed to load credentials from the system secret store."));
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
usr = into_u8(username);
psswd = into_u8(password.GetAsString());
return true;
#else
BOOST_LOG_TRIVIAL(error) << "wxUSE_SECRETSTORE not supported. Cannot load password from the system store.";
return false;
#endif // wxUSE_SECRETSTORE
}
}
//------------------------------------------
// PhysicalPrinterDialog
@ -202,6 +279,20 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxWindow* parent, wxString printer_
const std::set<std::string>& preset_names = printer->get_preset_names();
for (const std::string& preset_name : preset_names)
m_presets.emplace_back(new PresetForPrinter(this, preset_name));
// "stored" indicates data are stored secretly, load them from store.
if (m_printer.config.opt_string("printhost_password") == "stored" && m_printer.config.opt_string("printhost_password") == "stored") {
std::string username;
std::string password;
if (load_secret(m_printer.name, "printhost_password", username, password)) {
if (!username.empty())
m_printer.config.opt_string("printhost_user") = username;
if (!password.empty())
m_printer.config.opt_string("printhost_password") = password;
} else {
m_printer.config.opt_string("printhost_user") = std::string();
m_printer.config.opt_string("printhost_password") = std::string();
}
}
}
if (m_presets.size() == 1)
@ -348,6 +439,20 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
return sizer;
};
auto api_key_copy = [=](wxWindow* parent) {
auto sizer = create_sizer_with_btn(parent, &m_api_key_copy_btn, "copy", _L("Copy"));
m_api_key_copy_btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) {
if (Field* apikey_field = m_optgroup->get_field("printhost_apikey"); apikey_field) {
if (wxTextCtrl* temp = dynamic_cast<wxTextCtrl*>(apikey_field->getWindow()); temp ) {
wxTheClipboard->Open();
wxTheClipboard->SetData(new wxTextDataObject(temp->GetValue()));
wxTheClipboard->Close();
}
}
});
return sizer;
};
// Set a wider width for a better alignment
Option option = m_optgroup->get_option("print_host");
option.opt.width = Field::def_width_wider();
@ -360,7 +465,9 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
option = m_optgroup->get_option("printhost_apikey");
option.opt.width = Field::def_width_wider();
m_optgroup->append_single_option_line(option);
Line apikey_line = m_optgroup->create_single_option_line(option);
apikey_line.append_widget(api_key_copy);
m_optgroup->append_line(apikey_line);
option = m_optgroup->get_option("printhost_port");
option.opt.width = Field::def_width_wider();
@ -422,6 +529,38 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr
m_optgroup->append_line(line);
}
// Text line with info how passwords and api keys are stored
if (is_secret_store_ok()) {
Line line{ "", "" };
line.full_width = 1;
line.widget = [ca_file_hint](wxWindow* parent) {
wxString info = GUI::format_wxstr(L"%1%:\n\t%2%\n"
, _L("Storing passwords")
, GUI::format_wxstr(_L("On this system, %1% uses the system password store to safely store and read passwords and API keys."), SLIC3R_APP_NAME));
auto txt = new wxStaticText(parent, wxID_ANY, info);
txt->SetFont(wxGetApp().normal_font());
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(txt, 1, wxEXPAND);
return sizer;
};
m_optgroup->append_line(line);
} else {
Line line{ "", "" };
line.full_width = 1;
line.widget = [ca_file_hint](wxWindow* parent) {
wxString info = GUI::format_wxstr(L"%1%:\n\t%2%\n\t%3%\n"
, _L("Storing passwords")
, GUI::format_wxstr(_L("On this system, %1% cannot access the system password store."), SLIC3R_APP_NAME)
, _L("Passwords and API keys are stored in plain text."));
auto txt = new wxStaticText(parent, wxID_ANY, info);
txt->SetFont(wxGetApp().normal_font());
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(txt, 1, wxEXPAND);
return sizer;
};
m_optgroup->append_line(line);
}
for (const std::string& opt_key : std::vector<std::string>{ "printhost_user", "printhost_password" }) {
option = m_optgroup->get_option(opt_key);
option.opt.width = Field::def_width_wider();
@ -810,6 +949,13 @@ void PhysicalPrinterDialog::OnOK(wxEvent& event)
//update printer name, if it was changed
m_printer.set_name(into_u8(printer_name));
// save access data secretly
if (!m_printer.config.opt_string("printhost_password").empty()) {
if (save_secret(m_printer.name, "printhost_password", m_printer.config.opt_string("printhost_user"), m_printer.config.opt_string("printhost_password"))) {
m_printer.config.opt_string("printhost_password", false) = "stored";
}
}
// save new physical printer
printers.save_printer(m_printer, renamed_from);

View File

@ -76,6 +76,7 @@ class PhysicalPrinterDialog : public DPIDialog
ScalableButton* m_printhost_test_btn {nullptr};
ScalableButton* m_printhost_cafile_browse_btn {nullptr};
ScalableButton* m_printhost_port_browse_btn {nullptr};
ScalableButton* m_api_key_copy_btn {nullptr};
wxBoxSizer* m_presets_sizer {nullptr};

View File

@ -50,6 +50,9 @@
#include <wx/debug.h>
#include <wx/busyinfo.h>
#include <wx/stdpaths.h>
#if wxUSE_SECRETSTORE
#include <wx/secretstore.h>
#endif
#include <LibBGCode/convert/convert.hpp>
@ -5700,6 +5703,37 @@ void Plater::reslice_SLA_until_step(SLAPrintObjectStep step, const ModelObject &
this->reslice_until_step_inner(SLAPrintObjectStep(step), object, postpone_error_messages);
}
namespace {
bool load_secret(const std::string& id, const std::string& opt, std::string& usr, std::string& psswd)
{
#if wxUSE_SECRETSTORE
wxSecretStore store = wxSecretStore::GetDefault();
wxString errmsg;
if (!store.IsOk(&errmsg)) {
std::string msg = GUI::format("%1% (%2%).", _u8L("This system doesn't support storing passwords securely"), errmsg);
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
const wxString service = GUI::format_wxstr(L"%1%/PhysicalPrinter/%2%/%3%", SLIC3R_APP_NAME, id, opt);
wxString username;
wxSecretValue password;
if (!store.Load(service, username, password)) {
std::string msg(_u8L("Failed to load credentials from the system secret store."));
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return false;
}
usr = into_u8(username);
psswd = into_u8(password.GetAsString());
return true;
#else
BOOST_LOG_TRIVIAL(error) << "wxUSE_SECRETSTORE not supported. Cannot load password from the system store.";
return false;
#endif // wxUSE_SECRETSTORE
}
}
void Plater::send_gcode()
{
// if physical_printer is selected, send gcode for this printer
@ -5707,6 +5741,33 @@ void Plater::send_gcode()
if (! physical_printer_config || p->model.objects.empty())
return;
// Passwords and API keys
// "stored" indicates data are stored secretly, load them from store.
std::string printer_name = wxGetApp().preset_bundle->physical_printers.get_selected_printer().name;
if (physical_printer_config->opt_string("printhost_password") == "stored" && physical_printer_config->opt_string("printhost_password") == "stored") {
std::string username;
std::string password;
if (load_secret(printer_name, "printhost_password", username, password)) {
if (!username.empty())
physical_printer_config->opt_string("printhost_user") = username;
if (!password.empty())
physical_printer_config->opt_string("printhost_password") = password;
}
else {
physical_printer_config->opt_string("printhost_user") = std::string();
physical_printer_config->opt_string("printhost_password") = std::string();
}
}
/*
if (physical_printer_config->opt_string("printhost_apikey") == "stored") {
std::string username;
std::string password;
if (load_secret(printer_name, "printhost_apikey", username, password) && !password.empty())
physical_printer_config->opt_string("printhost_apikey") = password;
else
physical_printer_config->opt_string("printhost_apikey") = std::string();
}
*/
PrintHostJob upload_job(physical_printer_config);
if (upload_job.empty())
return;

View File

@ -0,0 +1,87 @@
#include "Secrets.hpp"
#include <stdio.h>
#if wxUSE_SECRETSTORE
#include <wx/secretstore.h>
#endif
namespace Slic3r {
#if wxUSE_SECRETSTORE
static bool PrintResult(bool ok)
{
wxPuts(ok ? "ok" : "ERROR");
return ok;
}
bool SelfTest(wxSecretStore& store, const wxString& service)
{
wxPrintf("Running the tests...\n");
const wxString userTest("test");
const wxSecretValue secret1(6, "secret");
wxPrintf("Storing the password:\t");
bool ok = store.Save(service, userTest, secret1);
if ( !PrintResult(ok) )
{
// The rest of the tests will probably fail too, no need to continue.
wxPrintf("Bailing out.\n");
return false;
}
wxPrintf("Loading the password:\t");
wxSecretValue secret;
wxString user;
ok = PrintResult(store.Load(service, user, secret) &&
user == userTest &&
secret == secret1);
// Overwriting the password should work.
const wxSecretValue secret2(6, "privet");
wxPrintf("Changing the password:\t");
if ( PrintResult(store.Save(service, user, secret2)) )
{
wxPrintf("Reloading the password:\t");
if ( !PrintResult(store.Load(service, user, secret) &&
secret == secret2) )
ok = false;
}
else
ok = false;
wxPrintf("Deleting the password:\t");
if ( !PrintResult(store.Delete(service)) )
ok = false;
// This is supposed to fail now.
wxPrintf("Deleting it again:\t");
if ( !PrintResult(!store.Delete(service)) )
ok = false;
// And loading should fail too.
wxPrintf("Loading after deleting:\t");
if ( !PrintResult(!store.Load(service, user, secret)) )
ok = false;
if ( ok )
wxPrintf("All tests passed!\n");
return ok;
}
#endif //wxUSE_SECRETSTORE
bool check_secrets()
{
#if wxUSE_SECRETSTORE
wxSecretStore store = wxSecretStore::GetDefault();
return SelfTest(store, "prusaslicer");
#else
printf("wxSecret not supported.\n");
return true;
#endif
}
} // namespace Slic3r

View File

@ -0,0 +1,10 @@
#ifndef PRUSASLICER_SECRETS_HPP
#define PRUSASLICER_SECRETS_HPP
namespace Slic3r {
bool check_secrets();
}
#endif // PRUSASLICER_SECRETS_HPP

View File

@ -4,10 +4,12 @@ add_executable(${_TEST_NAME}_tests
slic3r_jobs_tests.cpp
slic3r_version_tests.cpp
slic3r_arrangejob_tests.cpp
secretstore_tests.cpp
)
# mold linker for successful linking needs also to link TBB library and link it before libslic3r.
target_link_libraries(${_TEST_NAME}_tests test_common TBB::tbb TBB::tbbmalloc libslic3r_gui libslic3r)
if (MSVC)
target_link_libraries(${_TEST_NAME}_tests Setupapi.lib)
endif ()

View File

@ -0,0 +1,8 @@
#include "catch2/catch.hpp"
#include "slic3r/Utils/Secrets.hpp"
TEST_CASE("Secret store test", "[secretstore]") {
REQUIRE(Slic3r::check_secrets());
}