PrusaConnect communication with PrusaAuth login

Background communication with keeping tokens
Temporary output is notification
Temporary storing tokens in prusaslicer.ini

Using wx secret store to store tokens if possible

Improved PrusaConnect message - number of compatible models


Fix of saving refresh token


Refactoring of Auth and Auth Session classes.
This commit is contained in:
David Kocik 2023-07-25 15:52:38 +02:00
parent f624c55f6f
commit adc650ef4e
15 changed files with 1113 additions and 3 deletions

View File

@ -19,6 +19,10 @@ set(SLIC3R_GUI_SOURCES
GUI/AboutDialog.hpp
GUI/ArrangeSettingsDialogImgui.hpp
GUI/ArrangeSettingsDialogImgui.cpp
GUI/Auth.cpp
GUI/Auth.hpp
GUI/AuthSession.cpp
GUI/AuthSession.hpp
GUI/SysInfoDialog.cpp
GUI/SysInfoDialog.hpp
GUI/KBShortcutsDialog.cpp

479
src/slic3r/GUI/Auth.cpp Normal file
View File

@ -0,0 +1,479 @@
#include "Auth.hpp"
#include "GUI_App.hpp"
#include "format.hpp"
#include "../Utils/Http.hpp"
#include "I18N.hpp"
#include <boost/log/trivial.hpp>
#include <boost/regex.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/beast/core/detail/base64.hpp>
#include <curl/curl.h>
#include <string>
#include <iostream>
#include <random>
#include <algorithm>
#include <iterator>
#include <regex>
#include <iomanip>
#include <cstring>
#include <cstdint>
#if wxUSE_SECRETSTORE
#include <wx/secretstore.h>
#endif
#ifdef WIN32
#include <wincrypt.h>
#endif // WIN32
#ifdef __linux__
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#endif // __linux__
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
namespace Slic3r {
namespace GUI {
namespace {
std::string get_code_from_message(const std::string& url_message)
{
size_t pos = url_message.rfind("code=");
std::string out;
for (size_t i = pos + 5; i < url_message.size(); i++) {
const char& c = url_message[i];
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
out+= c;
else
break;
}
return out;
}
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& 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%/PrusaAuth/%2%", SLIC3R_APP_NAME, 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& 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%/PrusaAuth/%2%", SLIC3R_APP_NAME, 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
}
}
PrusaAuthCommunication::PrusaAuthCommunication(wxEvtHandler* evt_handler, AppConfig* app_config)
: m_evt_handler(evt_handler)
{
std::string access_token, refresh_token, shared_session_key;
if (is_secret_store_ok()) {
std::string key0, key1;
load_secret("access_token", key0, access_token);
load_secret("refresh_token", key1, refresh_token);
assert(key0 == key1);
shared_session_key = key0;
} else {
access_token = app_config->get("access_token");
refresh_token = app_config->get("refresh_token");
shared_session_key = app_config->get("shared_session_key");
}
if (!access_token.empty() || !refresh_token.empty())
m_remember_session = true;
m_session = std::make_unique<AuthSession>(evt_handler, access_token, refresh_token, shared_session_key);
init_session_thread();
// perform login at the start - do we want this
if (m_remember_session)
login();
}
PrusaAuthCommunication::~PrusaAuthCommunication() {
if (m_thread.joinable()) {
// Stop the worker thread, if running.
{
// Notify the worker thread to cancel wait on detection polling.
std::lock_guard<std::mutex> lck(m_thread_stop_mutex);
m_thread_stop = true;
}
m_thread_stop_condition.notify_all();
// Wait for the worker thread to stop.
m_thread.join();
}
}
void PrusaAuthCommunication::set_username(const std::string& username, AppConfig* app_config)
{
m_username = username;
{
std::lock_guard<std::mutex> lock(m_session_mutex);
if (is_secret_store_ok()) {
save_secret("access_token", m_session->get_shared_session_key(), m_remember_session ? m_session->get_access_token() : std::string());
save_secret("refresh_token", m_session->get_shared_session_key(), m_remember_session ? m_session->get_refresh_token() : std::string());
}
else {
app_config->set("access_token", m_remember_session ? m_session->get_access_token() : std::string());
app_config->set("refresh_token", m_remember_session ? m_session->get_refresh_token() : std::string());
app_config->set("shared_session_key", m_remember_session ? m_session->get_shared_session_key() : std::string());
}
}
}
void PrusaAuthCommunication::login_redirect()
{
const std::string AUTH_HOST = "https://test-account.prusa3d.com";
const std::string CLIENT_ID = client_id();
const std::string REDIRECT_URI = "prusaslicer://login";
CodeChalengeGenerator ccg;
m_code_verifier = ccg.generate_verifier();
std::string code_challenge = ccg.generate_chalenge(m_code_verifier);
BOOST_LOG_TRIVIAL(info) << "code verifier: " << m_code_verifier;
BOOST_LOG_TRIVIAL(info) << "code challenge: " << code_challenge;
wxString url = GUI::format_wxstr(L"%1%/o/authorize/?client_id=%2%&response_type=code&code_challenge=%3%&code_challenge_method=S256&scope=basic_info&redirect_uri=%4%", AUTH_HOST, CLIENT_ID, code_challenge, REDIRECT_URI);
wxQueueEvent(m_evt_handler,new OpenPrusaAuthEvent(GUI::EVT_OPEN_PRUSAAUTH, std::move(url)));
}
bool PrusaAuthCommunication::is_logged()
{
return !m_username.empty();
}
void PrusaAuthCommunication::login()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
if (!m_session->is_initialized()) {
login_redirect();
//return;
}
m_session->enqueue_action(UserActionID::USER_ID, nullptr, nullptr, {});
}
wakeup_session_thread();
}
void PrusaAuthCommunication::logout()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
m_session->clear();
}
wxQueueEvent(m_evt_handler, new PrusaAuthSuccessEvent(GUI::EVT_LOGGEDOUT_PRUSAAUTH, {}));
}
void PrusaAuthCommunication::on_login_code_recieved(const std::string& url_message)
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
const std::string code = get_code_from_message(url_message);
m_session->init_with_code(code, m_code_verifier);
}
wakeup_session_thread();
}
void PrusaAuthCommunication::enqueue_user_id_action()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
if (!m_session->is_initialized()) {
//login_redirect();
return;
}
m_session->enqueue_action(UserActionID::USER_ID, nullptr, nullptr, {});
}
wakeup_session_thread();
}
void PrusaAuthCommunication::enqueue_connect_dummy_action()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
if (!m_session->is_initialized()) {
BOOST_LOG_TRIVIAL(error) << "Connect Dummy endpoint connection failed - Not Logged in.";
return;
}
m_session->enqueue_action(UserActionID::CONNECT_DUMMY, nullptr, nullptr, {});
}
wakeup_session_thread();
}
void PrusaAuthCommunication::enqueue_connect_printers_action()
{
{
std::lock_guard<std::mutex> lock(m_session_mutex);
if (!m_session->is_initialized()) {
BOOST_LOG_TRIVIAL(error) << "Connect Printers endpoint connection failed - Not Logged in.";
return;
}
m_session->enqueue_action(UserActionID::CONNECT_PRINTERS, nullptr, nullptr, {});
}
wakeup_session_thread();
}
void PrusaAuthCommunication::init_session_thread()
{
m_thread = std::thread([this]() {
for (;;) {
// Wait for 1 second
// Cancellable.
{
std::unique_lock<std::mutex> lck(m_thread_stop_mutex);
m_thread_stop_condition.wait_for(lck, std::chrono::seconds(1), [this] { return m_thread_stop; });
}
if (m_thread_stop)
// Stop the worker thread.
break;
{
std::lock_guard<std::mutex> lock(m_session_mutex);
m_session->process_action_queue();
}
}
});
}
void PrusaAuthCommunication::wakeup_session_thread()
{
m_thread_stop_condition.notify_all();
}
namespace {
/*
void proccess_tree(const pt::ptree& tree, const std::string depth, std::string& out)
{
for (const auto& section : tree) {
printf("%s%s", depth.c_str(), section.first.c_str());
if (!section.second.data().empty()) {
if (section.first == "printer_type_name") {
out += section.second.data();
out += " : ";
} else if (section.first == "state") {
out += section.second.data();
out += "\n";
}
printf(" : %s\n", section.second.data().c_str());
} else {
printf("\n");
proccess_tree(section.second, depth + " ", out);
}
}
}
*/
typedef std::map<std::string, int> ModelCounter;
void proccess_tree(const pt::ptree& tree, const std::string depth, ModelCounter& models)
{
for (const auto& section : tree) {
//printf("%s%s", depth.c_str(), section.first.c_str());
if (!section.second.data().empty()) {
//printf(" : %s\n", section.second.data().c_str());
}
else {
if (section.first == "printer_type_compatible") {
for (const auto& sub : section.second) {
if (!sub.second.data().empty()) {
//printf(" : %s\n", section.second.data().c_str());
if(models.find(sub.second.data()) == models.end())
models.emplace(sub.second.data(), 1);
else
models[sub.second.data()]++;
}
}
} else {
//printf("\n");
proccess_tree(section.second, depth + " ", models);
}
}
}
}
}
std::string PrusaAuthCommunication::proccess_prusaconnect_printers_message(const std::string& message)
{
std::string out;
try {
std::stringstream ss(message);
pt::ptree ptree;
pt::read_json(ss, ptree);
ModelCounter counter;
proccess_tree(ptree, "", counter);
for (const auto model : counter)
{
out += GUI::format("%1%x %2%\n", std::to_string(model.second), model.first);
}
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse prusaconnect message. " << e.what();
}
return out;
}
std::string CodeChalengeGenerator::generate_chalenge(const std::string& verifier)
{
std::string code_challenge;
try
{
code_challenge = sha256(verifier);
code_challenge = base64_encode(code_challenge);
}
catch (const std::exception& e)
{
BOOST_LOG_TRIVIAL(error) << "Code Chalenge Generator failed: " << e.what();
}
assert(!code_challenge.empty());
return code_challenge;
}
std::string CodeChalengeGenerator::generate_verifier()
{
size_t length = 40;
std::string code_verifier = generate_code_verifier(length);
assert(code_verifier.size() == length);
return code_verifier;
}
std::string CodeChalengeGenerator::base64_encode(const std::string& input)
{
std::string output;
output.resize(boost::beast::detail::base64::encoded_size(input.size()));
boost::beast::detail::base64::encode(&output[0], input.data(), input.size());
// save encode - replace + and / with - and _
std::replace(output.begin(), output.end(), '+', '-');
std::replace(output.begin(), output.end(), '/', '_');
// remove last '=' sign
while (output.back() == '=')
output.pop_back();
return output;
}
std::string CodeChalengeGenerator::generate_code_verifier(size_t length)
{
const std::string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> distribution(0, chars.size() - 1);
std::string code_verifier;
for (int i = 0; i < length; ++i) {
code_verifier += chars[distribution(gen)];
}
return code_verifier;
}
#ifdef WIN32
std::string CodeChalengeGenerator::sha256(const std::string& input)
{
HCRYPTPROV prov_handle = NULL;
HCRYPTHASH hash_handle = NULL;
std::vector<BYTE> buffer(1024);
DWORD hash_size = 0;
DWORD buffer_size = sizeof(DWORD);
std::string output;
if (!CryptAcquireContext(&prov_handle, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
throw std::exception("CryptAcquireContext failed.");
}
if (!CryptCreateHash(prov_handle, CALG_SHA_256, 0, 0, &hash_handle)) {
CryptReleaseContext(prov_handle, 0);
throw std::exception("CryptCreateHash failed.");
}
if (!CryptHashData(hash_handle, reinterpret_cast<const BYTE*>(input.c_str()), input.length(), 0)) {
CryptDestroyHash(hash_handle);
CryptReleaseContext(prov_handle, 0);
throw std::exception("CryptCreateHash failed.");
}
if (!CryptGetHashParam(hash_handle, HP_HASHSIZE, reinterpret_cast<BYTE*>(&hash_size), &buffer_size, 0)) {
CryptDestroyHash(hash_handle);
CryptReleaseContext(prov_handle, 0);
throw std::exception("CryptGetHashParam HP_HASHSIZE failed.");
}
output.resize(hash_size);
if (!CryptGetHashParam(hash_handle, HP_HASHVAL, reinterpret_cast<BYTE*>(&output[0]), &hash_size, 0)) {
CryptDestroyHash(hash_handle);
CryptReleaseContext(prov_handle, 0);
throw std::exception("CryptGetHashParam HP_HASHVAL failed.");
}
return output;
}
#endif // WIN32
#ifdef __linux__
std::string CodeChalengeGenerator::sha256(const std::string& input) {
EVP_MD_CTX* mdctx;
const EVP_MD* md;
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int digestLen;
md = EVP_sha256();
mdctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(mdctx, md, NULL);
EVP_DigestUpdate(mdctx, input.c_str(), input.length());
EVP_DigestFinal_ex(mdctx, digest, &digestLen);
EVP_MD_CTX_free(mdctx);
return std::string(reinterpret_cast<char*>(digest), digestLen);
}
#endif // __linux__
}} // Slic3r::GUI

79
src/slic3r/GUI/Auth.hpp Normal file
View File

@ -0,0 +1,79 @@
#ifndef slic3r_Auth_hpp_
#define slic3r_Auth_hpp_
#include "AuthSession.hpp"
#include "Event.hpp"
#include "libslic3r/AppConfig.hpp"
#include <queue>
#include <map>
#include <thread>
#include <mutex>
#include <memory>
namespace Slic3r {
namespace GUI {
class CodeChalengeGenerator
{
public:
CodeChalengeGenerator() {}
~CodeChalengeGenerator() {}
std::string generate_chalenge(const std::string& verifier);
std::string generate_verifier();
private:
std::string generate_code_verifier(size_t length);
std::string base64_encode(const std::string& input);
std::string sha256(const std::string& input);
};
class PrusaAuthCommunication {
public:
PrusaAuthCommunication(wxEvtHandler* evt_handler, AppConfig* app_config);
~PrusaAuthCommunication();
// UI Session thread Interface
//
bool is_logged();
void login();
void logout();
// Trigger function starts various remote operations
// Each user action is implemented in different UserAction class and stored in m_actions.
void enqueue_user_id_action();
void enqueue_connect_dummy_action();
void enqueue_connect_printers_action();
void set_remember_session(bool b) { m_remember_session = b; }
// Callbacks - called from UI after receiving Event from Session thread. Some might use Session thread.
//
// Called when browser returns code via prusaslicer:// custom url.
// Exchanges code for tokens and shared_session_key
void on_login_code_recieved(const std::string& url_message);
void set_username(const std::string& username, AppConfig* app_config);
std::string get_username() const { return m_username; }
std::string proccess_prusaconnect_printers_message(const std::string& message);
private:
std::unique_ptr<AuthSession> m_session;
std::thread m_thread;
std::mutex m_session_mutex;
std::mutex m_thread_stop_mutex;
std::condition_variable m_thread_stop_condition;
bool m_thread_stop { false };
std::string m_code_verifier;
wxEvtHandler* m_evt_handler;
// if not empty - user is logged in
std::string m_username;
bool m_remember_session {false};
void wakeup_session_thread();
void init_session_thread();
void login_redirect();
std::string client_id() const { return "UfTRUm5QjWwaQEGpWQBHGHO3reAyuzgOdBaiqO52"; }
};
}
}
#endif

View File

@ -0,0 +1,232 @@
#include "AuthSession.hpp"
#include "GUI_App.hpp"
#include "format.hpp"
#include "../Utils/Http.hpp"
#include "I18N.hpp"
#include <boost/log/trivial.hpp>
#include <boost/regex.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/beast/core/detail/base64.hpp>
#include <curl/curl.h>
#include <string>
namespace fs = boost::filesystem;
namespace pt = boost::property_tree;
namespace Slic3r {
namespace GUI {
wxDEFINE_EVENT(EVT_OPEN_PRUSAAUTH, OpenPrusaAuthEvent);
wxDEFINE_EVENT(EVT_LOGGEDOUT_PRUSAAUTH, PrusaAuthSuccessEvent);
wxDEFINE_EVENT(EVT_PA_ID_USER_SUCCESS, PrusaAuthSuccessEvent);
wxDEFINE_EVENT(EVT_PA_ID_USER_FAIL, PrusaAuthFailEvent);
wxDEFINE_EVENT(EVT_PRUSAAUTH_SUCCESS, PrusaAuthSuccessEvent);
wxDEFINE_EVENT(EVT_PRUSACONNECT_PRINTERS_SUCCESS, PrusaAuthSuccessEvent);
wxDEFINE_EVENT(EVT_PRUSAAUTH_FAIL, PrusaAuthFailEvent);
void UserActionPost::perform(const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input)
{
std::string url = m_url;
BOOST_LOG_TRIVIAL(info) << m_action_name <<" POST " << url << " body: " << input;
auto http = Http::post(std::move(url));
if (!input.empty())
http.set_post_body(input);
http.header("Content-type", "application/x-www-form-urlencoded");
http.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << m_action_name << " action failed. status: " << status << " Body: " << body;
if (fail_callback)
fail_callback(body);
});
http.on_complete([&, this](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(info) << m_action_name << "action success. Status: " << status << " Body: " << body;
if (success_callback)
success_callback(body);
});
http.perform_sync();
}
void UserActionGetWithEvent::perform(const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, /*UNUSED*/ const std::string& input)
{
std::string url = m_url;
BOOST_LOG_TRIVIAL(info) << m_action_name << " GET " << url;
auto http = Http::get(url);
http.header("Authorization", "Bearer " + access_token);
http.on_error([&](std::string body, std::string error, unsigned status) {
BOOST_LOG_TRIVIAL(error) << m_action_name << " action failed. status: " << status << " Body: " << body;
if (fail_callback)
fail_callback(body);
std::string message = GUI::format("%1% action failed (%2%): %3%", m_action_name, std::to_string(status), body);
wxQueueEvent(m_evt_handler, new PrusaAuthFailEvent(m_fail_evt_type, std::move(message)));
});
http.on_complete([&, this](std::string body, unsigned status) {
BOOST_LOG_TRIVIAL(info) << m_action_name << " action success. Status: " << status << " Body: " << body;
if (success_callback)
success_callback(body);
wxQueueEvent(m_evt_handler, new PrusaAuthSuccessEvent(m_succ_evt_type, body));
});
http.perform_sync();
}
void AuthSession::process_action_queue()
{
if (m_priority_action_queue.empty() && m_action_queue.empty())
return;
if (this->is_initialized()) {
// if priority queue already has some action f.e. to exchange tokens, the test should not be neccessary but also shouldn't be problem
enqueue_test_with_refresh();
}
while (!m_priority_action_queue.empty()) {
m_actions[m_priority_action_queue.front().action_id]->perform(m_access_token, m_priority_action_queue.front().success_callback, m_priority_action_queue.front().fail_callback, m_priority_action_queue.front().input);
if (!m_priority_action_queue.empty())
m_priority_action_queue.pop();
}
if (!this->is_initialized())
return;
while (!m_action_queue.empty()) {
m_actions[m_action_queue.front().action_id]->perform(m_access_token, m_action_queue.front().success_callback, m_action_queue.front().fail_callback, m_action_queue.front().input);
if (!m_action_queue.empty())
m_action_queue.pop();
}
}
void AuthSession::enqueue_action(UserActionID id, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input)
{
m_action_queue.push({ id, success_callback, fail_callback, input });
}
void AuthSession::init_with_code(const std::string& code, const std::string& code_verifier)
{
// Data we have
const std::string REDIRECT_URI = "prusaslicer://login";
std::string post_fields = "code=" + code +
"&client_id=" + client_id() +
"&grant_type=authorization_code" +
"&redirect_uri=" + REDIRECT_URI +
"&code_verifier="+ code_verifier;
auto succ_fn = [&](const std::string& body){
// Data we need
std::string access_token, refresh_token, shared_session_key;
try {
std::stringstream ss(body);
pt::ptree ptree;
pt::read_json(ss, ptree);
const auto access_token_optional = ptree.get_optional<std::string>("access_token");
const auto refresh_token_optional = ptree.get_optional<std::string>("refresh_token");
const auto shared_session_key_optional = ptree.get_optional<std::string>("shared_session_key");
if (access_token_optional)
access_token = *access_token_optional;
if (refresh_token_optional)
refresh_token = *refresh_token_optional;
if (shared_session_key_optional)
shared_session_key = *shared_session_key_optional;
}
catch (const std::exception&) {
BOOST_LOG_TRIVIAL(error) << "Auth::http_access Could not parse server response.";
}
assert(!access_token.empty() && !refresh_token.empty() && !shared_session_key.empty());
if (access_token.empty() || refresh_token.empty() || shared_session_key.empty()) {
BOOST_LOG_TRIVIAL(error) << "Refreshing token has failed.";
m_access_token = std::string();
m_refresh_token = std::string();
m_shared_session_key = std::string();
return;
}
BOOST_LOG_TRIVIAL(info) << "access_token: " << access_token;
BOOST_LOG_TRIVIAL(info) << "refresh_token: " << refresh_token;
BOOST_LOG_TRIVIAL(info) << "shared_session_key: " << shared_session_key;
m_access_token = access_token;
m_refresh_token = refresh_token;
m_shared_session_key = shared_session_key;
};
// fail fn might be cancel_queue here
m_priority_action_queue.push({ UserActionID::CODE_FOR_TOKEN, succ_fn, std::bind(&AuthSession::enqueue_refresh, this, std::placeholders::_1), post_fields });
}
void AuthSession::enqueue_test_with_refresh()
{
// on test fail - try refresh
m_priority_action_queue.push({ UserActionID::TEST_CONNECTION, nullptr, std::bind(&AuthSession::enqueue_refresh, this, std::placeholders::_1), {} });
}
void AuthSession::enqueue_refresh(const std::string& body)
{
std::string post_fields = "grant_type=refresh_token"
"&client_id=" + client_id() +
"&refresh_token=" + m_refresh_token;
auto succ_callback = [&](const std::string& body){
std::string new_access_token;
std::string new_refresh_token;
std::string new_shared_session_key;
try {
std::stringstream ss(body);
pt::ptree ptree;
pt::read_json(ss, ptree);
const auto access_token_optional = ptree.get_optional<std::string>("access_token");
const auto refresh_token_optional = ptree.get_optional<std::string>("refresh_token");
const auto shared_session_key_optional = ptree.get_optional<std::string>("shared_session_key");
if (access_token_optional)
new_access_token = *access_token_optional;
if (refresh_token_optional)
new_refresh_token = *refresh_token_optional;
if (shared_session_key_optional)
new_shared_session_key = *shared_session_key_optional;
}
catch (const std::exception&) {
BOOST_LOG_TRIVIAL(error) << "Could not parse server response.";
}
assert(!new_access_token.empty() && !new_refresh_token.empty() && !new_shared_session_key.empty());
if (new_access_token.empty() || new_refresh_token.empty() || new_shared_session_key.empty()) {
BOOST_LOG_TRIVIAL(error) << "Refreshing token has failed.";
m_access_token = std::string();
m_refresh_token = std::string();
m_shared_session_key = std::string();
// TODO: cancel following queue
}
BOOST_LOG_TRIVIAL(info) << "access_token: " << new_access_token;
BOOST_LOG_TRIVIAL(info) << "refresh_token: " << new_refresh_token;
BOOST_LOG_TRIVIAL(info) << "shared_session_key: " << new_shared_session_key;
m_access_token = new_access_token;
m_refresh_token = new_refresh_token;
m_shared_session_key = new_shared_session_key;
m_priority_action_queue.push({ UserActionID::TEST_CONNECTION, nullptr, std::bind(&AuthSession::refresh_failed_callback, this, std::placeholders::_1), {} });
};
m_priority_action_queue.push({ UserActionID::REFRESH_TOKEN, succ_callback, std::bind(&AuthSession::refresh_failed_callback, this, std::placeholders::_1), post_fields });
}
void AuthSession::refresh_failed_callback(const std::string& body)
{
clear();
cancel_queue();
}
void AuthSession::cancel_queue()
{
while (!m_priority_action_queue.empty()) {
m_priority_action_queue.pop();
}
while (!m_action_queue.empty()) {
m_action_queue.pop();
}
}
}} // Slic3r::GUI

View File

@ -0,0 +1,152 @@
#ifndef slic3r_AuthSession_hpp_
#define slic3r_AuthSession_hpp_
#include "Event.hpp"
#include "libslic3r/AppConfig.hpp"
#include <queue>
#include <map>
#include <thread>
#include <mutex>
#include <memory>
namespace Slic3r {
namespace GUI {
using OpenPrusaAuthEvent = Event<wxString>;
using PrusaAuthSuccessEvent = Event<std::string>;
using PrusaAuthFailEvent = Event<std::string>;
wxDECLARE_EVENT(EVT_OPEN_PRUSAAUTH, OpenPrusaAuthEvent);
wxDECLARE_EVENT(EVT_LOGGEDOUT_PRUSAAUTH, PrusaAuthSuccessEvent);
wxDECLARE_EVENT(EVT_PA_ID_USER_SUCCESS, PrusaAuthSuccessEvent);
wxDECLARE_EVENT(EVT_PA_ID_USER_FAIL, PrusaAuthFailEvent);
wxDECLARE_EVENT(EVT_PRUSAAUTH_SUCCESS, PrusaAuthSuccessEvent);
wxDECLARE_EVENT(EVT_PRUSACONNECT_PRINTERS_SUCCESS, PrusaAuthSuccessEvent);
wxDECLARE_EVENT(EVT_PRUSAAUTH_FAIL, PrusaAuthFailEvent);
typedef std::function<void(const std::string& body)> UserActionSuccessFn;
typedef std::function<void(const std::string& body)> UserActionFailFn;
// UserActions implements different operations via trigger() method. Stored in m_actions.
enum class UserActionID {
DUMMY_ACTION,
REFRESH_TOKEN,
CODE_FOR_TOKEN,
TEST_CONNECTION,
USER_ID,
CONNECT_DUMMY,
CONNECT_PRINTERS,
};
class UserAction
{
public:
UserAction(const std::string name, const std::string url) : m_action_name(name), m_url(url){}
~UserAction() {}
virtual void perform(const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) = 0;
protected:
std::string m_action_name;
std::string m_url;
};
class UserActionGetWithEvent : public UserAction
{
public:
UserActionGetWithEvent(const std::string name, const std::string url, wxEvtHandler* evt_handler, wxEventType succ_event_type, wxEventType fail_event_type)
: m_succ_evt_type(succ_event_type)
, m_fail_evt_type(fail_event_type)
, m_evt_handler(evt_handler)
, UserAction(name, url)
{}
~UserActionGetWithEvent() {}
void perform(const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) override;
private:
wxEventType m_succ_evt_type;
wxEventType m_fail_evt_type;
wxEvtHandler* m_evt_handler;
};
class UserActionPost : public UserAction
{
public:
UserActionPost(const std::string name, const std::string url) : UserAction(name, url) {}
~UserActionPost() {}
void perform(const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) override;
};
class DummyUserAction : public UserAction
{
public:
DummyUserAction() : UserAction("Dummy", {}) {}
~DummyUserAction() {}
void perform(const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) override { }
};
struct ActionQueueData
{
UserActionID action_id;
UserActionSuccessFn success_callback;
UserActionFailFn fail_callback;
std::string input;
};
class AuthSession
{
public:
AuthSession(wxEvtHandler* evt_handler, const std::string& access_token, const std::string& refresh_token, const std::string& shared_session_key)
: m_access_token(access_token)
, m_refresh_token(refresh_token)
, m_shared_session_key(shared_session_key)
{
// do not forget to add delete to destructor
m_actions[UserActionID::DUMMY_ACTION] = std::make_unique<DummyUserAction>();
m_actions[UserActionID::REFRESH_TOKEN] = std::make_unique<UserActionPost>("EXCHANGE_TOKENS", "https://test-account.prusa3d.com/o/token/");
m_actions[UserActionID::CODE_FOR_TOKEN] = std::make_unique<UserActionPost>("EXCHANGE_TOKENS", "https://test-account.prusa3d.com/o/token/");
m_actions[UserActionID::TEST_CONNECTION] = std::make_unique<UserActionGetWithEvent>("TEST_CONNECTION", "https://test-account.prusa3d.com/api/v1/me/", evt_handler, EVT_PA_ID_USER_SUCCESS, EVT_PRUSAAUTH_FAIL);
m_actions[UserActionID::USER_ID] = std::make_unique<UserActionGetWithEvent>("USER_ID", "https://test-account.prusa3d.com/api/v1/me/", evt_handler, EVT_PA_ID_USER_SUCCESS, EVT_PRUSAAUTH_FAIL);
m_actions[UserActionID::CONNECT_DUMMY] = std::make_unique<UserActionGetWithEvent>("CONNECT_DUMMY", "dev.connect.prusa:8000/slicer/dummy", evt_handler, EVT_PRUSAAUTH_SUCCESS, EVT_PRUSAAUTH_FAIL);
m_actions[UserActionID::CONNECT_PRINTERS] = std::make_unique<UserActionGetWithEvent>("CONNECT_PRINTERS", "dev.connect.prusa:8000/slicer/printers", evt_handler, EVT_PRUSACONNECT_PRINTERS_SUCCESS, EVT_PRUSAAUTH_FAIL);
}
~AuthSession()
{
m_actions[UserActionID::DUMMY_ACTION].reset(nullptr);
m_actions[UserActionID::REFRESH_TOKEN].reset(nullptr);
m_actions[UserActionID::CODE_FOR_TOKEN].reset(nullptr);
m_actions[UserActionID::TEST_CONNECTION].reset(nullptr);
m_actions[UserActionID::USER_ID].reset(nullptr);
m_actions[UserActionID::CONNECT_DUMMY].reset(nullptr);
m_actions[UserActionID::CONNECT_PRINTERS].reset(nullptr);
//assert(m_actions.empty());
}
void clear() {
m_access_token.clear();
m_refresh_token.clear();
m_shared_session_key.clear();
}
void init_with_code(const std::string& code, const std::string& code_verifier);
void process_action_queue();
void enqueue_action(UserActionID id, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input);
bool is_initialized() { return !m_access_token.empty() || !m_refresh_token.empty(); }
std::string get_access_token() const { return m_access_token; }
std::string get_refresh_token() const { return m_refresh_token; }
std::string get_shared_session_key() const { return m_shared_session_key; }
private:
void enqueue_test_with_refresh();
void enqueue_refresh(const std::string& body);
void refresh_failed_callback(const std::string& body);
void cancel_queue();
std::string client_id() const { return "UfTRUm5QjWwaQEGpWQBHGHO3reAyuzgOdBaiqO52"; }
std::string m_access_token;
std::string m_refresh_token;
std::string m_shared_session_key;
std::queue<ActionQueueData> m_action_queue;
std::queue<ActionQueueData> m_priority_action_queue;
std::map<UserActionID, std::unique_ptr<UserAction>> m_actions;
};
}
}
#endif

View File

@ -98,6 +98,7 @@
#include "Downloader.hpp"
#include "PhysicalPrinterDialog.hpp"
#include "WifiConfigDialog.hpp"
#include "Auth.hpp"
#include "BitmapCache.hpp"
#include "Notebook.hpp"
@ -2494,6 +2495,12 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _L("Take Configuration &Snapshot"), _L("Capture a configuration snapshot"));
local_menu->Append(config_id_base + ConfigMenuUpdateConf, _L("Check for Configuration Updates"), _L("Check for configuration updates"));
local_menu->Append(config_id_base + ConfigMenuUpdateApp, _L("Check for Application Updates"), _L("Check for new version of application"));
local_menu->AppendSeparator();
wxMenuItem* updatable_item = local_menu->Append(config_id_base + ConfigMenuAuthLogin, _L("PrusaAuth Log in"), _L(""));
m_config_menu_updatable_items.emplace(ConfigMenuIDs::ConfigMenuAuthLogin, updatable_item);
updatable_item = local_menu->Append(config_id_base + ConfigMenuConnectDummy, _L("PrusaConnect Printers"), _L(""));
updatable_item->Enable(false);
m_config_menu_updatable_items.emplace(ConfigMenuIDs::ConfigMenuConnectDummy, updatable_item);
#if defined(__linux__) && defined(SLIC3R_DESKTOP_INTEGRATION)
//if (DesktopIntegrationDialog::integration_possible())
local_menu->Append(config_id_base + ConfigMenuDesktopIntegration, _L("Desktop Integration"), _L("Desktop Integration"));
@ -2542,6 +2549,19 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
case ConfigMenuUpdateApp:
app_version_check(true);
break;
case ConfigMenuAuthLogin:
{
if (this->plater()->get_auth_communication()->is_logged())
this->plater()->get_auth_communication()->logout();
else
this->plater()->get_auth_communication()->login();
}
break;
case ConfigMenuConnectDummy:
{
this->plater()->get_auth_communication()->enqueue_connect_printers_action();
}
break;
#ifdef __linux__
case ConfigMenuDesktopIntegration:
show_desktop_integration_dialog();
@ -2657,7 +2677,11 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
menu->Append(local_menu, _L("&Configuration"));
}
void GUI_App::update_config_menu()
{
m_config_menu_updatable_items[ConfigMenuIDs::ConfigMenuAuthLogin]->SetItemLabel(this->plater()->get_auth_communication()->is_logged() ? _L("PrusaAuth Log out") : _L("PrusaAuth Log in"));
m_config_menu_updatable_items[ConfigMenuIDs::ConfigMenuConnectDummy]->Enable(this->plater()->get_auth_communication()->is_logged());
}
void GUI_App::open_preferences(const std::string& highlight_option /*= std::string()*/, const std::string& tab_name/*= std::string()*/)
{
mainframe->preferences_dialog->show(highlight_option, tab_name);
@ -3431,6 +3455,21 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* pa
return launch && wxLaunchDefaultBrowser(url, flags);
}
bool GUI_App::open_login_browser_with_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, int flags/* = 0*/)
{
bool launch = true;
// warning dialog containes a "Remember my choice" checkbox
std::string option_key = "suppress_hyperlinks";
RichMessageDialog dialog(parent, _L("Open Log in page in default browser?"), _L("PrusaSlicer: Open Log in page"), wxICON_QUESTION | wxYES_NO);
dialog.ShowCheckBox(_L("Remember me"), true);
auto answer = dialog.ShowModal();
launch = answer == wxID_YES;
plater()->get_auth_communication()->set_remember_session(dialog.IsCheckBoxChecked());
return launch && wxLaunchDefaultBrowser(url, flags);
}
// static method accepting a wxWindow object as first parameter
// void warning_catcher{
// my($self, $message_dialog) = @_;

View File

@ -98,6 +98,8 @@ enum ConfigMenuIDs {
ConfigMenuTakeSnapshot,
ConfigMenuUpdateConf,
ConfigMenuUpdateApp,
ConfigMenuAuthLogin,
ConfigMenuConnectDummy,
ConfigMenuDesktopIntegration,
ConfigMenuPreferences,
ConfigMenuModeSimple,
@ -294,6 +296,7 @@ public:
void update_mode();
void add_config_menu(wxMenuBar *menu);
void update_config_menu();
bool has_unsaved_preset_changes() const;
bool has_current_preset_changes() const;
void update_saved_preset_from_current_preset();
@ -317,6 +320,7 @@ public:
// Calls wxLaunchDefaultBrowser if user confirms in dialog.
// Add "Rememeber my choice" checkbox to question dialog, when it is forced or a "suppress_hyperlinks" option has empty value
bool open_browser_with_warning_dialog(const wxString& url, wxWindow* parent = nullptr, bool force_remember_choice = true, int flags = 0);
bool open_login_browser_with_dialog(const wxString& url, wxWindow* parent = nullptr, int flags = 0);
#ifdef __APPLE__
void OSXStoreOpenFiles(const wxArrayString &files) override;
// wxWidgets override to get an event on open files.
@ -419,6 +423,10 @@ private:
void app_version_check(bool from_user);
bool m_wifi_config_dialog_shown { false };
bool m_wifi_config_dialog_was_declined { false };
// change to vector of items when adding more items that require update
//wxMenuItem* m_login_config_menu_item { nullptr };
std::map< ConfigMenuIDs, wxMenuItem*> m_config_menu_updatable_items;
};
DECLARE_APP(GUI_App)

View File

@ -378,6 +378,7 @@ namespace GUI {
wxDEFINE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent);
wxDEFINE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEvent);
wxDEFINE_EVENT(EVT_LOGIN_OTHER_INSTANCE, LoginOtherInstanceEvent);
wxDEFINE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent);
void OtherInstanceMessageHandler::init(wxEvtHandler* callback_evt_handler)
@ -520,6 +521,9 @@ void OtherInstanceMessageHandler::handle_message(const std::string& message)
else if (it->rfind("prusaslicer://open?file=", 0) == 0)
#endif
downloads.emplace_back(*it);
else if (it->rfind("prusaslicer://login", 0) == 0) {
wxPostEvent(m_callback_evt_handler, LoginOtherInstanceEvent(GUI::EVT_LOGIN_OTHER_INSTANCE, std::string(*it)));
}
}
if (! paths.empty()) {
//wxEvtHandler* evt_handler = wxGetApp().plater(); //assert here?

View File

@ -48,8 +48,10 @@ class MainFrame;
using LoadFromOtherInstanceEvent = Event<std::vector<boost::filesystem::path>>;
using StartDownloadOtherInstanceEvent = Event<std::vector<std::string>>;
using LoginOtherInstanceEvent = Event<std::string>;
wxDECLARE_EVENT(EVT_LOAD_MODEL_OTHER_INSTANCE, LoadFromOtherInstanceEvent);
wxDECLARE_EVENT(EVT_START_DOWNLOAD_OTHER_INSTANCE, StartDownloadOtherInstanceEvent);
wxDECLARE_EVENT(EVT_LOGIN_OTHER_INSTANCE, LoginOtherInstanceEvent);
using InstanceGoToFrontEvent = SimpleEvent;
wxDECLARE_EVENT(EVT_INSTANCE_GO_TO_FRONT, InstanceGoToFrontEvent);

View File

@ -124,6 +124,7 @@ class MainFrame : public DPIFrame
miSend, // Send G-code Send to print
miMaterialTab, // Filament Settings Material Settings
miPrinterTab, // Different bitmap for Printer Settings
miLogin,
};
// vector of a MenuBar items changeable in respect to printer technology

View File

@ -130,6 +130,8 @@ enum class NotificationType
URLNotRegistered,
// Config file was detected during startup, open wifi config dialog via hypertext
WifiConfigFileDetected
//
PrusaAuthUserID,
};
class NotificationManager

View File

@ -119,6 +119,8 @@
#include "Gizmos/GLGizmoSVG.hpp" // Drop SVG file
#include "Gizmos/GLGizmoCut.hpp"
#include "FileArchiveDialog.hpp"
#include "Auth.hpp"
#include "DesktopIntegrationDialog.hpp"
#ifdef __APPLE__
#include "Gizmos/GLGizmosManager.hpp"
@ -262,6 +264,7 @@ struct Plater::priv
GLToolbar collapse_toolbar;
Preview *preview;
std::unique_ptr<NotificationManager> notification_manager;
std::unique_ptr<PrusaAuthCommunication> auth_communication;
ProjectDirtyStateManager dirty_state;
@ -608,6 +611,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
}))
, sidebar(new Sidebar(q))
, notification_manager(std::make_unique<NotificationManager>(q))
, auth_communication(std::make_unique<PrusaAuthCommunication>(q, wxGetApp().app_config))
, m_worker{q, std::make_unique<NotificationProgressIndicator>(notification_manager.get()), "ui_worker"}
, m_sla_import_dlg{new SLAImportDialog{q}}
, delayed_scene_refresh(false)
@ -853,11 +857,75 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
wxGetApp().start_download(evt.data[i]);
}
});
this->q->Bind(EVT_LOGIN_OTHER_INSTANCE, [this](LoginOtherInstanceEvent& evt) {
BOOST_LOG_TRIVIAL(trace) << "Received login from other instance event.";
auth_communication->on_login_code_recieved(evt.data);
});
this->q->Bind(EVT_INSTANCE_GO_TO_FRONT, [this](InstanceGoToFrontEvent &) {
bring_instance_forward();
});
this->q->Bind(EVT_OPEN_PRUSAAUTH, [this](OpenPrusaAuthEvent& evt) {
BOOST_LOG_TRIVIAL(info) << "open browser: " << evt.data;
// first register url to be sure to get the code back
auto downloader_worker = new DownloaderUtils::Worker(nullptr);
downloader_worker->perform_register(wxGetApp().app_config->get("url_downloader_dest"));
#ifdef __linux__
if (downloader_worker->get_perform_registration_linux())
DesktopIntegrationDialog::perform_downloader_desktop_integration();
#endif // __linux__
// than open url
wxGetApp().open_login_browser_with_dialog(evt.data);
});
this->q->Bind(EVT_LOGGEDOUT_PRUSAAUTH, [this](PrusaAuthSuccessEvent& evt) {
auth_communication->set_username({}, wxGetApp().app_config);
std::string text = _u8L("Logged out.");
this->notification_manager->close_notification_of_type(NotificationType::PrusaAuthUserID);
this->notification_manager->push_notification(NotificationType::PrusaAuthUserID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text);
wxGetApp().update_config_menu();
});
this->q->Bind(EVT_PA_ID_USER_SUCCESS, [this](PrusaAuthSuccessEvent& evt) {
std::string text;
try {
std::stringstream ss(evt.data);
boost::property_tree::ptree ptree;
boost::property_tree::read_json(ss, ptree);
std::string public_username;
const auto public_username_optional = ptree.get_optional<std::string>("public_username");
if (public_username_optional)
public_username = *public_username_optional;
text = format(_u8L("Logged as %1%."), public_username);
}
catch (const std::exception&) {
BOOST_LOG_TRIVIAL(error) << "UserIDUserAction Could not parse server response.";
}
assert(!text.empty());
auth_communication->set_username(evt.data, wxGetApp().app_config);
this->notification_manager->close_notification_of_type(NotificationType::PrusaAuthUserID);
this->notification_manager->push_notification(NotificationType::PrusaAuthUserID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text);
wxGetApp().update_config_menu();
});
this->q->Bind(EVT_PRUSAAUTH_FAIL, [this](PrusaAuthFailEvent& evt) {
auth_communication->set_username({}, wxGetApp().app_config);
this->notification_manager->close_notification_of_type(NotificationType::PrusaAuthUserID);
this->notification_manager->push_notification(NotificationType::PrusaAuthUserID, NotificationManager::NotificationLevel::WarningNotificationLevel, evt.data);
});
this->q->Bind(EVT_PRUSAAUTH_SUCCESS, [this](PrusaAuthSuccessEvent& evt) {
this->notification_manager->close_notification_of_type(NotificationType::PrusaAuthUserID);
this->notification_manager->push_notification(NotificationType::PrusaAuthUserID, NotificationManager::NotificationLevel::ImportantNotificationLevel, evt.data);
});
this->q->Bind(EVT_PRUSACONNECT_PRINTERS_SUCCESS, [this](PrusaAuthSuccessEvent& evt) {
std::string out = GUI::format( "Printers in your PrusaConnect team:\n%1%", auth_communication->proccess_prusaconnect_printers_message(evt.data));
this->notification_manager->close_notification_of_type(NotificationType::PrusaAuthUserID);
this->notification_manager->push_notification(NotificationType::PrusaAuthUserID, NotificationManager::NotificationLevel::ImportantNotificationLevel, out);
});
wxGetApp().other_instance_message_handler()->init(this->q);
// collapse sidebar according to saved value
@ -6580,6 +6648,16 @@ const NotificationManager * Plater::get_notification_manager() const
return p->notification_manager.get();
}
PrusaAuthCommunication* Plater::get_auth_communication()
{
return p->auth_communication.get();
}
const PrusaAuthCommunication* Plater::get_auth_communication() const
{
return p->auth_communication.get();
}
void Plater::init_notification_manager()
{
p->init_notification_manager();

View File

@ -62,6 +62,7 @@ class Mouse3DController;
class NotificationManager;
struct Camera;
class GLToolbar;
class PrusaAuthCommunication;
class Plater: public wxPanel
{
@ -347,8 +348,11 @@ public:
void set_bed_shape(const Pointfs& shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const;
void set_default_bed_shape() const;
NotificationManager * get_notification_manager();
const NotificationManager * get_notification_manager() const;
NotificationManager* get_notification_manager();
const NotificationManager* get_notification_manager() const;
PrusaAuthCommunication* get_auth_communication();
const PrusaAuthCommunication* get_auth_communication() const;
void init_notification_manager();

View File

@ -557,6 +557,12 @@ Http& Http::set_post_body(const std::string &body)
return *this;
}
Http& Http::set_referer(const std::string& referer)
{
if (p) { ::curl_easy_setopt(p->curl, CURLOPT_REFERER, referer.c_str()); }
return *this;
}
Http& Http::set_put_body(const fs::path &path)
{
if (p) { p->set_put_body(path);}
@ -587,6 +593,22 @@ Http& Http::on_ip_resolve(IPResolveFn fn)
return *this;
}
Http& Http::cookie_file(const std::string& file_path)
{
if (p) {
::curl_easy_setopt(p->curl, CURLOPT_COOKIEFILE, file_path.c_str());
}
return *this;
}
Http& Http::cookie_jar(const std::string& file_path)
{
if (p) {
::curl_easy_setopt(p->curl, CURLOPT_COOKIEJAR, file_path.c_str());
}
return *this;
}
Http::Ptr Http::perform()
{
auto self = std::make_shared<Http>(std::move(*this));

View File

@ -128,6 +128,10 @@ public:
// Called if curl_easy_getinfo resolved just used IP address.
Http& on_ip_resolve(IPResolveFn fn);
Http& cookie_file(const std::string& file_path);
Http& cookie_jar(const std::string& file_path);
Http& set_referer(const std::string& referer);
// Starts performing the request in a background thread
Ptr perform();
// Starts performing the request on the current thread