internal static HttpCookies LogInMfa(UserInfo userInfo,
                                             R.LogInResult logInResult,
                                             string token,
                                             IUi ui,
                                             ISecureStorage storage,
                                             RestClient rest)
        {
            void CheckCancel(Ui.Passcode passcode)
            {
                if (passcode == Ui.Passcode.Cancel)
                {
                    throw new CanceledMultiFactorException("Second factor step is canceled by the user");
                }
            }

            var methods = logInResult.MfaMethods?.AllowedMethods;

            if (methods == null)
            {
                throw MakeInvalidResponseError("allowed MFA methods not found");
            }

            Ui.Passcode code;

            if (methods.Contains("totp"))
            {
                code = ui.ProvideGoogleAuthPasscode();
                CheckCancel(code);
                SubmitTotp(userInfo, code, token, logInResult.MfaToken, rest);
            }
            else if (methods.Contains("yubikey"))
            {
                throw new UnsupportedFeatureException(
                          $"Zoho removed support for the classic YubiKey. FIDO U2F is not supported yet.");
            }
            else
            {
                var unsupportedMethods = methods.JoinToString(", ");
                throw new UnsupportedFeatureException($"MFA methods '{unsupportedMethods}' are not supported");
            }

            // Has to be done regardless of the "remember me" setting
            return(MarkDeviceTrusted(userInfo, code.RememberMe, token, logInResult.MfaToken, rest));
        }
Exemple #2
0
        // Returns a valid session or throws
        internal static Session LoginWithOtp(string username,
                                             string password,
                                             int keyIterationCount,
                                             OtpMethod method,
                                             ClientInfo clientInfo,
                                             IUi ui,
                                             RestClient rest)
        {
            var passcode = method switch
            {
                OtpMethod.GoogleAuth => ui.ProvideGoogleAuthPasscode(),
                OtpMethod.MicrosoftAuth => ui.ProvideMicrosoftAuthPasscode(),
                OtpMethod.Yubikey => ui.ProvideYubikeyPasscode(),
                _ => throw new InternalErrorException("Invalid OTP method")
            };

            if (passcode == OtpResult.Cancel)
            {
                throw new CanceledMultiFactorException("Second factor step is canceled by the user");
            }

            var response = PerformSingleLoginRequest(username,
                                                     password,
                                                     keyIterationCount,
                                                     new Dictionary <string, object> {
                ["otp"] = passcode.Passcode
            },
                                                     clientInfo,
                                                     rest);

            var session = ExtractSessionFromLoginResponse(response, keyIterationCount, clientInfo);

            if (session == null)
            {
                throw MakeLoginError(response);
            }

            if (passcode.RememberMe)
            {
                MarkDeviceAsTrusted(session, clientInfo, rest);
            }

            return(session);
        }
        internal static string Login(string username,
                                     byte[] passwordHash,
                                     string deviceId,
                                     IUi ui,
                                     ISecureStorage storage,
                                     RestClient rest)
        {
            // Try simple password login, potentially with a stored second factor token if
            // "remember me" was used before.
            var rememberMeOptions = GetRememberMeOptions(storage);
            var response          = RequestAuthToken(username, passwordHash, deviceId, rememberMeOptions, rest);

            // Simple password login (no 2FA) succeeded
            if (response.AuthToken != null)
            {
                return(response.AuthToken);
            }

            var secondFactor = response.SecondFactor;

            if (secondFactor.Methods == null || secondFactor.Methods.Count == 0)
            {
                throw new InternalErrorException("Expected a non empty list of available 2FA methods");
            }

            // We had a "remember me" token saved, but the login failed anyway. This token is not valid anymore.
            if (rememberMeOptions != null)
            {
                EraseRememberMeToken(storage);
            }

            var      method   = ChooseSecondFactorMethod(secondFactor, ui);
            var      extra    = secondFactor.Methods[method];
            Passcode passcode = null;

            switch (method)
            {
            case Response.SecondFactorMethod.GoogleAuth:
                passcode = ui.ProvideGoogleAuthPasscode();
                break;

            case Response.SecondFactorMethod.Email:
                // When only the email 2FA present, the email is sent by the server right away.
                // Trigger only when other methods are present.
                if (secondFactor.Methods.Count != 1)
                {
                    TriggerEmailMfaPasscode(username, passwordHash, rest);
                }

                passcode = ui.ProvideEmailPasscode((string)extra["Email"] ?? "");
                break;

            case Response.SecondFactorMethod.Duo:
            case Response.SecondFactorMethod.DuoOrg:
            {
                var duo = Duo.Authenticate((string)extra["Host"] ?? "",
                                           (string)extra["Signature"] ?? "",
                                           ui,
                                           rest.Transport);

                if (duo != null)
                {
                    passcode = new Passcode(duo.Passcode, duo.RememberMe);
                }

                break;
            }

            case Response.SecondFactorMethod.YubiKey:
                passcode = ui.ProvideYubiKeyPasscode();
                break;

            case Response.SecondFactorMethod.U2f:
                passcode = AskU2fPasscode(JObject.Parse((string)extra["Challenge"]), ui);
                break;

            default:
                throw new UnsupportedFeatureException($"2FA method {method} is not supported");
            }

            // We're done interacting with the UI
            ui.Close();

            if (passcode == null)
            {
                throw MakeCancelledMfaError();
            }

            var secondFactorResponse = RequestAuthToken(username,
                                                        passwordHash,
                                                        deviceId,
                                                        new SecondFactorOptions(method,
                                                                                passcode.Code,
                                                                                passcode.RememberMe),
                                                        rest);

            // Password + 2FA is successful
            if (secondFactorResponse.AuthToken != null)
            {
                SaveRememberMeToken(secondFactorResponse, storage);
                return(secondFactorResponse.AuthToken);
            }

            throw new BadMultiFactorException("Second factor code is not correct");
        }