Fix and improve logic around credentials and 2FA when authenticating.

Review Request #10312 — Created Nov. 8, 2018 and submitted — Latest diff uploaded


RBTools has had support for using two-factor authentication with
RBCommons, but due to changes that were made a while ago, its success
depended on how and when the server requested authentication from the
client. More than likely, it just wouldn't ask for the token.

Before, the Basic Auth handler would make an HTTP request to attempt the
credentials and look for a 2FA header, but before it did that it would
see if an HTTP request was already made with the exact same credentials,
and since this was often true at this point, it would fail.

The logic has been completely redone to make fewer assumptions about
the conditions in which it needed to prompt for tokens. The above logic
now checks in the initial Unauthorized handler for a 2FA token
requirement, avoiding the need for that extra HTTP request. Then, based
on that and on any previous authorization attempts, it may or may not
attempt authorization again.

The new logic does more than just fix 2FA in a safer way. It's also
simply smarter about reducing HTTP requests, helping prevent login rate
limits from impacting users. It also protects against empty
usernames, passwords, and 2FA tokens, so hitting Enter no longer
needlessly triggers an HTTP request (or a crash, which could also

Attempted logging in with bad credentials, valid credentials without
2FA, and valid credentials with 2FA.

For 2FA-backed accounts, tested entering valid and invalid tokens.
Verified the proper token attempt limit kicked in.

These were attempted using both an explicit rbt login and with
rbt status.

Tested using Python 2.7 and 3.7.