You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2016/09/06 14:50:36 UTC

[03/10] allura git commit: [#8117] Reconfirm auth on security pages

[#8117] Reconfirm auth on security pages


Project: http://git-wip-us.apache.org/repos/asf/allura/repo
Commit: http://git-wip-us.apache.org/repos/asf/allura/commit/bced8935
Tree: http://git-wip-us.apache.org/repos/asf/allura/tree/bced8935
Diff: http://git-wip-us.apache.org/repos/asf/allura/diff/bced8935

Branch: refs/heads/master
Commit: bced893549b00775774c23fc928bfb71c31f9f22
Parents: 208b99b
Author: Dave Brondsema <da...@brondsema.net>
Authored: Thu Aug 25 13:15:38 2016 -0400
Committer: Dave Brondsema <da...@brondsema.net>
Committed: Tue Sep 6 10:38:51 2016 -0400

----------------------------------------------------------------------
 Allura/allura/controllers/auth.py           | 28 ++++++---------
 Allura/allura/lib/decorators.py             | 36 ++++++++++++++++++-
 Allura/allura/templates/reconfirm_auth.html | 44 ++++++++++++++++++++++++
 Allura/allura/templates/user_prefs.html     | 20 +++++++++--
 Allura/development.ini                      |  3 ++
 5 files changed, 109 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/allura/blob/bced8935/Allura/allura/controllers/auth.py
----------------------------------------------------------------------
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index d326397..93a0875 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -17,7 +17,7 @@
 
 import logging
 import os
-import datetime
+from datetime import datetime, timedelta
 import re
 from urlparse import urlparse, urljoin
 
@@ -37,7 +37,7 @@ from allura.lib import validators as V
 from allura.lib.security import require_authenticated, has_access
 from allura.lib import helpers as h
 from allura.lib import plugin
-from allura.lib.decorators import require_post
+from allura.lib.decorators import require_post, reconfirm_auth
 from allura.lib.repository import RepositoryApp
 from allura.lib.widgets import (
     SubscriptionForm,
@@ -141,7 +141,7 @@ class AuthController(BaseController):
             redirect(login_url)
         hash_expiry = user_record.get_tool_data(
             'AuthPasswordReset', 'hash_expiry')
-        if not hash_expiry or hash_expiry < datetime.datetime.utcnow():
+        if not hash_expiry or hash_expiry < datetime.utcnow():
             log.info('Reset hash expired: {} {}'.format(hash, hash_expiry))
             flash('Unable to process reset, please try again')
             redirect(login_url)
@@ -203,8 +203,8 @@ class AuthController(BaseController):
             hash = h.nonce(42)
             user_record.set_tool_data('AuthPasswordReset',
                                       hash=hash,
-                                      hash_expiry=datetime.datetime.utcnow() +
-                                      datetime.timedelta(seconds=int(config.get('auth.recovery_hash_expiry_period', 600))))
+                                      hash_expiry=datetime.utcnow() +
+                                      timedelta(seconds=int(config.get('auth.recovery_hash_expiry_period', 600))))
 
             log.info('Sending password recovery link to %s', email_record.email)
             subject = '%s Password recovery' % config['site_name']
@@ -646,16 +646,8 @@ class PreferencesController(BaseController):
 
     @expose('jinja:allura:templates/user_totp.html')
     @without_trailing_slash
-    #@reconfirm_password
+    @reconfirm_auth
     def totp_new(self, **kw):
-        '''
-        auth_provider = plugin.AuthenticationProvider.get(request)
-        # TODO: don't require it every single time
-        if not kw.get('password') or not auth_provider.validate_password(c.user, kw.get('password')):
-            flash('You must provide your current password to set up multifactor authentication', 'error')
-            redirect('.')
-        '''
-
         totp_service = TotpService.get()
         if 'totp_new_key' not in session:
             # never been here yet
@@ -678,7 +670,7 @@ class PreferencesController(BaseController):
 
     @expose('jinja:allura:templates/user_totp.html')
     @without_trailing_slash
-    #@reconfirm_password
+    @reconfirm_auth
     def totp_view(self, **kw):
         totp_service = TotpService.get()
         totp = totp_service.get_totp(c.user)
@@ -690,8 +682,8 @@ class PreferencesController(BaseController):
         )
 
     @expose('jinja:allura:templates/user_totp.html')
+    @reconfirm_auth
     @require_post()
-    # @reconfirm_password
     def totp_set(self, code, **kw):
         # TODO: email notification
         key = session['totp_new_key']
@@ -714,8 +706,8 @@ class PreferencesController(BaseController):
 
     @expose()
     @require_post()
-    # @reconfirm_password
-    def multifactor_disable(self):
+    @reconfirm_auth
+    def multifactor_disable(self, **kw):
         # TODO: email notification
         h.auditlog_user('Disabled multifactor TOTP')
         totp_service = TotpService.get()

http://git-wip-us.apache.org/repos/asf/allura/blob/bced8935/Allura/allura/lib/decorators.py
----------------------------------------------------------------------
diff --git a/Allura/allura/lib/decorators.py b/Allura/allura/lib/decorators.py
index cb5cfc5..dc4e1c3 100644
--- a/Allura/allura/lib/decorators.py
+++ b/Allura/allura/lib/decorators.py
@@ -22,9 +22,14 @@ import logging
 from collections import defaultdict
 from urllib import unquote
 
+from datetime import datetime
+
+from datetime import timedelta
 from decorator import decorator
+from paste.deploy.converters import asint
 from tg.decorators import before_validate
-from tg import request, redirect
+from tg import request, redirect, session, config
+from tg.render import render
 from webob import exc
 from pylons import tmpl_context as c
 
@@ -32,6 +37,9 @@ from allura.lib import helpers as h
 from allura.lib import utils
 
 
+log = logging.getLogger(__name__)
+
+
 def task(*args, **kw):
     """Decorator that adds a ``.post()`` function to the decorated callable.
 
@@ -84,6 +92,10 @@ class event_handler(object):
 
 
 class require_post(object):
+    '''
+    A decorator to require controllers by accessed with a POST only.  Use whenever data will be modified by a
+    controller, since that's what POST is good for.  We have CSRF protection middleware on POSTs, too.
+    '''
 
     def __init__(self, redir=None):
         self.redir = redir
@@ -98,6 +110,28 @@ class require_post(object):
         return func
 
 
+@decorator
+def reconfirm_auth(func, *args, **kwargs):
+    '''
+    A decorator to require the user to reconfirm their login.  Useful for sensitive pages.
+    '''
+    from allura.lib.plugin import AuthenticationProvider
+
+    if request.POST.get('password'):
+        if AuthenticationProvider.get(request).validate_password(c.user, request.POST['password']):
+            session['auth-reconfirmed'] = datetime.utcnow()
+            session.save()
+        else:
+            c.form_errors['password'] = 'Invalid password.'
+
+    allowed_timedelta = timedelta(seconds=asint(config.get('auth.reconfirm.seconds', 60)))
+    last_reconfirm = session.get('auth-reconfirmed', datetime.min)
+    if datetime.utcnow() - last_reconfirm <= allowed_timedelta:
+        return func(*args, **kwargs)
+    else:
+        return render({}, 'jinja', "allura:templates/reconfirm_auth.html")
+
+
 class log_action(object):  # pragma no cover
 
     def __init__(self,

http://git-wip-us.apache.org/repos/asf/allura/blob/bced8935/Allura/allura/templates/reconfirm_auth.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/reconfirm_auth.html b/Allura/allura/templates/reconfirm_auth.html
new file mode 100644
index 0000000..f44be2d
--- /dev/null
+++ b/Allura/allura/templates/reconfirm_auth.html
@@ -0,0 +1,44 @@
+{#-
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+-#}
+{% set hide_left_bar = True %}
+{% extends g.theme.master %}
+
+{% block title %}{{ config['site_name'] }} - Reconfirm Password{% endblock %}
+
+{% block header %}Reconfirm Your Password{% endblock %}
+
+{% block content %}
+<form method="post">
+    <h2>Password Confirmation</h2>
+    <p>To access this account security page, you must reconfirm your password:</p>
+    <div class="fielderror">{{ c.form_errors['password'] }}</div>
+    <input type="password" name="password" autofocus>
+    <br>
+    <input type="submit" value="Submit">
+
+    {# include any post params again, so that their original form submit can go through successfully #}
+    {% for key, val in request.POST.iteritems() %}
+        {% if key != 'password' %}
+            <input type="hidden" name="{{ key }}" value="{{ val }}">
+        {% endif %}
+    {% endfor %}
+
+    {{ lib.csrf_token() }}
+</form>
+{% endblock %}

http://git-wip-us.apache.org/repos/asf/allura/blob/bced8935/Allura/allura/templates/user_prefs.html
----------------------------------------------------------------------
diff --git a/Allura/allura/templates/user_prefs.html b/Allura/allura/templates/user_prefs.html
index f2cd5fc..66450e5 100644
--- a/Allura/allura/templates/user_prefs.html
+++ b/Allura/allura/templates/user_prefs.html
@@ -123,9 +123,12 @@
             </a></p>
             {% if user_multifactor %}
                 <p><b class="fa fa-qrcode"></b> <a href="totp_view">View existing configuration</a></p>
-
-                {# FIXME, need confirmation #}
-                <p><b class="fa fa-trash"></b> <a href="multifactor_disable" class="post-link">Disable</a></p>
+                <form action="multifactor_disable" method="post">
+                <p>
+                    <b class="fa fa-trash"></b> <a href="#" class="disable">Disable</a>
+                </p>
+                {{ lib.csrf_token() }}
+                </form>
             {% endif %}
         </fieldset>
       {% endif %}
@@ -178,6 +181,17 @@
 {% endblock %}
 
 {% block extra_js %}
+  <script type="text/javascript">
+      $(function() {
+          $('.multifactor .disable').click(function(e){
+              var ok = confirm('Are you sure you want to disable multifactor authentication?');
+              if(ok) {
+                  $(this).closest('form').submit();
+              }
+              e.preventDefault();
+          });
+      });
+  </script>
   {% if h.asbool(tg.config.get('auth.allow_edit_prefs', True)) %}
   {# js to ask for a current password on the email form #}
   <script type="text/javascript">

http://git-wip-us.apache.org/repos/asf/allura/blob/bced8935/Allura/development.ini
----------------------------------------------------------------------
diff --git a/Allura/development.ini b/Allura/development.ini
index 24ab3be..a72f9fd 100644
--- a/Allura/development.ini
+++ b/Allura/development.ini
@@ -190,6 +190,9 @@ auth.upload_ssh_url = /auth/preferences/
 ; In seconds
 auth.recovery_hash_expiry_period = 600
 
+; Some pages require users to reconfirm their password.  This controls how long that lasts for
+auth.reconfirm.seconds = 60
+
 ; TOTP stands for Time-based One Time Password
 ; it is the most common two-factor auth protocol, used with Google Authenticator and other phone apps
 auth.multifactor.totp = true