Example #1
0
        internal static void UpdateUi(DuoStatus status, string text, IDuoUi ui)
        {
            if (text.IsNullOrEmpty())
            {
                return;
            }

            ui.UpdateDuoStatus(status, text);
        }
Example #2
0
        // Returns the second factor token from Duo or null when canceled by the user.
        public static Result Authenticate(string host, string signature, IDuoUi ui, IRestTransport transport)
        {
            var rest = new RestClient(transport, $"https://{host}");

            var(tx, app) = ParseSignature(signature);
            var html = DownloadFrame(tx, rest);

            var(sid, devices) = ParseFrame(html);

            while (true)
            {
                // Ask the user to choose what to do
                var choice = ui.ChooseDuoFactor(devices);
                if (choice == null)
                {
                    return(null); // Canceled by user
                }
                // SMS is a special case: it doesn't submit any codes, it rather tells the server to send
                // a new batch of passcodes to the phone via SMS.
                if (choice.Factor == DuoFactor.SendPasscodesBySms)
                {
                    SubmitFactor(sid, choice, "", rest);
                    choice = new DuoChoice(choice.Device, DuoFactor.Passcode, choice.RememberMe);
                }

                // Ask for the passcode
                var passcode = "";
                if (choice.Factor == DuoFactor.Passcode)
                {
                    passcode = ui.ProvideDuoPasscode(choice.Device);
                    if (passcode.IsNullOrEmpty())
                    {
                        return(null); // Canceled by user
                    }
                }

                var token = SubmitFactorAndWaitForToken(sid, choice, passcode, ui, rest);

                // Flow error like an incorrect passcode. The UI has been updated with the error. Keep going.
                if (token.IsNullOrEmpty())
                {
                    continue;
                }

                // All good
                return(new Result($"{token}:{app}", choice.RememberMe));
            }
        }
Example #3
0
        // Returns null when a recoverable flow error (like incorrect code or time out) happened
        // TODO: Don't return null, use something more obvious
        internal static string SubmitFactorAndWaitForToken(string sid,
                                                           DuoChoice choice,
                                                           string passcode,
                                                           IDuoUi ui,
                                                           RestClient rest)
        {
            var txid = SubmitFactor(sid, choice, passcode, rest);

            var url = PollForResultUrl(sid, txid, ui, rest);

            if (url.IsNullOrEmpty())
            {
                return(null);
            }

            return(FetchToken(sid, url, ui, rest));
        }
Example #4
0
        internal static string FetchToken(string sid, string url, IDuoUi ui, RestClient rest)
        {
            var response = PostForm <R.FetchToken>(url,
                                                   new Dictionary <string, object> {
                ["sid"] = sid
            },
                                                   rest);

            UpdateUi(response, ui);

            var token = response.Cookie;

            if (token.IsNullOrEmpty())
            {
                throw MakeInvalidResponseError("Duo: authentication token expected in response but wasn't found");
            }

            return(token);
        }
Example #5
0
        // Returns null when a recoverable flow error (like incorrect code or time out) happened
        // TODO: Don't return null, use something more obvious
        internal static string PollForResultUrl(string sid, string txid, IDuoUi ui, RestClient rest)
        {
            const int maxPollAttempts = 100;

            // Normally it wouldn't poll nearly as many times. Just a few at most. It either bails on error or
            // returns the result. This number here just to prevent an infinite loop, which is never a good idea.
            for (var i = 0; i < maxPollAttempts; i += 1)
            {
                var response = PostForm <R.Poll>("frame/status",
                                                 new Dictionary <string, object> {
                    ["sid"] = sid, ["txid"] = txid
                },
                                                 rest);

                var(status, text) = GetResponseStatus(response);
                UpdateUi(status, text, ui);

                switch (status)
                {
                case DuoStatus.Success:
                    var url = response.Url;
                    if (url.IsNullOrEmpty())
                    {
                        throw MakeInvalidResponseError("Duo: result URL (result_url) was expected but wasn't found");
                    }

                    // Done
                    return(url);

                case DuoStatus.Error:
                    return(null); // TODO: Use something better than null
                }
            }

            throw MakeInvalidResponseError("Duo: expected to receive a valid result or error, got none of it");
        }
Example #6
0
 internal static void UpdateUi(R.Status response, IDuoUi ui)
 {
     var(status, text) = GetResponseStatus(response);
     UpdateUi(status, text, ui);
 }