// Returns a valid session or throws internal static Session LoginWithOob(string username, string password, int keyIterationCount, Dictionary <string, string> parameters, ClientInfo clientInfo, IUi ui, RestClient rest) { var answer = ApproveOob(username, parameters, ui, rest); if (answer == OobResult.Cancel) { throw new CanceledMultiFactorException("Out of band step is canceled by the user"); } var extraParameters = new Dictionary <string, object>(1); if (answer.WaitForOutOfBand) { extraParameters["outofbandrequest"] = 1; } else { extraParameters["otp"] = answer.Passcode; } Session session; for (;;) { // In case of the OOB auth the server doesn't respond instantly. This works more like a long poll. // The server times out in about 10 seconds so there's no need to back off. var response = PerformSingleLoginRequest(username, password, keyIterationCount, extraParameters, clientInfo, rest); session = ExtractSessionFromLoginResponse(response, keyIterationCount, clientInfo); if (session != null) { break; } if (GetOptionalErrorAttribute(response, "cause") != "outofbandrequired") { throw MakeLoginError(response); } // Retry extraParameters["outofbandretry"] = "1"; extraParameters["outofbandretryid"] = GetErrorAttribute(response, "retryid"); } if (answer.RememberMe) { MarkDeviceAsTrusted(session, clientInfo, rest); } return(session); }
// // Internal // internal static Session Login(string username, string password, ClientInfo clientInfo, IUi ui, RestClient rest) { // 1. First we need to request PBKDF2 key iteration count. // // We no longer request the iteration count from the server in a separate request because it // started to fail in weird ways. It seems there's a special combination or the UA and cookies // that returns the correct result. And that is not 100% reliable. After two or three attempts // it starts to fail again with an incorrect result. // // So we just went back a few years to the original way LastPass used to handle the iterations. // Namely, submit the default value and if it fails, the error would contain the correct value: // <response><error iterations="5000" /></response> var keyIterationCount = 100100; XDocument response = null; Session session = null; for (var i = 0; i < 2; i++) { // 2. Knowing the iterations count we can hash the password and log in. // One the first attempt simply with the username and password. response = PerformSingleLoginRequest(username, password, keyIterationCount, new Dictionary <string, object>(), clientInfo, rest); session = ExtractSessionFromLoginResponse(response, keyIterationCount, clientInfo); if (session != null) { return(session); } // It's possible for the request above to come back with the correct iteration count. // In this case we have to parse and repeat. var correctIterationCount = GetOptionalErrorAttribute(response, "iterations"); if (correctIterationCount == null) { break; } if (!int.TryParse(correctIterationCount, out keyIterationCount)) { throw new InternalErrorException($"Failed to parse the iteration count, expected an integer value '{correctIterationCount}'"); } } // 3. The simple login failed. This is usually due to some error, invalid credentials or // a multifactor authentication being enabled. var cause = GetOptionalErrorAttribute(response, "cause"); if (cause == null) { throw MakeLoginError(response); } // 3.1. One-time-password is required if (KnownOtpMethods.TryGetValue(cause, out var otpMethod)) { session = LoginWithOtp(username, password, keyIterationCount, otpMethod, clientInfo, ui, rest); } // 3.2. Some out-of-bound authentication is enabled. This does not require any // additional input from the user. else if (cause == "outofbandrequired") { session = LoginWithOob(username, password, keyIterationCount, GetAllErrorAttributes(response), clientInfo, ui, rest); } // Nothing worked if (session == null) { throw MakeLoginError(response); } return(session); }
// // Internal // internal static Session Login(string username, string password, ClientInfo clientInfo, IUi ui, RestClient rest) { // 1. First we need to request PBKDF2 key iteration count. var keyIterationCount = RequestIterationCount(username, rest); // 2. Knowing the iterations count we can hash the password and log in. // One the first attempt simply with the username and password. var response = PerformSingleLoginRequest(username, password, keyIterationCount, new Dictionary <string, object>(), clientInfo, rest); var session = ExtractSessionFromLoginResponse(response, keyIterationCount, clientInfo); if (session != null) { return(session); } // 3. The simple login failed. This is usually due to some error, invalid credentials or // a multifactor authentication being enabled. var cause = GetOptionalErrorAttribute(response, "cause"); if (cause == null) { throw MakeLoginError(response); } // 3.1. One-time-password is required if (KnownOtpMethods.TryGetValue(cause, out var otpMethod)) { session = LoginWithOtp(username, password, keyIterationCount, otpMethod, clientInfo, ui, rest); } // 3.2. Some out-of-bound authentication is enabled. This does not require any // additional input from the user. else if (cause == "outofbandrequired") { session = LoginWithOob(username, password, keyIterationCount, GetAllErrorAttributes(response), clientInfo, ui, rest); } // Nothing worked if (session == null) { throw MakeLoginError(response); } return(session); }