diff options
Diffstat (limited to 'indra/viewer_components')
-rw-r--r-- | indra/viewer_components/CMakeLists.txt | 1 | ||||
-rw-r--r-- | indra/viewer_components/login/CMakeLists.txt | 43 | ||||
-rw-r--r-- | indra/viewer_components/login/lllogin.cpp | 383 | ||||
-rw-r--r-- | indra/viewer_components/login/lllogin.h | 133 | ||||
-rw-r--r-- | indra/viewer_components/login/tests/lllogin_test.cpp | 382 |
5 files changed, 942 insertions, 0 deletions
diff --git a/indra/viewer_components/CMakeLists.txt b/indra/viewer_components/CMakeLists.txt new file mode 100644 index 0000000000..d5eea0d0b0 --- /dev/null +++ b/indra/viewer_components/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(login) diff --git a/indra/viewer_components/login/CMakeLists.txt b/indra/viewer_components/login/CMakeLists.txt new file mode 100644 index 0000000000..434b58f5c7 --- /dev/null +++ b/indra/viewer_components/login/CMakeLists.txt @@ -0,0 +1,43 @@ +project(login) + +include(00-Common) +include(LLCommon) +include(LLMath) +include(LLXML) +include(Pth) + +include_directories( + ${LLCOMMON_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLXML_INCLUDE_DIRS} + ${PTH_INCLUDE_DIRS} + ) + +set(login_SOURCE_FILES + lllogin.cpp + ) + +set(login_HEADER_FILES + lllogin.h + ) + +set_source_files_properties(${login_HEADER_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) + +list(APPEND + login_SOURCE_FILES + ${login_HEADER_FILES} + ) + +add_library(lllogin + ${login_SOURCE_FILES} + ) + +target_link_libraries(lllogin + ${LLCOMMON_LIBRARIES} + ${LLMATH_LIBRARIES} + ${LLXML_LIBRARIES} + ${PTH_LIBRARIES} + ) + +ADD_BUILD_TEST(lllogin lllogin "") diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp new file mode 100644 index 0000000000..7f2b27e64c --- /dev/null +++ b/indra/viewer_components/login/lllogin.cpp @@ -0,0 +1,383 @@ +/** + * @file lllogin.cpp + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include <boost/coroutine/coroutine.hpp> +#include "linden_common.h" +#include "llsd.h" +#include "llsdutil.h" + +/*==========================================================================*| +#ifdef LL_WINDOWS + // non-virtual destructor warning, boost::statechart does this intentionally. + #pragma warning (disable : 4265) +#endif +|*==========================================================================*/ + +#include "lllogin.h" + +#include <boost/bind.hpp> +#include <boost/scoped_ptr.hpp> + +#include "llevents.h" +#include "lleventfilter.h" +#include "lleventcoro.h" + +//********************* +// LLLogin +// *NOTE:Mani - Is this Impl needed now that the state machine runs the show? +class LLLogin::Impl +{ +public: + Impl(): + mPump("login", true) // Create the module's event pump with a tweaked (unique) name. + { + mValidAuthResponse["status"] = LLSD(); + mValidAuthResponse["errorcode"] = LLSD(); + mValidAuthResponse["error"] = LLSD(); + mValidAuthResponse["transfer_rate"] = LLSD(); + } + + void connect(const std::string& uri, const LLSD& credentials); + void disconnect(); + LLEventPump& getEventPump() { return mPump; } + +private: + void sendProgressEvent(const std::string& desc, const LLSD& data = LLSD::emptyMap()) + { + LLSD status_data; + status_data["state"] = desc; + status_data["progress"] = 0.0f; + + if(mAuthResponse.has("transfer_rate")) + { + status_data["transfer_rate"] = mAuthResponse["transfer_rate"]; + } + + if(data.size() != 0) + { + status_data["data"] = data; + } + + mPump.post(status_data); + } + + LLSD validateResponse(const std::string& pumpName, const LLSD& response) + { + // Validate the response. If we don't recognize it, things + // could get ugly. + std::string mismatch(llsd_matches(mValidAuthResponse, response)); + if (! mismatch.empty()) + { + LL_ERRS("LLLogin") << "Received unrecognized event (" << mismatch << ") on " + << pumpName << "pump: " << response + << LL_ENDL; + return LLSD(); + } + + return response; + } + + typedef boost::coroutines::coroutine<void(const std::string&, const LLSD&)> coroutine_type; + + void login_(coroutine_type::self& self, const std::string& uri, const LLSD& credentials); + + boost::scoped_ptr<coroutine_type> mCoro; + LLEventStream mPump; + LLSD mAuthResponse, mValidAuthResponse; +}; + +void LLLogin::Impl::connect(const std::string& uri, const LLSD& credentials) +{ + // If there's a previous coroutine instance, and that instance is still + // active, destroying the instance will terminate the coroutine by + // throwing an exception, thus unwinding the stack and destroying all + // local objects. It should (!) all Just Work. Nonetheless, it would be + // strange, so make a note of it. + if (mCoro && *mCoro) + { + LL_WARNS("LLLogin") << "Previous login attempt interrupted by new request" << LL_ENDL; + } + + // Construct a coroutine that will run our login_() method; placeholders + // forward the params from the (*mCoro)(etc.) call below. Using scoped_ptr + // ensures that if mCoro was already pointing to a previous instance, that + // old instance will be destroyed as noted above. + mCoro.reset(new coroutine_type(boost::bind(&Impl::login_, this, _1, _2, _3))); + // Run the coroutine until its first wait; at that point, return here. + (*mCoro)(std::nothrow, uri, credentials); + std::cout << "Here I am\n"; +} + +void LLLogin::Impl::login_(coroutine_type::self& self, + const std::string& uri, const LLSD& credentials) +{ + // Mimicking previous behavior, every time the OldSchoolLogin state + // machine arrived in the Offline state, it would send a progress + // announcement. + sendProgressEvent("offline", mAuthResponse["responses"]); + // Arriving in SRVRequest state + LLEventStream replyPump("reply", true); + // Should be an array of one or more uri strings. + LLSD rewrittenURIs; + { + LLEventTimeout filter(replyPump); + sendProgressEvent("srvrequest"); + + // Request SRV record. + LL_INFOS("LLLogin") << "Requesting SRV record from " << uri << LL_ENDL; + + // *NOTE:Mani - Completely arbitrary timeout value for SRV request. + filter.errorAfter(5, "SRV Request timed out!"); + + // Make request + LLSD request; + request["op"] = "rewriteURI"; + request["uri"] = uri; + request["reply"] = replyPump.getName(); + rewrittenURIs = postAndWait(self, request, "LLAres", filter); + } // we no longer need the filter + + LLEventPump& xmlrpcPump(LLEventPumps::instance().obtain("LLXMLRPCTransaction")); + + // Loop through the rewrittenURIs, counting attempts along the way. + // Because of possible redirect responses, we may make more than one + // attempt per rewrittenURIs entry. + LLSD::Integer attempts = 0; + for (LLSD::array_const_iterator urit(rewrittenURIs.beginArray()), + urend(rewrittenURIs.endArray()); + urit != urend; ++urit) + { + LLSD request(credentials); + request["reply"] = replyPump.getName(); + request["uri"] = *urit; + std::string status; + + // Loop back to here if login attempt redirects to a different + // request["uri"] + for (;;) + { + ++attempts; + LLSD progress_data; + progress_data["attempt"] = attempts; + progress_data["request"] = request; + sendProgressEvent("authenticating", progress_data); + + // We expect zero or more "Downloading" status events, followed by + // exactly one event with some other status. Use postAndWait() the + // first time, because -- at least in unit-test land -- it's + // possible for the reply to arrive before the post() call + // returns. Subsequent responses, of course, must be awaited + // without posting again. + for (mAuthResponse = validateResponse(replyPump.getName(), + postAndWait(self, request, xmlrpcPump, replyPump, "reply")); + mAuthResponse["status"].asString() == "Downloading"; + mAuthResponse = validateResponse(replyPump.getName(), + waitForEventOn(self, replyPump))) + { + // Still Downloading -- send progress update. + sendProgressEvent("downloading"); + } + status = mAuthResponse["status"].asString(); + + // Okay, we've received our final status event for this + // request. Unless we got a redirect response, break the retry + // loop for the current rewrittenURIs entry. + if (! (status == "Complete" && + mAuthResponse["responses"]["login"].asString() == "indeterminate")) + { + break; + } + + // Here the login service at the current URI is redirecting us + // to some other URI ("indeterminate" -- why not "redirect"?). + // The response should contain another uri to try, with its + // own auth method. + request["uri"] = mAuthResponse["next_url"]; + request["method"] = mAuthResponse["next_method"]; + } // loop back to try the redirected URI + + // Here we're done with redirects for the current rewrittenURIs + // entry. + if (status == "Complete") + { + // StatusComplete does not imply auth success. Check the + // actual outcome of the request. We've already handled the + // "indeterminate" case in the loop above. + sendProgressEvent((mAuthResponse["responses"]["login"].asString() == "true")? + "online" : "offline", + mAuthResponse["responses"]); + return; // Done! + } + // If we don't recognize status at all, trouble + if (! (status == "CURLError" + || status == "XMLRPCError" + || status == "OtherError")) + { + LL_ERRS("LLLogin") << "Unexpected status from " << xmlrpcPump.getName() << " pump: " + << mAuthResponse << LL_ENDL; + return; + } + + // Here status IS one of the errors tested above. + } // Retry if there are any more rewrittenURIs. + + // Here we got through all the rewrittenURIs without succeeding. Tell + // caller this didn't work out so well. Of course, the only failure data + // we can reasonably show are from the last of the rewrittenURIs. + sendProgressEvent("offline", mAuthResponse["responses"]); +} + +void LLLogin::Impl::disconnect() +{ + sendProgressEvent("offline", mAuthResponse["responses"]); +} + +//********************* +// LLLogin +LLLogin::LLLogin() : + mImpl(new LLLogin::Impl()) +{ +} + +LLLogin::~LLLogin() +{ +} + +void LLLogin::connect(const std::string& uri, const LLSD& credentials) +{ + mImpl->connect(uri, credentials); +} + + +void LLLogin::disconnect() +{ + mImpl->disconnect(); +} + +LLEventPump& LLLogin::getEventPump() +{ + return mImpl->getEventPump(); +} + +// The following is the list of important functions that happen in the +// current login process that we want to move to this login module. + +// The list associates to event with the original idle_startup() 'STATE'. + +// Rewrite URIs + // State_LOGIN_AUTH_INIT +// Given a vector of login uris (usually just one), perform a dns lookup for the +// SRV record from each URI. I think this is used to distribute login requests to +// a single URI to multiple hosts. +// This is currently a synchronous action. (See LLSRV::rewriteURI() implementation) +// On dns lookup error the output uris == the input uris. +// +// Input: A vector of login uris +// Output: A vector of login uris +// +// Code: +// std::vector<std::string> uris; +// LLViewerLogin::getInstance()->getLoginURIs(uris); +// std::vector<std::string>::const_iterator iter, end; +// for (iter = uris.begin(), end = uris.end(); iter != end; ++iter) +// { +// std::vector<std::string> rewritten; +// rewritten = LLSRV::rewriteURI(*iter); +// sAuthUris.insert(sAuthUris.end(), +// rewritten.begin(), rewritten.end()); +// } +// sAuthUriNum = 0; + +// Authenticate +// STATE_LOGIN_AUTHENTICATE +// Connect to the login server, presumably login.cgi, requesting the login +// and a slew of related initial connection information. +// This is an asynch action. The final response, whether success or error +// is handled by STATE_LOGIN_PROCESS_REPONSE. +// There is no immediate error or output from this call. +// +// Input: +// URI +// Credentials (first, last, password) +// Start location +// Bool Flags: +// skip optional update +// accept terms of service +// accept critical message +// Last exec event. (crash state of previous session) +// requested optional data (inventory skel, initial outfit, etc.) +// local mac address +// viewer serial no. (md5 checksum?) + +//sAuthUriNum = llclamp(sAuthUriNum, 0, (S32)sAuthUris.size()-1); +//LLUserAuth::getInstance()->authenticate( +// sAuthUris[sAuthUriNum], +// auth_method, +// firstname, +// lastname, +// password, // web_login_key, +// start.str(), +// gSkipOptionalUpdate, +// gAcceptTOS, +// gAcceptCriticalMessage, +// gLastExecEvent, +// requested_options, +// hashed_mac_string, +// LLAppViewer::instance()->getSerialNumber()); + +// +// Download the Response +// STATE_LOGIN_NO_REPONSE_YET and STATE_LOGIN_DOWNLOADING +// I had assumed that this was default behavior of the message system. However... +// During login, the message system is checked only by these two states in idle_startup(). +// I guess this avoids the overhead of checking network messages for those login states +// that don't need to do so, but geez! +// There are two states to do this one function just to update the login +// status text from 'Logging In...' to 'Downloading...' +// + +// +// Handle Login Response +// STATE_LOGIN_PROCESS_RESPONSE +// +// This state handle the result of the request to login. There is a metric ton of +// code in this case. This state will transition to: +// STATE_WORLD_INIT, on success. +// STATE_AUTHENTICATE, on failure. +// STATE_UPDATE_CHECK, to handle user during login interaction like TOS display. +// +// Much of the code in this case belongs on the viewer side of the fence and not in login. +// Login should probably return with a couple of events, success and failure. +// Failure conditions can be specified in the events data pacet to allow the viewer +// to re-engauge login as is appropriate. (Or should there be multiple failure messages?) +// Success is returned with the data requested from the login. According to OGP specs +// there may be intermediate steps before reaching this result in future login +// implementations. diff --git a/indra/viewer_components/login/lllogin.h b/indra/viewer_components/login/lllogin.h new file mode 100644 index 0000000000..0598b4e457 --- /dev/null +++ b/indra/viewer_components/login/lllogin.h @@ -0,0 +1,133 @@ +/** + * @file lllogin.h + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLLOGIN_H +#define LL_LLLOGIN_H + +#include <boost/scoped_ptr.hpp> + +class LLSD; +class LLEventPump; + +/** + * @class LLLogin + * @brief Class to encapsulate the action and state of grid login. + */ +class LLLogin +{ +public: + LLLogin(); + ~LLLogin(); + + /** + * Make a connection to a grid. + * @param uri The 'well known and published' authentication URL. + * @param credentials LLSD data that contians the credentials. + * *NOTE:Mani The credential data can vary depending upon the authentication + * method used. The current interface matches the values passed to + * the XMLRPC login request. + { + method : string, + first : string, + last : string, + passwd : string, + start : string, + skipoptional : bool, + agree_to_tos : bool, + read_critical : bool, + last_exec_event : int, + version : string, + channel : string, + mac : string, + id0 : string, + options : [ strings ] + } + + */ + void connect(const std::string& uri, const LLSD& credentials); + + /** + * Disconnect from a the current connection. + */ + void disconnect(); + + /** + * Retrieve the event pump from this login class. + */ + LLEventPump& getEventPump(); + + /* + Event API + + LLLogin will issue multiple events to it pump to indicate the + progression of states through login. The most important + states are "offline" and "online" which indicate auth failure + and auth success respectively. + + pump: login (tweaked) + These are the events posted to the 'login' + event pump from the login module. + { + state : string, // See below for the list of states. + progress : real // for progress bar. + data : LLSD // Dependent upon state. + } + + States for method 'login_to_simulator' + offline - set initially state and upon failure. data is the server response. + srvrequest - upon uri rewrite request. no data. + authenticating - upon auth request. data, 'attempt' number and 'request' llsd. + downloading - upon ack from auth server, before completion. no data + online - upon auth success. data is server response. + + + Dependencies: + pump: LLAres + LLLogin makes a request for a SRV record from the uri provided by the connect method. + The following event pump should exist to service that request. + pump name: LLAres + request = { + op : "rewriteURI" + uri : string + reply : string + + pump: LLXMLRPCListener + The request merely passes the credentials LLSD along, with one additional + member, 'reply', which is the string name of the event pump to reply on. + + */ + +private: + class Impl; + boost::scoped_ptr<Impl> mImpl; +}; + +#endif // LL_LLLOGIN_H diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp new file mode 100644 index 0000000000..07c9db1099 --- /dev/null +++ b/indra/viewer_components/login/tests/lllogin_test.cpp @@ -0,0 +1,382 @@ +/** + * @file llviewerlogin_test.cpp + * @author Mark Palange + * @date 2009-02-26 + * @brief Tests of lllazy.h. + * + * $LicenseInfo:firstyear=2009&license=internal$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "../lllogin.h" +// STL headers +// std headers +#include <iostream> +// external library headers +// other Linden headers +#include "llsd.h" +#include "../../../test/lltut.h" +#include "llevents.h" + +/***************************************************************************** +* TUT +*****************************************************************************/ +// This is a listener to receive results from lllogin. +class LoginListener +{ + std::string mName; + LLSD mLastEvent; +public: + LoginListener(const std::string& name) : + mName(name) + {} + + bool call(const LLSD& event) + { + std::cout << "LoginListener called!: " << event << std::endl; + mLastEvent = event; + return false; + } + + LLBoundListener listenTo(LLEventPump& pump) + { + return pump.listen(mName, boost::bind(&LoginListener::call, this, _1)); + } + + const LLSD& lastEvent() { return mLastEvent; } +}; + +class LLAresListener +{ + std::string mName; + LLSD mEvent; + bool mImmediateResponse; + bool mMultipleURIResponse; + +public: + LLAresListener(const std::string& name, + bool i = false, + bool m = false + ) : + mName(name), + mImmediateResponse(i), + mMultipleURIResponse(m) + {} + + bool handle_event(const LLSD& event) + { + std::cout << "LLAresListener called!: " << event << std::endl; + mEvent = event; + if(mImmediateResponse) + { + sendReply(); + } + return false; + } + + void sendReply() + { + if(mEvent["op"].asString() == "rewriteURI") + { + LLSD result; + if(mMultipleURIResponse) + { + result.append(LLSD("login.foo.com")); + } + result.append(mEvent["uri"]); + LLEventPumps::instance().obtain(mEvent["reply"]).post(result); + } + } + + LLBoundListener listenTo(LLEventPump& pump) + { + return pump.listen(mName, boost::bind(&LLAresListener::handle_event, this, _1)); + } +}; + +class LLXMLRPCListener +{ + std::string mName; + LLSD mEvent; + bool mImmediateResponse; + LLSD mResponse; + +public: + LLXMLRPCListener(const std::string& name, + bool i = false, + const LLSD& response = LLSD() + ) : + mName(name), + mImmediateResponse(i), + mResponse(response) + { + if(mResponse.isUndefined()) + { + mResponse["status"] = "Complete"; // StatusComplete + mResponse["errorcode"] = 0; + mResponse["error"] = "dummy response"; + mResponse["transfer_rate"] = 0; + mResponse["responses"]["login"] = true; + } + } + + void setResponse(const LLSD& r) + { + mResponse = r; + } + + bool handle_event(const LLSD& event) + { + std::cout << "LLXMLRPCListener called!: " << event << std::endl; + mEvent = event; + if(mImmediateResponse) + { + sendReply(); + } + return false; + } + + void sendReply() + { + LLEventPumps::instance().obtain(mEvent["reply"]).post(mResponse); + } + + LLBoundListener listenTo(LLEventPump& pump) + { + return pump.listen(mName, boost::bind(&LLXMLRPCListener::handle_event, this, _1)); + } +}; + +namespace tut +{ + struct llviewerlogin_data + { + llviewerlogin_data() : + pumps(LLEventPumps::instance()) + {} + LLEventPumps& pumps; + }; + + typedef test_group<llviewerlogin_data> llviewerlogin_group; + typedef llviewerlogin_group::object llviewerlogin_object; + llviewerlogin_group llviewerlogingrp("llviewerlogin"); + + template<> template<> + void llviewerlogin_object::test<1>() + { + // Testing login with immediate repsonses from Ares and XMLPRC + // The response from both requests will come before the post request exits. + // This tests an edge case of the login state handling. + LLEventStream llaresPump("LLAres"); // Dummy LLAres pump. + LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump + + bool respond_immediately = true; + // Have 'dummy ares' repsond immediately. + LLAresListener dummyLLAres("dummy_llares", respond_immediately); + dummyLLAres.listenTo(llaresPump); + + // Have dummy XMLRPC respond immediately. + LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc", respond_immediately); + dummyXMLRPC.listenTo(xmlrpcPump); + + LLLogin login; + + LoginListener listener("test_ear"); + listener.listenTo(login.getEventPump()); + + LLSD credentials; + credentials["first"] = "foo"; + credentials["last"] = "bar"; + credentials["passwd"] = "secret"; + + login.connect("login.bar.com", credentials); + + ensure_equals("Online state", listener.lastEvent()["state"].asString(), "online"); + } + + template<> template<> + void llviewerlogin_object::test<2>() + { + // Tests a successful login in with delayed responses. + // Also includes 'failure' that cause the login module + // To re-attempt connection, once from a basic failure + // and once from the 'indeterminate' response. + + set_test_name("LLLogin multiple srv uris w/ success"); + + // Testing normal login procedure. + LLEventStream llaresPump("LLAres"); // Dummy LLAres pump. + LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump + + bool respond_immediately = false; + bool multiple_addresses = true; + LLAresListener dummyLLAres("dummy_llares", respond_immediately, multiple_addresses); + dummyLLAres.listenTo(llaresPump); + + LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc"); + dummyXMLRPC.listenTo(xmlrpcPump); + + LLLogin login; + + LoginListener listener("test_ear"); + listener.listenTo(login.getEventPump()); + + LLSD credentials; + credentials["first"] = "foo"; + credentials["last"] = "bar"; + credentials["passwd"] = "secret"; + + login.connect("login.bar.com", credentials); + + ensure_equals("SRV state", listener.lastEvent()["state"].asString(), "srvrequest"); + + dummyLLAres.sendReply(); + + // Test Authenticating State prior to first response. + ensure_equals("Auth state 1", listener.lastEvent()["state"].asString(), "authenticating"); + ensure_equals("Attempt 1", listener.lastEvent()["data"]["attempt"].asInteger(), 1); + ensure_equals("URI 1", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.foo.com"); + + // First send emulated LLXMLRPCListener failure, + // this should return login to the authenticating step and increase the attempt + // count. + LLSD data; + data["status"] = "OtherError"; + data["errorcode"] = 0; + data["error"] = "dummy response"; + data["transfer_rate"] = 0; + dummyXMLRPC.setResponse(data); + dummyXMLRPC.sendReply(); + + ensure_equals("Fail back to authenticate 1", listener.lastEvent()["state"].asString(), "authenticating"); + ensure_equals("Attempt 2", listener.lastEvent()["data"]["attempt"].asInteger(), 2); + ensure_equals("URI 2", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.bar.com"); + + // Now send the 'indeterminate' response. + data.clear(); + data["status"] = "Complete"; // StatusComplete + data["errorcode"] = 0; + data["error"] = "dummy response"; + data["transfer_rate"] = 0; + data["responses"]["login"] = "indeterminate"; + data["next_url"] = "login.indeterminate.com"; + data["next_method"] = "test_login_method"; + dummyXMLRPC.setResponse(data); + dummyXMLRPC.sendReply(); + + ensure_equals("Fail back to authenticate 2", listener.lastEvent()["state"].asString(), "authenticating"); + ensure_equals("Attempt 3", listener.lastEvent()["data"]["attempt"].asInteger(), 3); + ensure_equals("URI 3", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.indeterminate.com"); + + // Finally let the auth succeed. + data.clear(); + data["status"] = "Complete"; // StatusComplete + data["errorcode"] = 0; + data["error"] = "dummy response"; + data["transfer_rate"] = 0; + data["responses"]["login"] = "true"; + dummyXMLRPC.setResponse(data); + dummyXMLRPC.sendReply(); + + ensure_equals("Success state", listener.lastEvent()["state"].asString(), "online"); + + login.disconnect(); + + ensure_equals("Disconnected state", listener.lastEvent()["state"].asString(), "offline"); + } + + template<> template<> + void llviewerlogin_object::test<3>() + { + // Test completed response, that fails to login. + set_test_name("LLLogin valid response, failure (eg. bad credentials)"); + + // Testing normal login procedure. + LLEventStream llaresPump("LLAres"); // Dummy LLAres pump. + LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump + + LLAresListener dummyLLAres("dummy_llares"); + dummyLLAres.listenTo(llaresPump); + + LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc"); + dummyXMLRPC.listenTo(xmlrpcPump); + + LLLogin login; + LoginListener listener("test_ear"); + listener.listenTo(login.getEventPump()); + + LLSD credentials; + credentials["first"] = "who"; + credentials["last"] = "what"; + credentials["passwd"] = "badpasswd"; + + login.connect("login.bar.com", credentials); + + ensure_equals("SRV state", listener.lastEvent()["state"].asString(), "srvrequest"); + + dummyLLAres.sendReply(); + + ensure_equals("Auth state", listener.lastEvent()["state"].asString(), "authenticating"); + + // Send the failed auth request reponse + LLSD data; + data["status"] = "Complete"; + data["errorcode"] = 0; + data["error"] = "dummy response"; + data["transfer_rate"] = 0; + data["responses"]["login"] = "false"; + dummyXMLRPC.setResponse(data); + dummyXMLRPC.sendReply(); + + ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline"); + } + + template<> template<> + void llviewerlogin_object::test<4>() + { + // Test incomplete response, that end the attempt. + set_test_name("LLLogin valid response, failure (eg. bad credentials)"); + + // Testing normal login procedure. + LLEventStream llaresPump("LLAres"); // Dummy LLAres pump. + LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump + + LLAresListener dummyLLAres("dummy_llares"); + dummyLLAres.listenTo(llaresPump); + + LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc"); + dummyXMLRPC.listenTo(xmlrpcPump); + + LLLogin login; + LoginListener listener("test_ear"); + listener.listenTo(login.getEventPump()); + + LLSD credentials; + credentials["first"] = "these"; + credentials["last"] = "don't"; + credentials["passwd"] = "matter"; + + login.connect("login.bar.com", credentials); + + ensure_equals("SRV state", listener.lastEvent()["state"].asString(), "srvrequest"); + + dummyLLAres.sendReply(); + + ensure_equals("Auth state", listener.lastEvent()["state"].asString(), "authenticating"); + + // Send the failed auth request reponse + LLSD data; + data["status"] = "OtherError"; + data["errorcode"] = 0; + data["error"] = "dummy response"; + data["transfer_rate"] = 0; + dummyXMLRPC.setResponse(data); + dummyXMLRPC.sendReply(); + + ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline"); + } +} |