From cd64842e338fb216f0772e371278c56b921f6f87 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 18 Jul 2024 14:24:36 -0400 Subject: Improve viewer's defense against `LLEventAPI` failures. `LLEventAPI` is specifically intended to allow a LEAP plugin, or a Lua script, to access certain viewer functionality. Errors in external code like that cannot be addressed during viewer development. Any code path that allows external code in any form to crash the viewer opens up a potential abuse vector, if a trusting user runs external code from an untrustworthy source. `LLDispatchListener` reports exceptions back to its invoker, if the invoker provides a "reply" `LLEventPump` name. Absent "reply", though, `LLDispatchListener` is documented to let any such exception propagate. That behavior may be okay for internal use, but in the case of the `LLEventAPI` subclass, it veers into the abuse scenario described above. Make `LLEventAPI` ensure that any exception propagating from `LLDispatchListener` is caught and logged, but not propagated. Also enrich error reporting for the "batch" `LLDispatchListener` operations. --- indra/llcommon/lleventapi.cpp | 20 ++++++++++++++++++++ indra/llcommon/lleventapi.h | 1 + indra/llcommon/lleventdispatcher.cpp | 15 +++++++++------ indra/llcommon/lleventdispatcher.h | 4 +++- 4 files changed, 33 insertions(+), 7 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lleventapi.cpp b/indra/llcommon/lleventapi.cpp index 8b724256b8..4672371b4f 100644 --- a/indra/llcommon/lleventapi.cpp +++ b/indra/llcommon/lleventapi.cpp @@ -55,6 +55,26 @@ LLEventAPI::~LLEventAPI() { } +bool LLEventAPI::process(const LLSD& event) const +{ + // LLDispatchListener is documented to let DispatchError propagate if the + // incoming request has no "reply" key. That may be fine for internal-only + // use, but LLEventAPI opens the door for external requests. It should NOT + // be possible for any external requester to crash the viewer with an + // unhandled exception, especially not by something as simple as omitting + // the "reply" key. + try + { + return LLDispatchListener::process(event); + } + catch (const std::exception& err) + { + // log the exception, but otherwise ignore it + LL_WARNS("LLEventAPI") << LLError::Log::classname(err) << ": " << err.what() << LL_ENDL; + return false; + } +} + LLEventAPI::Response::Response(const LLSD& seed, const LLSD& request, const LLSD::String& replyKey): mResp(seed), mReq(request), diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h index 3ae820db51..da7c58e6f0 100644 --- a/indra/llcommon/lleventapi.h +++ b/indra/llcommon/lleventapi.h @@ -157,6 +157,7 @@ protected: LLEventAPI(const LL::LazyEventAPIParams&); private: + bool process(const LLSD& event) const override; std::string mDesc; }; diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 9dd6864cff..12b966fadf 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -800,7 +800,7 @@ void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const { result = (*this)(event); } - catch (const DispatchError& err) + catch (const std::exception& err) { if (! event.has(mReplyKey)) { @@ -810,7 +810,10 @@ void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const // Here there was an error and the incoming event has mReplyKey. Reply // with a map containing an "error" key explaining the problem. - return reply(llsd::map("error", err.what()), event); + return reply(llsd::map("error", + stringize(LLError::Log::classname(err), + ": ", err.what())), + event); } // We seem to have gotten a valid result. But we don't know whether the @@ -846,7 +849,7 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const { // in case of errors, tell user the dispatch key, the fact that // we're processing a request map and the current key in that map - SetState(this, '[', key, '[', name, "]]"); + SetState transient(this, '[', key, '[', name, "]]"); // With this form, capture return value even if undefined: // presence of the key in the response map can be used to detect // which request keys succeeded. @@ -869,7 +872,7 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const if (! event.has(mReplyKey)) { // can't send reply, throw - sCallFail(error); + callFail(error); } else { @@ -923,7 +926,7 @@ void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) con // in case of errors, tell user the dispatch key, the fact that // we're processing a request array, the current entry in that // array and the corresponding callable name - SetState(this, '[', key, '[', i, "]=", name, ']'); + SetState transient(this, '[', key, '[', i, "]=", name, ']'); // With this form, capture return value even if undefined results.append((*this)(name, args)); } @@ -944,7 +947,7 @@ void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) con if (! event.has(mReplyKey)) { // can't send reply, throw - sCallFail(error); + callFail(error); } else { diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 5adaa3ebae..698412fdb4 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -853,8 +853,10 @@ public: std::string getPumpName() const { return getName(); } +protected: + virtual bool process(const LLSD& event) const; + private: - bool process(const LLSD& event) const; void call_one(const LLSD& name, const LLSD& event) const; void call_map(const LLSD& reqmap, const LLSD& event) const; void call_array(const LLSD& reqarray, const LLSD& event) const; -- cgit v1.2.3 From b79bf898c63d06486c978cbeecbe533e5872b56c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 18 Jul 2024 14:42:26 -0400 Subject: Guarantee that the "login" LLEventPump has exactly that name. We used to allow "tweaking" the name. Don't. --- indra/viewer_components/login/lllogin.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp index 2a0468f3ad..dfaaff21c5 100644 --- a/indra/viewer_components/login/lllogin.cpp +++ b/indra/viewer_components/login/lllogin.cpp @@ -53,7 +53,9 @@ class LLLogin::Impl { public: Impl(): - mPump("login", true) // Create the module's event pump with a tweaked (unique) name. + // Create the module's event pump, and do not tweak the name. Multiple + // parties depend on this LLEventPump having exactly the name "login". + mPump("login", false) { mValidAuthResponse["status"] = LLSD(); mValidAuthResponse["errorcode"] = LLSD(); -- cgit v1.2.3 From 1913ea4ec2f46458e2971e6e02afacf4da670af2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Aug 2024 19:07:02 -0400 Subject: Search --luafile script on LuaCommandPath. --- indra/newview/llappviewer.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index ef12fe0bd3..ff29f64aeb 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -113,6 +113,7 @@ #include "llgltfmateriallist.h" // Linden library includes +#include "fsyspath.h" #include "llavatarnamecache.h" #include "lldiriterator.h" #include "llexperiencecache.h" @@ -1216,8 +1217,23 @@ bool LLAppViewer::init() "--luafile", "LuaScript", [](const LLSD& script) { - // no completion callback: we don't need to know - LLLUAmanager::runScriptFile(script); + LLSD paths(gSavedSettings.getLLSD("LuaCommandPath")); + LL_DEBUGS("Lua") << "LuaCommandPath = " << paths << LL_ENDL; + for (const auto& path : llsd::inArray(paths)) + { + // if script path is already absolute, operator/() preserves it + auto abspath(fsyspath(gDirUtilp->getAppRODataDir()) / path.asString()); + auto absscript{ (abspath / script.asString()) }; + std::error_code ec; + if (std::filesystem::exists(absscript, ec)) + { + // no completion callback: we don't need to know + LLLUAmanager::runScriptFile(absscript.u8string()); + return; // from lambda + } + } + LL_WARNS("Lua") << "--luafile " << std::quoted(script.asString()) + << " not found on " << paths << LL_ENDL; }); processComposeSwitch( "LuaAutorunPath", "LuaAutorunPath", -- cgit v1.2.3 From af947de6928daa70098edb8346effb9dc8146f47 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 2 Aug 2024 19:24:07 -0400 Subject: Add 'LLPanelLogin' 'login', 'savedLogins' operations. 'login' accepts optional 'username', 'slurl', 'grid'. 'savedLogins' returns the list of saved usernames in both display form and internal form. Make LLPanelLogin::getUserName() accept (const LLPointer&). There's a whole separate discussion pending as to whether const LLPointer should provide access to non-const T methods. Similarly, make LLCredential::getIdentifier() a const method. These two changes enable read-only access to credentials. Make LLPanelLogin methods capture and reuse LLGridManager::instance() as appropriate. Add require/login.lua and test_login.lua. --- indra/newview/llpanellogin.cpp | 57 +++++---- indra/newview/llpanellogin.h | 2 +- indra/newview/llpanelloginlistener.cpp | 178 ++++++++++++++++++++++++++++ indra/newview/llpanelloginlistener.h | 2 + indra/newview/llsecapi.h | 2 +- indra/newview/scripts/lua/require/login.lua | 21 ++-- indra/newview/scripts/lua/test_login.lua | 10 +- 7 files changed, 226 insertions(+), 46 deletions(-) (limited to 'indra') diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index 86f56f0949..a6dbcedc4b 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -81,15 +81,16 @@ BOOL LLPanelLogin::sCredentialSet = FALSE; LLPointer load_user_credentials(std::string &user_key) { - if (gSecAPIHandler->hasCredentialMap("login_list", LLGridManager::getInstance()->getGrid())) + std::string grid{ LLGridManager::instance().getGrid() }; + if (gSecAPIHandler->hasCredentialMap("login_list", grid)) { // user_key should be of "name Resident" format - return gSecAPIHandler->loadFromCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), user_key); + return gSecAPIHandler->loadFromCredentialMap("login_list", grid, user_key); } else { // legacy (or legacy^2, since it also tries to load from settings) - return gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + return gSecAPIHandler->loadCredential(grid); } } @@ -223,7 +224,8 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, sendChildToBack(getChildView("forgot_password_text")); sendChildToBack(getChildView("sign_up_text")); - std::string current_grid = LLGridManager::getInstance()->getGrid(); + LLGridManager& gridmgr{ LLGridManager::instance() }; + std::string current_grid = gridmgr.getGrid(); if (!mFirstLoginThisInstall) { LLComboBox* favorites_combo = getChild("start_location_combo"); @@ -237,7 +239,7 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, // Load all of the grids, sorted, and then add a bar and the current grid at the top server_choice_combo->removeall(); - std::map known_grids = LLGridManager::getInstance()->getKnownGrids(); + std::map known_grids = gridmgr.getKnownGrids(); for (std::map::iterator grid_choice = known_grids.begin(); grid_choice != known_grids.end(); grid_choice++) @@ -251,7 +253,7 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, server_choice_combo->sortByName(); LL_DEBUGS("AppInit") << "adding current " << current_grid << LL_ENDL; - server_choice_combo->add(LLGridManager::getInstance()->getGridLabel(), + server_choice_combo->add(gridmgr.getGridLabel(), current_grid, ADD_TOP); server_choice_combo->selectFirstItem(); @@ -760,6 +762,7 @@ void LLPanelLogin::onUpdateStartSLURL(const LLSLURL& new_start_slurl) LL_DEBUGS("AppInit")<getChild("start_location_combo"); + LLGridManager& gridmgr{ LLGridManager::instance() }; /* * Determine whether or not the new_start_slurl modifies the grid. * @@ -774,17 +777,17 @@ void LLPanelLogin::onUpdateStartSLURL(const LLSLURL& new_start_slurl) { case LLSLURL::LOCATION: { - std::string slurl_grid = LLGridManager::getInstance()->getGrid(new_start_slurl.getGrid()); + std::string slurl_grid = gridmgr.getGrid(new_start_slurl.getGrid()); if ( ! slurl_grid.empty() ) // is that a valid grid? { - if ( slurl_grid != LLGridManager::getInstance()->getGrid() ) // new grid? + if ( slurl_grid != gridmgr.getGrid() ) // new grid? { // the slurl changes the grid, so update everything to match - LLGridManager::getInstance()->setGridChoice(slurl_grid); + gridmgr.setGridChoice(slurl_grid); // update the grid selector to match the slurl LLComboBox* server_combo = sInstance->getChild("server_combo"); - std::string server_label(LLGridManager::getInstance()->getGridLabel(slurl_grid)); + std::string server_label(gridmgr.getGridLabel(slurl_grid)); server_combo->setSimple(server_label); updateServer(); // to change the links and splash screen @@ -831,8 +834,7 @@ void LLPanelLogin::autologinToLocation(const LLSLURL& slurl) if ( LLPanelLogin::sInstance != NULL ) { - void* unused_parameter = 0; - LLPanelLogin::sInstance->onClickConnect(unused_parameter); + LLPanelLogin::sInstance->onClickConnect(false); } } @@ -872,7 +874,8 @@ void LLPanelLogin::loadLoginPage() { if (!sInstance) return; - LLURI login_page = LLURI(LLGridManager::getInstance()->getLoginPage()); + LLGridManager& gridmgr{ LLGridManager::instance() }; + LLURI login_page = LLURI(gridmgr.getLoginPage()); LLSD params(login_page.queryMap()); LL_DEBUGS("AppInit") << "login_page: " << login_page << LL_ENDL; @@ -899,7 +902,7 @@ void LLPanelLogin::loadLoginPage() params["channel"] = LLVersionInfo::instance().getChannel(); // Grid - params["grid"] = LLGridManager::getInstance()->getGridId(); + params["grid"] = gridmgr.getGridId(); // add OS info params["os"] = LLOSInfo::instance().getOSStringSimple(); @@ -916,7 +919,7 @@ void LLPanelLogin::loadLoginPage() login_page.path(), params)); - gViewerWindow->setMenuBackgroundColor(false, !LLGridManager::getInstance()->isInProductionGrid()); + gViewerWindow->setMenuBackgroundColor(false, !gridmgr.isInProductionGrid()); LLMediaCtrl* web_browser = sInstance->getChild("login_html"); if (web_browser->getCurrentNavUrl() != login_uri.asString()) @@ -949,9 +952,10 @@ void LLPanelLogin::onClickConnect(bool commit_fields) // the grid definitions may come from a user-supplied grids.xml, so they may not be good LL_DEBUGS("AppInit")<<"grid "<setGridChoice(combo_val.asString()); + gridmgr.setGridChoice(combo_val.asString()); } catch (LLInvalidGridName ex) { @@ -984,7 +988,7 @@ void LLPanelLogin::onClickConnect(bool commit_fields) std::string identifier_type; cred->identifierType(identifier_type); LLSD allowed_credential_types; - LLGridManager::getInstance()->getLoginIdentifierTypes(allowed_credential_types); + gridmgr.getLoginIdentifierTypes(allowed_credential_types); // check the typed in credential type against the credential types expected by the server. for(LLSD::array_iterator i = allowed_credential_types.beginArray(); @@ -1133,6 +1137,7 @@ void LLPanelLogin::updateServer() { if (sInstance) { + LLGridManager& gridmgr{ LLGridManager::instance() }; try { // if they've selected another grid, we should load the credentials @@ -1152,7 +1157,7 @@ void LLPanelLogin::updateServer() // populate dropbox and setFields // Note: following call is related to initializeLoginInfo() - LLPointer credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + LLPointer credential = gSecAPIHandler->loadCredential(gridmgr.getGrid()); sInstance->populateUserList(credential); // restore creds @@ -1165,12 +1170,12 @@ void LLPanelLogin::updateServer() { // populate dropbox and setFields // Note: following call is related to initializeLoginInfo() - LLPointer credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + LLPointer credential = gSecAPIHandler->loadCredential(gridmgr.getGrid()); sInstance->populateUserList(credential); } // update the login panel links - bool system_grid = LLGridManager::getInstance()->isSystemGrid(); + bool system_grid = gridmgr.isSystemGrid(); // Want to vanish not only create_new_account_btn, but also the // title text over it, so turn on/off the whole layout_panel element. @@ -1219,11 +1224,12 @@ void LLPanelLogin::populateUserList(LLPointer credential) getChild("password_edit")->setValue(std::string()); mUsernameLength = 0; mPasswordLength = 0; + std::string grid{ LLGridManager::instance().getGrid() }; - if (gSecAPIHandler->hasCredentialMap("login_list", LLGridManager::getInstance()->getGrid())) + if (gSecAPIHandler->hasCredentialMap("login_list", grid)) { LLSecAPIHandler::credential_map_t credencials; - gSecAPIHandler->loadCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), credencials); + gSecAPIHandler->loadCredentialMap("login_list", grid, credencials); LLSecAPIHandler::credential_map_t::iterator cr_iter = credencials.begin(); LLSecAPIHandler::credential_map_t::iterator cr_end = credencials.end(); @@ -1278,7 +1284,8 @@ void LLPanelLogin::onSelectServer() LLComboBox* server_combo = getChild("server_combo"); LLSD server_combo_val = server_combo->getSelectedValue(); LL_INFOS("AppInit") << "grid "<setGridChoice(server_combo_val.asString()); + auto& gridmgr{ LLGridManager::instance() }; + gridmgr.setGridChoice(server_combo_val.asString()); addFavoritesToStartLocation(); /* @@ -1308,7 +1315,7 @@ void LLPanelLogin::onSelectServer() LLSLURL slurl(location); // generata a slurl from the location combo contents if (location.empty() || (slurl.getType() == LLSLURL::LOCATION - && slurl.getGrid() != LLGridManager::getInstance()->getGrid()) + && slurl.getGrid() != gridmgr.getGrid()) ) { // the grid specified by the location is not this one, so clear the combo @@ -1338,7 +1345,7 @@ bool LLPanelLogin::getShowFavorites() } // static -std::string LLPanelLogin::getUserName(LLPointer &cred) +std::string LLPanelLogin::getUserName(const LLPointer &cred) { if (cred.isNull()) { diff --git a/indra/newview/llpanellogin.h b/indra/newview/llpanellogin.h index 7f4c2487bf..a7019ea2a3 100644 --- a/indra/newview/llpanellogin.h +++ b/indra/newview/llpanellogin.h @@ -88,7 +88,7 @@ public: static bool getShowFavorites(); // extract name from cred in a format apropriate for username field - static std::string getUserName(LLPointer &cred); + static std::string getUserName(const LLPointer &cred); private: friend class LLPanelLoginListener; diff --git a/indra/newview/llpanelloginlistener.cpp b/indra/newview/llpanelloginlistener.cpp index fb6f034b7f..a4418145d7 100644 --- a/indra/newview/llpanelloginlistener.cpp +++ b/indra/newview/llpanelloginlistener.cpp @@ -32,9 +32,19 @@ #include "llpanelloginlistener.h" // STL headers // std headers +#include +#include // external library headers // other Linden headers +#include "llcombobox.h" #include "llpanellogin.h" +#include "llsdutil.h" +#include "llsecapi.h" +#include "llslurl.h" +#include "llstartup.h" +#include "lluictrl.h" +#include "llviewernetwork.h" +#include "stringize.h" LLPanelLoginListener::LLPanelLoginListener(LLPanelLogin* instance): LLEventAPI("LLPanelLogin", "Access to LLPanelLogin methods"), @@ -43,9 +53,177 @@ LLPanelLoginListener::LLPanelLoginListener(LLPanelLogin* instance): add("onClickConnect", "Pretend user clicked the \"Log In\" button", &LLPanelLoginListener::onClickConnect); + add("login", + "Login to Second Life with saved credentials.\n" + "Pass [\"username\"] to select credentials previously saved with that username.\n" + "Pass [\"slurl\"] to specify target location.\n" + "Pass [\"grid\"] to select one of the valid grids in grids.xml.", + &LLPanelLoginListener::login, + llsd::map("reply", LLSD::String())); + add("savedLogins", + "Return \"logins\" as a list of {} saved login names.\n" + "Pass [\"grid\"] to select one of the valid grids in grids.xml.", + &LLPanelLoginListener::savedLogins, + llsd::map("reply", LLSD::String())); } void LLPanelLoginListener::onClickConnect(const LLSD&) const { mPanel->onClickConnect(false); } + +void LLPanelLoginListener::login(const LLSD& args) const +{ + // Although we require a "reply" key, don't instantiate a Response object: + // if things go well, response to our invoker will come later, once the + // viewer gets the response from the login server. + std::string username(args["username"]), slurlstr(args["slurl"]), grid(args["grid"]); + LL_DEBUGS("AppInit") << "Login with saved credentials"; + if (! username.empty()) + { + LL_CONT << " for user " << std::quoted(username); + } + if (! slurlstr.empty()) + { + LL_CONT << " to location " << std::quoted(slurlstr); + } + if (! grid.empty()) + { + LL_CONT << " on grid " << std::quoted(grid); + } + LL_CONT << LL_ENDL; + + // change grid first, allowing slurl to override + auto& gridmgr{ LLGridManager::instance() }; + if (! grid.empty()) + { + // setGridChoice() can throw LLInvalidGridName -- but if so, let it + // propagate, trusting that LLEventAPI will catch it and send an + // appropriate reply. + auto server_combo = mPanel->getChild("server_combo"); + server_combo->setSimple(gridmgr.getGridLabel(grid)); + LLPanelLogin::updateServer(); + } + if (! slurlstr.empty()) + { + LLSLURL slurl(slurlstr); + if (! slurl.isSpatial()) + { + // don't bother with LLTHROW() because we expect LLEventAPI to + // catch and report back to invoker + throw DispatchError(stringize("Invalid start SLURL ", std::quoted(slurlstr))); + } + // Bypass LLStartUp::setStartSLURL() because, after validating as + // above, it bounces right back to LLPanelLogin::onUpdateStartSLURL(). + // It also sets the "NextLoginLocation" user setting, but as with grid, + // we don't yet know whether that's desirable for scripted login. + LLPanelLogin::onUpdateStartSLURL(slurl); + } + if (! username.empty()) + { + // Transform (e.g.) "Nat Linden" to the internal form expected by + // loadFromCredentialMap(), e.g. "nat_linden" + LLStringUtil::toLower(username); + LLStringUtil::replaceChar(username, ' ', '_'); + LLStringUtil::replaceChar(username, '.', '_'); + + // call gridmgr.getGrid() because our caller didn't necessarily pass + // ["grid"] -- or it might have been overridden by the SLURL + auto cred = gSecAPIHandler->loadFromCredentialMap("login_list", + gridmgr.getGrid(), + username); + LLPanelLogin::setFields(cred); + } + + if (mPanel->getChild("username_combo")->getValue().asString().empty() || + mPanel->getChild("password_edit") ->getValue().asString().empty()) + { + // as above, let LLEventAPI report back to invoker + throw DispatchError(stringize("Unrecognized username ", std::quoted(username))); + } + + // Here we're about to trigger actual login, which is all we can do in + // this method. All subsequent responses must come via the "login" + // LLEventPump. + LLEventPumps::instance().obtain("login").listen( + "Lua login", + [args] + (const LLBoundListener& conn, const LLSD& update) + { + LLSD response{ llsd::map("ok", false) }; + if (update["change"] == "connect") + { + // success! + response["ok"] = true; + } + else if (update["change"] == "disconnect") + { + // Does this ever actually happen? + // LLLoginInstance::handleDisconnect() says it's a placeholder. + response["error"] = "login disconnect"; + } + else if (update["change"] == "fail.login") + { + // why? + // LLLoginInstance::handleLoginFailure() has special code to + // handle "tos", "update" and "mfa_challenge". Do not respond + // automatically because these things are SUPPOSED to engage + // the user. + // Copy specific response fields because there's an enormous + // chunk of stuff that comes back on success. + LLSD data{ update["data"] }; + const char* fields[] = { + "reason", + "error_code" + }; + for (auto field : fields) + { + response[field] = data[field]; + } + response["error"] = update["message"]; + } + else + { + // Ignore all other "change" values: LLLogin sends multiple update + // events. Only a few of them (above) indicate completion. + return; + } + // For all the completion cases, disconnect from the "login" + // LLEventPump. + conn.disconnect(); + // and at last, send response to the original invoker + sendReply(response, args); + }); + + // Turn the Login crank. + mPanel->onClickConnect(false); +} + +LLSD LLPanelLoginListener::savedLogins(const LLSD& args) const +{ + LL_INFOS() << "called with " << args << LL_ENDL; + std::string grid = (args.has("grid")? args["grid"].asString() + : LLGridManager::instance().getGrid()); + if (! gSecAPIHandler->hasCredentialMap("login_list", grid)) + { + LL_INFOS() << "no creds for " << grid << LL_ENDL; + return llsd::map("error", + stringize("No saved logins for grid ", std::quoted(grid))); + } + LLSecAPIHandler::credential_map_t creds; + gSecAPIHandler->loadCredentialMap("login.list", grid, creds); + auto result = llsd::map( + "logins", + llsd::toArray( + creds, + [](const auto& pair) + { + return llsd::map( + "name", + LLPanelLogin::getUserName(pair.second), + "key", + pair.first); + })); + LL_INFOS() << "returning creds " << result << LL_ENDL; + return result; +} diff --git a/indra/newview/llpanelloginlistener.h b/indra/newview/llpanelloginlistener.h index b552ccd5b0..f2f4d70ff9 100644 --- a/indra/newview/llpanelloginlistener.h +++ b/indra/newview/llpanelloginlistener.h @@ -40,6 +40,8 @@ public: private: void onClickConnect(const LLSD&) const; + void login(const LLSD&) const; + LLSD savedLogins(const LLSD&) const; LLPanelLogin* mPanel; }; diff --git a/indra/newview/llsecapi.h b/indra/newview/llsecapi.h index 5cc78d09dc..367bfe1469 100644 --- a/indra/newview/llsecapi.h +++ b/indra/newview/llsecapi.h @@ -311,7 +311,7 @@ public: mIdentifier = identifier; mAuthenticator = authenticator; } - virtual LLSD getIdentifier() { return mIdentifier; } + virtual LLSD getIdentifier() const { return mIdentifier; } virtual void identifierType(std::string& idType); virtual LLSD getAuthenticator() { return mAuthenticator; } virtual void authenticatorType(std::string& authType); diff --git a/indra/newview/scripts/lua/require/login.lua b/indra/newview/scripts/lua/require/login.lua index 0d8591cace..f4e65b1606 100644 --- a/indra/newview/scripts/lua/require/login.lua +++ b/indra/newview/scripts/lua/require/login.lua @@ -1,19 +1,12 @@ -local UI = require 'UI' local leap = require 'leap' +local startup = require 'startup' +local mapargs = require 'mapargs' -local function login(username, password) - if username and password then - local userpath = '//username_combo/Combo Text Entry' - local passpath = '//password_edit' - -- first clear anything presently in those text fields - for _, path in pairs({userpath, passpath}) do - UI.click(path) - UI.keypress{keysym='Backsp', path=path} - end - UI.type{path=userpath, text=username} - UI.type{path=passpath, text=password} - end - leap.send('LLPanelLogin', {op='onClickConnect'}) +local function login(...) + startup.wait('STATE_LOGIN_WAIT') + local args = mapargs('username,grid,slurl', ...) + args.op = 'login' + return leap.request('LLPanelLogin', args) end return login diff --git a/indra/newview/scripts/lua/test_login.lua b/indra/newview/scripts/lua/test_login.lua index a8c31807bc..f263940f79 100644 --- a/indra/newview/scripts/lua/test_login.lua +++ b/indra/newview/scripts/lua/test_login.lua @@ -1,7 +1,7 @@ -startup = require 'startup' +inspect = require 'inspect' login = require 'login' -startup.wait('STATE_LOGIN_WAIT') -login() --- Fill in valid credentials as they would be entered on the login screen --- login('My Username', 'password') +print(inspect(login{ + username='Nat Linden', +-- grid='agni' + })) -- cgit v1.2.3 From b12d135c38e327774c01594acfa96e37a9e67a64 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 5 Aug 2024 11:24:01 -0400 Subject: Fix a couple problems with "LLPanelLogin" listener (thanks Maxim!). Convert plain grid (e.g. "agni") to domain form (e.g. "util.agni.lindenlab.com"). Fix a typo in `savedLogins()`: "login_list", not "login.list". login.lua now returns a table with two functions: `login.login()` and `login.savedLogins()`. Defend Lua caller against trying to engage login features too late in startup sequence: in addition to waiting for "STATE_LOGIN_WAIT", produce an error if `startup.state()` is beyond that state. Since by then `LLPanelLogin` is destroyed, `leap.request("LLPanelLogin", ...)` would get no response, causing the calling Lua script to hang until viewer shutdown. --- indra/newview/llpanelloginlistener.cpp | 22 ++++++++++++++++++++-- indra/newview/scripts/lua/require/login.lua | 23 ++++++++++++++++++++++- indra/newview/scripts/lua/test_login.lua | 9 ++++++--- 3 files changed, 48 insertions(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/newview/llpanelloginlistener.cpp b/indra/newview/llpanelloginlistener.cpp index a4418145d7..aeaf2d1d00 100644 --- a/indra/newview/llpanelloginlistener.cpp +++ b/indra/newview/llpanelloginlistener.cpp @@ -72,6 +72,23 @@ void LLPanelLoginListener::onClickConnect(const LLSD&) const mPanel->onClickConnect(false); } +namespace +{ + + std::string gridkey(const std::string& grid) + { + if (grid.find('.') != std::string::npos) + { + return grid; + } + else + { + return stringize("util.", grid, ".lindenlab.com"); + } + } + +} // anonymous namespace + void LLPanelLoginListener::login(const LLSD& args) const { // Although we require a "reply" key, don't instantiate a Response object: @@ -97,6 +114,7 @@ void LLPanelLoginListener::login(const LLSD& args) const auto& gridmgr{ LLGridManager::instance() }; if (! grid.empty()) { + grid = gridkey(grid); // setGridChoice() can throw LLInvalidGridName -- but if so, let it // propagate, trusting that LLEventAPI will catch it and send an // appropriate reply. @@ -202,7 +220,7 @@ void LLPanelLoginListener::login(const LLSD& args) const LLSD LLPanelLoginListener::savedLogins(const LLSD& args) const { LL_INFOS() << "called with " << args << LL_ENDL; - std::string grid = (args.has("grid")? args["grid"].asString() + std::string grid = (args.has("grid")? gridkey(args["grid"].asString()) : LLGridManager::instance().getGrid()); if (! gSecAPIHandler->hasCredentialMap("login_list", grid)) { @@ -211,7 +229,7 @@ LLSD LLPanelLoginListener::savedLogins(const LLSD& args) const stringize("No saved logins for grid ", std::quoted(grid))); } LLSecAPIHandler::credential_map_t creds; - gSecAPIHandler->loadCredentialMap("login.list", grid, creds); + gSecAPIHandler->loadCredentialMap("login_list", grid, creds); auto result = llsd::map( "logins", llsd::toArray( diff --git a/indra/newview/scripts/lua/require/login.lua b/indra/newview/scripts/lua/require/login.lua index f4e65b1606..50fc9e3793 100644 --- a/indra/newview/scripts/lua/require/login.lua +++ b/indra/newview/scripts/lua/require/login.lua @@ -2,11 +2,32 @@ local leap = require 'leap' local startup = require 'startup' local mapargs = require 'mapargs' -local function login(...) +local login = {} + +local function ensure_login_state(op) + -- no point trying to login until the viewer is ready startup.wait('STATE_LOGIN_WAIT') + -- Once we've actually started login, LLPanelLogin is destroyed, and so is + -- its "LLPanelLogin" listener. At that point, + -- leap.request("LLPanelLogin", ...) will hang indefinitely because no one + -- is listening on that LLEventPump any more. Intercept that case and + -- produce a sensible error. + local state = startup.state() + if startup.before('STATE_LOGIN_WAIT', state) then + error(`Can't engage login operation {op} once we've reached state {state}`, 2) + end +end + +function login.login(...) + ensure_login_state('login') local args = mapargs('username,grid,slurl', ...) args.op = 'login' return leap.request('LLPanelLogin', args) end +function login.savedLogins(grid) + ensure_login_state('savedLogins') + return leap.request('LLPanelLogin', {op='savedLogins'})['logins'] +end + return login diff --git a/indra/newview/scripts/lua/test_login.lua b/indra/newview/scripts/lua/test_login.lua index f263940f79..54d3635a41 100644 --- a/indra/newview/scripts/lua/test_login.lua +++ b/indra/newview/scripts/lua/test_login.lua @@ -1,7 +1,10 @@ inspect = require 'inspect' login = require 'login' -print(inspect(login{ - username='Nat Linden', --- grid='agni' +local grid = 'agni' +print(inspect(login.savedLogins(grid))) + +print(inspect(login.login{ + username='Your Username', + grid=grid })) -- cgit v1.2.3