diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 2ba2a16893..b2f467b99f 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -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 diff --git a/src/slic3r/GUI/Auth.cpp b/src/slic3r/GUI/Auth.cpp new file mode 100644 index 0000000000..1a91aa4761 --- /dev/null +++ b/src/slic3r/GUI/Auth.cpp @@ -0,0 +1,479 @@ +#include "Auth.hpp" +#include "GUI_App.hpp" +#include "format.hpp" +#include "../Utils/Http.hpp" +#include "I18N.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if wxUSE_SECRETSTORE +#include +#endif + +#ifdef WIN32 +#include +#endif // WIN32 + +#ifdef __linux__ +#include +#include +#include +#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(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 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 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 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 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 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 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 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 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 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 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 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 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 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(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(&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(&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(digest), digestLen); +} +#endif // __linux__ +}} // Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/Auth.hpp b/src/slic3r/GUI/Auth.hpp new file mode 100644 index 0000000000..77274a2e32 --- /dev/null +++ b/src/slic3r/GUI/Auth.hpp @@ -0,0 +1,79 @@ +#ifndef slic3r_Auth_hpp_ +#define slic3r_Auth_hpp_ + +#include "AuthSession.hpp" +#include "Event.hpp" +#include "libslic3r/AppConfig.hpp" + +#include +#include +#include +#include +#include + +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 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 \ No newline at end of file diff --git a/src/slic3r/GUI/AuthSession.cpp b/src/slic3r/GUI/AuthSession.cpp new file mode 100644 index 0000000000..d4ab7f3431 --- /dev/null +++ b/src/slic3r/GUI/AuthSession.cpp @@ -0,0 +1,232 @@ +#include "AuthSession.hpp" +#include "GUI_App.hpp" +#include "format.hpp" +#include "../Utils/Http.hpp" +#include "I18N.hpp" + +#include +#include +#include +#include +#include +#include +#include + +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("access_token"); + const auto refresh_token_optional = ptree.get_optional("refresh_token"); + const auto shared_session_key_optional = ptree.get_optional("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("access_token"); + const auto refresh_token_optional = ptree.get_optional("refresh_token"); + const auto shared_session_key_optional = ptree.get_optional("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 \ No newline at end of file diff --git a/src/slic3r/GUI/AuthSession.hpp b/src/slic3r/GUI/AuthSession.hpp new file mode 100644 index 0000000000..afb9392e70 --- /dev/null +++ b/src/slic3r/GUI/AuthSession.hpp @@ -0,0 +1,152 @@ +#ifndef slic3r_AuthSession_hpp_ +#define slic3r_AuthSession_hpp_ + +#include "Event.hpp" +#include "libslic3r/AppConfig.hpp" + +#include +#include +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +using OpenPrusaAuthEvent = Event; +using PrusaAuthSuccessEvent = Event; +using PrusaAuthFailEvent = Event; +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 UserActionSuccessFn; +typedef std::function 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(); + m_actions[UserActionID::REFRESH_TOKEN] = std::make_unique("EXCHANGE_TOKENS", "https://test-account.prusa3d.com/o/token/"); + m_actions[UserActionID::CODE_FOR_TOKEN] = std::make_unique("EXCHANGE_TOKENS", "https://test-account.prusa3d.com/o/token/"); + m_actions[UserActionID::TEST_CONNECTION] = std::make_unique("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("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("CONNECT_DUMMY", "dev.connect.prusa:8000/slicer/dummy", evt_handler, EVT_PRUSAAUTH_SUCCESS, EVT_PRUSAAUTH_FAIL); + m_actions[UserActionID::CONNECT_PRINTERS] = std::make_unique("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 m_action_queue; + std::queue m_priority_action_queue; + std::map> m_actions; +}; + +} +} +#endif \ No newline at end of file diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index a5646bb52e..c0115afd8a 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -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) = @_; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 6d14123af3..6271b9a905 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -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) diff --git a/src/slic3r/GUI/InstanceCheck.cpp b/src/slic3r/GUI/InstanceCheck.cpp index a8f8adfdc9..595b71fd1a 100644 --- a/src/slic3r/GUI/InstanceCheck.cpp +++ b/src/slic3r/GUI/InstanceCheck.cpp @@ -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? diff --git a/src/slic3r/GUI/InstanceCheck.hpp b/src/slic3r/GUI/InstanceCheck.hpp index 3d9d2e0fb2..d70f4a90a5 100644 --- a/src/slic3r/GUI/InstanceCheck.hpp +++ b/src/slic3r/GUI/InstanceCheck.hpp @@ -48,8 +48,10 @@ class MainFrame; using LoadFromOtherInstanceEvent = Event>; using StartDownloadOtherInstanceEvent = Event>; +using LoginOtherInstanceEvent = Event; 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); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 37fcfc3ab2..42d6afd129 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -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 diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index d74b28af60..9759fd7d1f 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -130,6 +130,8 @@ enum class NotificationType URLNotRegistered, // Config file was detected during startup, open wifi config dialog via hypertext WifiConfigFileDetected + // + PrusaAuthUserID, }; class NotificationManager diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cd1c5e4ea3..859a55c8c7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -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 notification_manager; + std::unique_ptr 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(q)) + , auth_communication(std::make_unique(q, wxGetApp().app_config)) , m_worker{q, std::make_unique(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("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(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index ac596a9dcb..fd0cad7f9a 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -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(); diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index 8ec63d7b54..38ca8782aa 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -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(std::move(*this)); diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index 941d638006..8e00b123fc 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -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