mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-05-14 00:52:04 +00:00
Fix a regression issue orca couldn't login into BBL cloud (#13407)
* Add ticket-based OAuth flow for third-party login in HttpServer * Fix legacy plugin login
This commit is contained in:
@@ -2502,7 +2502,7 @@ std::map<std::string, std::string> GUI_App::get_extra_header()
|
||||
std::map<std::string, std::string> 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<std::string, std::string> 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()
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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::Response> 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<ResponseRedirect>(location_str);
|
||||
}
|
||||
} else {
|
||||
return std::make_shared<ResponseNotFound>();
|
||||
}
|
||||
|
||||
// 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<ResponseRedirect>(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<ResponseRedirect>(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<std::string>();
|
||||
if (token_j.contains("refreshToken"))
|
||||
refresh_token = token_j["refreshToken"].get<std::string>();
|
||||
if (token_j.contains("expiresIn"))
|
||||
expires_in_str = std::to_string(token_j["expiresIn"].get<double>());
|
||||
if (token_j.contains("refreshExpiresIn"))
|
||||
refresh_expires_in_str = std::to_string(token_j["refreshExpiresIn"].get<double>());
|
||||
} 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<std::string>();
|
||||
if (user_j.contains("name"))
|
||||
user_name = user_j["name"].get<std::string>();
|
||||
if (user_j.contains("avatar"))
|
||||
user_avatar = user_j["avatar"].get<std::string>();
|
||||
if (user_j.contains("account"))
|
||||
user_account = user_j["account"].get<std::string>();
|
||||
} 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<ResponseRedirect>(location_str);
|
||||
}
|
||||
|
||||
return std::make_shared<ResponseNotFound>();
|
||||
}
|
||||
|
||||
void HttpServer::ResponseNotFound::write_response(std::stringstream& ssOut)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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<wxWebViewHandler>(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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
// ============================================================================
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<func_get_model_mall_home_url>(get_function("bambu_network_get_model_mall_home_url"));
|
||||
m_get_model_mall_detail_url = reinterpret_cast<func_get_model_mall_detail_url>(get_function("bambu_network_get_model_mall_detail_url"));
|
||||
m_get_my_profile = reinterpret_cast<func_get_my_profile>(get_function("bambu_network_get_my_profile"));
|
||||
m_get_my_token = reinterpret_cast<func_get_my_token>(get_function("bambu_network_get_my_token"));
|
||||
m_track_enable = reinterpret_cast<func_track_enable>(get_function("bambu_network_track_enable"));
|
||||
m_track_remove_files = reinterpret_cast<func_track_remove_files>(get_function("bambu_network_track_remove_files"));
|
||||
m_track_event = reinterpret_cast<func_track_event>(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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
// ========================================================================
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<std::recursive_mutex> lock(state_mutex);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user