mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-01 05:12:00 +08:00
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:
parent
f624c55f6f
commit
adc650ef4e
@ -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
479
src/slic3r/GUI/Auth.cpp
Normal 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
79
src/slic3r/GUI/Auth.hpp
Normal 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
|
232
src/slic3r/GUI/AuthSession.cpp
Normal file
232
src/slic3r/GUI/AuthSession.cpp
Normal 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
|
152
src/slic3r/GUI/AuthSession.hpp
Normal file
152
src/slic3r/GUI/AuthSession.hpp
Normal 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
|
@ -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) = @_;
|
||||
|
@ -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)
|
||||
|
@ -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?
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -130,6 +130,8 @@ enum class NotificationType
|
||||
URLNotRegistered,
|
||||
// Config file was detected during startup, open wifi config dialog via hypertext
|
||||
WifiConfigFileDetected
|
||||
//
|
||||
PrusaAuthUserID,
|
||||
};
|
||||
|
||||
class NotificationManager
|
||||
|
@ -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)
|
||||
@ -854,10 +858,74 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
}
|
||||
|
||||
});
|
||||
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();
|
||||
|
@ -62,6 +62,7 @@ class Mouse3DController;
|
||||
class NotificationManager;
|
||||
struct Camera;
|
||||
class GLToolbar;
|
||||
class PrusaAuthCommunication;
|
||||
|
||||
class Plater: public wxPanel
|
||||
{
|
||||
@ -350,6 +351,9 @@ public:
|
||||
NotificationManager* get_notification_manager();
|
||||
const NotificationManager* get_notification_manager() const;
|
||||
|
||||
PrusaAuthCommunication* get_auth_communication();
|
||||
const PrusaAuthCommunication* get_auth_communication() const;
|
||||
|
||||
void init_notification_manager();
|
||||
|
||||
void bring_instance_forward();
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user