[#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
 

Reply via email to