[#8128] support TOTP two-factor auth by appending your code to your password
Project: http://git-wip-us.apache.org/repos/asf/allura/repo Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/1b603f59 Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/1b603f59 Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/1b603f59 Branch: refs/heads/db/8128 Commit: 1b603f59db5c2ce8c5bb15aeb118d280dbc47998 Parents: a3dfd28 Author: Dave Brondsema <d...@brondsema.net> Authored: Tue Sep 20 13:53:41 2016 -0400 Committer: Dave Brondsema <d...@brondsema.net> Committed: Tue Sep 20 14:52:10 2016 -0400 ---------------------------------------------------------------------- Allura/docs/getting_started/scm_host.rst | 5 +++ scripts/ApacheAccessHandler.py | 46 +++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/allura/blob/1b603f59/Allura/docs/getting_started/scm_host.rst ---------------------------------------------------------------------- diff --git a/Allura/docs/getting_started/scm_host.rst b/Allura/docs/getting_started/scm_host.rst index 7b94f95..9141edf 100644 --- a/Allura/docs/getting_started/scm_host.rst +++ b/Allura/docs/getting_started/scm_host.rst @@ -241,6 +241,11 @@ message. and write requests and thus requires WRITE permission for every request. See ticket #7288 +.. note:: + + If two-factor auth is enabled, enter your password + current 6-digit code together, as your password. + You will have to enter your password each time, and may run into temporary permission denied when it fails. + Advanced Alternative -------------------- http://git-wip-us.apache.org/repos/asf/allura/blob/1b603f59/scripts/ApacheAccessHandler.py ---------------------------------------------------------------------- diff --git a/scripts/ApacheAccessHandler.py b/scripts/ApacheAccessHandler.py index b8fc2fe..3d01f0c 100644 --- a/scripts/ApacheAccessHandler.py +++ b/scripts/ApacheAccessHandler.py @@ -113,10 +113,16 @@ def check_repo_path(req): return repo_path is not None +class RateLimitExceeded(Exception): + pass + + def check_authentication(req): password = req.get_basic_auth_pw() # MUST be called before req.user username = req.user log(req, "checking auth for: %s" % username) + if not username or not password: + return False auth_url = req.get_options().get('ALLURA_AUTH_URL', 'https://127.0.0.1/auth/do_login') r = requests.post(auth_url, allow_redirects=False, data={ 'username': username, @@ -126,7 +132,37 @@ def check_authentication(req): }, cookies={ '_session_id': 'this-is-our-session', }) - return r.status_code == 302 and r.headers['location'].endswith('/login_successful') + if r.status_code == 302 and r.headers['location'].endswith('/login_successful'): + return True + else: + # try 2FA + password, code = password[:-6], password[-6:] + log(req, 'trying multifactor for user: %s' % username) + sess = requests.Session() + r = sess.post(auth_url, allow_redirects=False, data={ + 'username': username, + 'password': password, + 'return_to': '/login_successful', + '_session_id': 'this-is-our-session', + }, cookies={ + '_session_id': 'this-is-our-session', + }) + if r.status_code == 302 and '/auth/multifactor' in r.headers['location']: + multifactor_url = auth_url.replace('do_login', 'do_multifactor') + r = sess.post(multifactor_url, allow_redirects=False, data={ + 'mode': 'totp', + 'code': code, + 'return_to': '/login_successful', + '_session_id': 'this-is-our-session', + }, cookies={ + '_session_id': 'this-is-our-session', + }) + if r.status_code == 302 and r.headers['location'].endswith('/login_successful'): + return True + else: + if 'rate limit exceeded' in r.text: + raise RateLimitExceeded() + return False def check_permissions(req): @@ -156,9 +192,13 @@ def handler(req): req.add_common_vars() if not check_repo_path(req): + log(req, 'path not found in Allura for URL %s' % req.parsed_uri[apache.URI_PATH]) return apache.HTTP_NOT_FOUND - - authenticated = check_authentication(req) + try: + authenticated = check_authentication(req) + except RateLimitExceeded as e: + # HTTP "Too Many Requests" to give the user a bit of a hint about why it failed + return 429 if req.user and not authenticated: return apache.HTTP_UNAUTHORIZED