diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 54707e6dd6..6ae2bede8c 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -2502,7 +2502,7 @@ std::map GUI_App::get_extra_header() std::map extra_headers; extra_headers.insert(std::make_pair("X-BBL-Client-Type", "slicer")); extra_headers.insert(std::make_pair("X-BBL-Client-Name", SLIC3R_APP_NAME)); - extra_headers.insert(std::make_pair("X-BBL-Client-Version", VersionInfo::convert_full_version(SLIC3R_VERSION))); + extra_headers.insert(std::make_pair("X-BBL-Client-Version", get_bbl_client_version())); #if defined(__WINDOWS__) #ifdef _M_X64 extra_headers.insert(std::make_pair("X-BBL-OS-Type", "windows")); @@ -2524,6 +2524,16 @@ std::map GUI_App::get_extra_header() return extra_headers; } +std::string GUI_App::get_bbl_client_version() +{ + if (BBLNetworkPlugin::instance().get_get_my_token() == nullptr) { + // Legacy Bambu plugin lacks bambu_network_get_my_token. Pin client + // version so the auth server keeps using the ?access_token= redirect. + return "01.10.01.50"; + } + return VersionInfo::convert_full_version(SLIC3R_VERSION); +} + //BBS void GUI_App::init_http_extra_header() { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 0cbc93221e..e9099a2ede 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -707,6 +707,10 @@ public: bool hot_reload_network_plugin(); std::string get_latest_network_version() const; bool has_network_update_available() const; + // Return the client version to report to Bambu servers. Pinned to + // 01.10.01.50 when the legacy network plugin lacks get_my_token support + // so the auth server stays on the ?access_token= redirect path. + std::string get_bbl_client_version(); private: int updating_bambu_networking(); diff --git a/src/slic3r/GUI/HttpServer.cpp b/src/slic3r/GUI/HttpServer.cpp index a806bd6e24..e45d3f5751 100644 --- a/src/slic3r/GUI/HttpServer.cpp +++ b/src/slic3r/GUI/HttpServer.cpp @@ -3,6 +3,7 @@ #include "GUI_App.hpp" #include "slic3r/Utils/Http.hpp" #include "slic3r/Utils/NetworkAgent.hpp" +#include "slic3r/Utils/BBLNetworkPlugin.hpp" namespace Slic3r { namespace GUI { @@ -268,9 +269,106 @@ std::shared_ptr HttpServer::bbl_auth_handle_request(const std::string location_str = (boost::format("%1%?result=fail&error=%2%") % redirect_url % error_str).str(); return std::make_shared(location_str); } - } else { - return std::make_shared(); } + + // Ticket-based redirect: Bambu Lab's auth server redirects here after a + // third-party (Google) OAuth so that the access token never travels through + // the URL. We exchange the ticket via the network plugin's get_my_token, + // then run the same get_my_profile + change_user flow as access_token. + // Skip entirely on legacy plugins missing bambu_network_get_my_token — + // those clients pin X-BBL-Client-Version so the server stays on the legacy + // ?access_token= redirect path and never sends ?ticket= here. + const std::string ticket = url_get_param(url, "ticket"); + const std::string ticket_redirect_url = url_get_param(url, "redirect_url"); + if (!ticket.empty() && !ticket_redirect_url.empty() && + BBLNetworkPlugin::instance().get_get_my_token() != nullptr) { + BOOST_LOG_TRIVIAL(info) << "thirdparty_login: ticket flow"; + NetworkAgent* agent = wxGetApp().getAgent(); + if (!agent) { + std::string location_str = (boost::format("%1%?result=fail&error=no_agent") % ticket_redirect_url).str(); + return std::make_shared(location_str); + } + + auto fail_redirect = [&ticket_redirect_url](const std::string& reason) { + std::string location_str = (boost::format("%1%?result=fail&error=%2%") % ticket_redirect_url % reason).str(); + return std::make_shared(location_str); + }; + + unsigned int token_http_code = 0; + std::string token_body; + int token_result = agent->get_my_token(ticket, &token_http_code, &token_body); + if (token_result != 0) { + BOOST_LOG_TRIVIAL(warning) << "thirdparty_login: get_my_token failed, http_code=" << token_http_code; + return fail_redirect("get_my_token_error_" + std::to_string(token_result)); + } + + std::string access_token; + std::string refresh_token; + std::string expires_in_str; + std::string refresh_expires_in_str; + try { + json token_j = json::parse(token_body); + if (token_j.contains("accessToken")) + access_token = token_j["accessToken"].get(); + if (token_j.contains("refreshToken")) + refresh_token = token_j["refreshToken"].get(); + if (token_j.contains("expiresIn")) + expires_in_str = std::to_string(token_j["expiresIn"].get()); + if (token_j.contains("refreshExpiresIn")) + refresh_expires_in_str = std::to_string(token_j["refreshExpiresIn"].get()); + } catch (...) { + return fail_redirect("token_parse_error"); + } + + if (access_token.empty()) { + return fail_redirect("token_missing"); + } + + unsigned int profile_http_code = 0; + std::string profile_body; + int profile_result = agent->get_my_profile(access_token, &profile_http_code, &profile_body); + if (profile_result != 0) { + BOOST_LOG_TRIVIAL(warning) << "thirdparty_login: get_my_profile failed, http_code=" << profile_http_code; + return fail_redirect("get_user_profile_error_" + std::to_string(profile_result)); + } + + std::string user_id; + std::string user_name; + std::string user_account; + std::string user_avatar; + try { + json user_j = json::parse(profile_body); + if (user_j.contains("uidStr")) + user_id = user_j["uidStr"].get(); + if (user_j.contains("name")) + user_name = user_j["name"].get(); + if (user_j.contains("avatar")) + user_avatar = user_j["avatar"].get(); + if (user_j.contains("account")) + user_account = user_j["account"].get(); + } catch (...) { + BOOST_LOG_TRIVIAL(warning) << "thirdparty_login: profile JSON parse failed"; + } + + json j; + j["data"]["refresh_token"] = refresh_token; + j["data"]["token"] = access_token; + j["data"]["expires_in"] = expires_in_str; + j["data"]["refresh_expires_in"] = refresh_expires_in_str; + j["data"]["user"]["uid"] = user_id; + j["data"]["user"]["name"] = user_name; + j["data"]["user"]["account"] = user_account; + j["data"]["user"]["avatar"] = user_avatar; + agent->change_user(j.dump()); + if (agent->is_user_login()) { + wxGetApp().request_user_login(1); + } + GUI::wxGetApp().CallAfter([] { wxGetApp().ShowUserLogin(false); }); + std::string location_str = (boost::format("%1%?result=success") % ticket_redirect_url).str(); + return std::make_shared(location_str); + } + + return std::make_shared(); } void HttpServer::ResponseNotFound::write_response(std::stringstream& ssOut) diff --git a/src/slic3r/GUI/WebUserLoginDialog.cpp b/src/slic3r/GUI/WebUserLoginDialog.cpp index 743401626d..a6936e634c 100644 --- a/src/slic3r/GUI/WebUserLoginDialog.cpp +++ b/src/slic3r/GUI/WebUserLoginDialog.cpp @@ -84,7 +84,7 @@ ZUserLogin::ZUserLogin() : wxDialog((wxWindow *) (wxGetApp().mainframe), wxID_AN BOOST_LOG_TRIVIAL(info) << "login url = " << TargetUrl.ToStdString(); - m_bbl_user_agent = wxString::Format("BBL-Slicer/v%s", SLIC3R_VERSION); + m_bbl_user_agent = wxString::Format("BBL-Slicer/v%s", wxGetApp().get_bbl_client_version()); // set the frame icon diff --git a/src/slic3r/GUI/Widgets/WebView.cpp b/src/slic3r/GUI/Widgets/WebView.cpp index 82933b6069..d31c168ded 100644 --- a/src/slic3r/GUI/Widgets/WebView.cpp +++ b/src/slic3r/GUI/Widgets/WebView.cpp @@ -276,7 +276,7 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url) #ifdef __WIN32__ webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.52 BBL-Slicer/v%s (%s) BBL-Language/%s", - SLIC3R_VERSION, Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light", language_code.mb_str())); + Slic3r::GUI::wxGetApp().get_bbl_client_version(), Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light", language_code.mb_str())); webView->Create(parent, wxID_ANY, url2, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); // We register the wxfs:// protocol for testing purposes webView->RegisterHandler(wxSharedPtr(new wxWebViewArchiveHandler("bbl"))); @@ -294,7 +294,7 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, wxString const & url) } webView->Create(parent, wxID_ANY, url2, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE); webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) BBL-Slicer/v%s (%s) BBL-Language/%s", - SLIC3R_VERSION, Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light", language_code.mb_str())); + Slic3r::GUI::wxGetApp().get_bbl_client_version(), Slic3r::GUI::wxGetApp().dark_mode() ? "dark" : "light", language_code.mb_str())); #endif #ifdef __WXMAC__ WKWebView * wkWebView = (WKWebView *) webView->GetNativeBackend(); @@ -399,7 +399,7 @@ void WebView::RecreateAll() language_code = language_code.ToStdString(); for (auto webView : g_webviews) { webView->SetUserAgent(wxString::Format("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) BBL-Slicer/v%s (%s) BBL-Language/%s", - SLIC3R_VERSION, dark ? "dark" : "light", language_code.mb_str())); + Slic3r::GUI::wxGetApp().get_bbl_client_version(), dark ? "dark" : "light", language_code.mb_str())); webView->Reload(); } } diff --git a/src/slic3r/Utils/BBLCloudServiceAgent.cpp b/src/slic3r/Utils/BBLCloudServiceAgent.cpp index 5f20549db2..ec54041b57 100644 --- a/src/slic3r/Utils/BBLCloudServiceAgent.cpp +++ b/src/slic3r/Utils/BBLCloudServiceAgent.cpp @@ -628,6 +628,17 @@ int BBLCloudServiceAgent::get_my_profile(std::string token, unsigned int* http_c return -1; } +int BBLCloudServiceAgent::get_my_token(std::string ticket, unsigned int* http_code, std::string* http_body) +{ + auto& plugin = BBLNetworkPlugin::instance(); + auto agent = plugin.get_agent(); + auto func = plugin.get_get_my_token(); + if (func && agent) { + return func(agent, ticket, http_code, http_body); + } + return -1; +} + // ============================================================================ // Analytics & Tracking // ============================================================================ diff --git a/src/slic3r/Utils/BBLCloudServiceAgent.hpp b/src/slic3r/Utils/BBLCloudServiceAgent.hpp index 0c960cf5d7..b4ca79be82 100644 --- a/src/slic3r/Utils/BBLCloudServiceAgent.hpp +++ b/src/slic3r/Utils/BBLCloudServiceAgent.hpp @@ -98,6 +98,7 @@ public: int get_model_mall_home_url(std::string* url) override; int get_model_mall_detail_url(std::string* url, std::string id) override; int get_my_profile(std::string token, unsigned int* http_code, std::string* http_body) override; + int get_my_token(std::string ticket, unsigned int* http_code, std::string* http_body) override; // Analytics & Tracking int track_enable(bool enable) override; diff --git a/src/slic3r/Utils/BBLNetworkPlugin.cpp b/src/slic3r/Utils/BBLNetworkPlugin.cpp index e3b7f46be7..1db8cd5ed9 100644 --- a/src/slic3r/Utils/BBLNetworkPlugin.cpp +++ b/src/slic3r/Utils/BBLNetworkPlugin.cpp @@ -176,7 +176,8 @@ int BBLNetworkPlugin::initialize(bool using_backup, const std::string& version) << ", version=" << (loaded_version.empty() ? "unknown" : loaded_version) << ", send_message=" << (m_send_message ? "loaded" : "null") << ", start_print=" << (m_start_print ? "loaded" : "null") - << ", start_local_print=" << (m_start_local_print ? "loaded" : "null"); + << ", start_local_print=" << (m_start_local_print ? "loaded" : "null") + << ", get_my_token=" << (m_get_my_token ? "loaded" : "null"); return 0; } @@ -622,6 +623,7 @@ void BBLNetworkPlugin::load_all_function_pointers() m_get_model_mall_home_url = reinterpret_cast(get_function("bambu_network_get_model_mall_home_url")); m_get_model_mall_detail_url = reinterpret_cast(get_function("bambu_network_get_model_mall_detail_url")); m_get_my_profile = reinterpret_cast(get_function("bambu_network_get_my_profile")); + m_get_my_token = reinterpret_cast(get_function("bambu_network_get_my_token")); m_track_enable = reinterpret_cast(get_function("bambu_network_track_enable")); m_track_remove_files = reinterpret_cast(get_function("bambu_network_track_remove_files")); m_track_event = reinterpret_cast(get_function("bambu_network_track_event")); @@ -725,6 +727,7 @@ void BBLNetworkPlugin::clear_all_function_pointers() m_get_model_mall_home_url = nullptr; m_get_model_mall_detail_url = nullptr; m_get_my_profile = nullptr; + m_get_my_token = nullptr; m_track_enable = nullptr; m_track_remove_files = nullptr; m_track_event = nullptr; diff --git a/src/slic3r/Utils/BBLNetworkPlugin.hpp b/src/slic3r/Utils/BBLNetworkPlugin.hpp index 523d3962d6..661d968858 100644 --- a/src/slic3r/Utils/BBLNetworkPlugin.hpp +++ b/src/slic3r/Utils/BBLNetworkPlugin.hpp @@ -107,6 +107,7 @@ typedef int (*func_get_subtask)(void *agent, BBLModelTask* task, OnGetSubTaskFn typedef int (*func_get_model_mall_home_url)(void *agent, std::string* url); typedef int (*func_get_model_mall_detail_url)(void *agent, std::string* url, std::string id); typedef int (*func_get_my_profile)(void *agent, std::string token, unsigned int *http_code, std::string *http_body); +typedef int (*func_get_my_token)(void *agent, std::string ticket, unsigned int *http_code, std::string *http_body); typedef int (*func_track_enable)(void *agent, bool enable); typedef int (*func_track_remove_files)(void *agent); typedef int (*func_track_event)(void *agent, std::string evt_key, std::string content); @@ -361,6 +362,7 @@ public: func_get_model_mall_home_url get_get_model_mall_home_url() const { return m_get_model_mall_home_url; } func_get_model_mall_detail_url get_get_model_mall_detail_url() const { return m_get_model_mall_detail_url; } func_get_my_profile get_get_my_profile() const { return m_get_my_profile; } + func_get_my_token get_get_my_token() const { return m_get_my_token; } func_track_enable get_track_enable() const { return m_track_enable; } func_track_remove_files get_track_remove_files() const { return m_track_remove_files; } func_track_event get_track_event() const { return m_track_event; } @@ -496,6 +498,7 @@ private: func_get_model_mall_home_url m_get_model_mall_home_url{nullptr}; func_get_model_mall_detail_url m_get_model_mall_detail_url{nullptr}; func_get_my_profile m_get_my_profile{nullptr}; + func_get_my_token m_get_my_token{nullptr}; func_track_enable m_track_enable{nullptr}; func_track_remove_files m_track_remove_files{nullptr}; func_track_event m_track_event{nullptr}; diff --git a/src/slic3r/Utils/ICloudServiceAgent.hpp b/src/slic3r/Utils/ICloudServiceAgent.hpp index 5a5d02cd38..bb1e9afd09 100644 --- a/src/slic3r/Utils/ICloudServiceAgent.hpp +++ b/src/slic3r/Utils/ICloudServiceAgent.hpp @@ -353,6 +353,14 @@ public: */ virtual int get_my_profile(std::string token, unsigned int* http_code, std::string* http_body) = 0; + /** + * Exchange a one-time login ticket (from a third-party OAuth callback) + * for token + profile JSON. Used by the localhost callback handler when + * the auth server redirects with ?ticket=...&redirect_url=... instead of + * passing tokens directly in the URL. + */ + virtual int get_my_token(std::string ticket, unsigned int* http_code, std::string* http_body) = 0; + // ======================================================================== // Analytics & Tracking // ======================================================================== diff --git a/src/slic3r/Utils/NetworkAgent.cpp b/src/slic3r/Utils/NetworkAgent.cpp index 1ec0ef437d..a554e9aa04 100644 --- a/src/slic3r/Utils/NetworkAgent.cpp +++ b/src/slic3r/Utils/NetworkAgent.cpp @@ -781,6 +781,12 @@ int NetworkAgent::get_my_profile(std::string token, unsigned int* http_code, std return -1; } +int NetworkAgent::get_my_token(std::string ticket, unsigned int* http_code, std::string* http_body) +{ + if (m_cloud_agent) return m_cloud_agent->get_my_token(ticket, http_code, http_body); + return -1; +} + int NetworkAgent::track_enable(bool enable) { this->enable_track = enable; diff --git a/src/slic3r/Utils/NetworkAgent.hpp b/src/slic3r/Utils/NetworkAgent.hpp index 402497fc19..ce3590ad86 100644 --- a/src/slic3r/Utils/NetworkAgent.hpp +++ b/src/slic3r/Utils/NetworkAgent.hpp @@ -144,6 +144,7 @@ public: int get_model_mall_home_url(std::string* url); int get_model_mall_detail_url(std::string* url, std::string id); int get_my_profile(std::string token, unsigned int* http_code, std::string* http_body); + int get_my_token(std::string ticket, unsigned int* http_code, std::string* http_body); int track_enable(bool enable); int track_remove_files(); int track_event(std::string evt_key, std::string content); diff --git a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp index 524ac72ed8..ecb3e462d0 100644 --- a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp +++ b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp @@ -2474,6 +2474,14 @@ int OrcaCloudServiceAgent::get_my_profile(std::string token, unsigned int* http_ return BAMBU_NETWORK_SUCCESS; } +int OrcaCloudServiceAgent::get_my_token(std::string ticket, unsigned int* http_code, std::string* http_body) +{ + BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_my_token (stub) - Orca cloud uses code-based OAuth, not tickets"; + if (http_code) *http_code = 0; + if (http_body) *http_body = ""; + return -1; +} + int OrcaCloudServiceAgent::track_enable(bool enable) { std::lock_guard lock(state_mutex); diff --git a/src/slic3r/Utils/OrcaCloudServiceAgent.hpp b/src/slic3r/Utils/OrcaCloudServiceAgent.hpp index 510829c717..140d8da9e0 100644 --- a/src/slic3r/Utils/OrcaCloudServiceAgent.hpp +++ b/src/slic3r/Utils/OrcaCloudServiceAgent.hpp @@ -199,6 +199,7 @@ public: int get_model_mall_home_url(std::string* url) override; int get_model_mall_detail_url(std::string* url, std::string id) override; int get_my_profile(std::string token, unsigned int* http_code, std::string* http_body) override; + int get_my_token(std::string ticket, unsigned int* http_code, std::string* http_body) override; // ======================================================================== // ICloudServiceAgent Interface Implementation - Analytics & Tracking