fixed conflict between GAE and 2-factor authentication

This commit is contained in:
mdipierro
2014-07-28 22:01:48 -05:00
parent 29303488ab
commit 024912bda1
+27 -29
View File
@@ -992,6 +992,7 @@ class Auth(object):
everybody_group_id=None, everybody_group_id=None,
manager_actions={}, manager_actions={},
auth_manager_role=None, auth_manager_role=None,
two_factor_authentication_group = None,
login_captcha=None, login_captcha=None,
register_captcha=None, register_captcha=None,
pre_registration_div=None, pre_registration_div=None,
@@ -2283,16 +2284,16 @@ class Auth(object):
_code='INVALID TICKET')) _code='INVALID TICKET'))
raise HTTP(200, message) raise HTTP(200, message)
def _reset_2_factor_auth(self, session): def _reset_two_factor_auth(self, session):
'''When two-step authentication is enabled, this function is used to '''When two-step authentication is enabled, this function is used to
clear the session after successfully completing second challenge clear the session after successfully completing second challenge
or when the maximum number of tries allowed has expired. or when the maximum number of tries allowed has expired.
''' '''
session.auth_2_factor_user = None session.auth_two_factor_user = None
session.auth_2_factor = None session.auth_two_factor = None
session.auth_2_factor_enabled = False session.auth_two_factor_enabled = False
# Allow up to 4 attempts (the 1st one plus 3 more) # Allow up to 4 attempts (the 1st one plus 3 more)
session.auth_2_factor_tries_left = 3 session.auth_two_factor_tries_left = 3
def login( def login(
self, self,
@@ -2381,12 +2382,12 @@ class Auth(object):
# If two-factor authentication is enabled, and the maximum # If two-factor authentication is enabled, and the maximum
# number of tries allowed is used up, reset the session to # number of tries allowed is used up, reset the session to
# pre-login state with two-factor auth # pre-login state with two-factor auth
if session.auth_2_factor_enabled and session.auth_2_factor_tries_left < 1: if session.auth_two_factor_enabled and session.auth_two_factor_tries_left < 1:
# Exceeded maximum allowed tries for this code. Require user to enter # Exceeded maximum allowed tries for this code. Require user to enter
# username and password again. # username and password again.
user = None user = None
accepted_form = False accepted_form = False
self._reset_2_factor_auth(session) self._reset_two_factor_auth(session)
# Redirect to the default 'next' page without logging # Redirect to the default 'next' page without logging
# in. If that page requires login, user will be redirected # in. If that page requires login, user will be redirected
# back to the main login form # back to the main login form
@@ -2399,7 +2400,7 @@ class Auth(object):
# Note to devs: The code inside the if-block is unchanged from the # Note to devs: The code inside the if-block is unchanged from the
# previous version of this file, other than for indentation inside # previous version of this file, other than for indentation inside
# to put it inside the if-block # to put it inside the if-block
if session.auth_2_factor_user is None: if session.auth_two_factor_user is None:
# do we use our own login form, or from a central source? # do we use our own login form, or from a central source?
if settings.login_form == self: if settings.login_form == self:
form = SQLFORM( form = SQLFORM(
@@ -2545,15 +2546,12 @@ class Auth(object):
# username and password at the first challenge). # username and password at the first challenge).
# Check if this user is signed up for two-factor authentication # Check if this user is signed up for two-factor authentication
# Default rule is that the user must be part of a group that is called # Default rule is that the user must be part of a group that is called
# 'web2py Two-Step Authentication' # auth.settings.two_factor_authentication_group
if user: if user and self.settings.two_factor_authentication_group:
memberships = self.db((self.table_membership().user_id == user.id) role = self.settings.two_factor_authentication_group
&(self.table_group().id == self.table_membership().group_id)).select( session.auth_two_factor_enabled = self.has_membership(user_id=user.id,role=role)
self.table_group().role)
session.auth_2_factor_enabled = 'web2py Two-Step Authentication' in [i.role for i in memberships]
# If user is signed up for two-factor authentication, present the second
# challenge # challenge
if session.auth_2_factor_enabled: if session.auth_two_factor_enabled:
form = SQLFORM.factory( form = SQLFORM.factory(
Field('authentication_code', Field('authentication_code',
required=True, required=True,
@@ -2569,43 +2567,43 @@ class Auth(object):
# Handle the case when a user has submitted the login/password # Handle the case when a user has submitted the login/password
# form successfully, and the password has been validated, but # form successfully, and the password has been validated, but
# the two-factor form has not been displayed or validated yet. # the two-factor form has not been displayed or validated yet.
if session.auth_2_factor_user is None and user is not None: if session.auth_two_factor_user is None and user is not None:
session.auth_2_factor_user = user # store the validated user and associate with this session session.auth_two_factor_user = user # store the validated user and associate with this session
session.auth_2_factor = random.randint(100000, 999999) session.auth_two_factor = random.randint(100000, 999999)
session.auth_2_factor_tries_left = 3 # Allow user to try up to 4 times session.auth_two_factor_tries_left = 3 # Allow user to try up to 4 times
# TODO: Add some error checking to handle cases where email cannot be sent # TODO: Add some error checking to handle cases where email cannot be sent
self.settings.mailer.send( self.settings.mailer.send(
to=user.email, to=user.email,
subject="Two-step Login Authentication Code", subject="Two-step Login Authentication Code",
message="Your temporary login code is {0}".format(session.auth_2_factor)) message="Your temporary login code is {0}".format(session.auth_two_factor))
if form.accepts(request, session if self.csrf_prevention else None, if form.accepts(request, session if self.csrf_prevention else None,
formname='login', dbio=False, formname='login', dbio=False,
onvalidation=onvalidation, onvalidation=onvalidation,
hideerror=settings.hideerror): hideerror=settings.hideerror):
accepted_form = True accepted_form = True
if form.vars['authentication_code'] == str(session.auth_2_factor): if form.vars['authentication_code'] == str(session.auth_two_factor):
# Handle the case when the two-factor form has been successfully validated # Handle the case when the two-factor form has been successfully validated
# and the user was previously stored (the current user should be None because # and the user was previously stored (the current user should be None because
# in this case, the previous username/password login form should not be displayed. # in this case, the previous username/password login form should not be displayed.
# This will allow the code after the 2-factor authentication block to proceed as # This will allow the code after the 2-factor authentication block to proceed as
# normal. # normal.
if user is None or user == session.auth_2_factor_user: if user is None or user == session.auth_two_factor_user:
user = session.auth_2_factor_user user = session.auth_two_factor_user
# For security, because the username stored in the # For security, because the username stored in the
# session somehow does not match the just validated # session somehow does not match the just validated
# user. Should not be possible without session stealing # user. Should not be possible without session stealing
# which is hard with SSL. # which is hard with SSL.
elif user != session.auth_2_factor_user: elif user != session.auth_two_factor_user:
user = None user = None
# Either way, the user and code associated with this session should # Either way, the user and code associated with this session should
# be removed. This handles cases where the session login may have # be removed. This handles cases where the session login may have
# expired but browser window is open, so the old session key and # expired but browser window is open, so the old session key and
# session usernamem will still exist # session usernamem will still exist
self._reset_2_factor_auth(session) self._reset_two_factor_auth(session)
else: else:
# TODO: Limit the number of retries allowed. # TODO: Limit the number of retries allowed.
response.flash = 'Incorrect code. {0} more attempt(s) remaining.'.format(session.auth_2_factor_tries_left) response.flash = 'Incorrect code. {0} more attempt(s) remaining.'.format(session.auth_two_factor_tries_left)
session.auth_2_factor_tries_left -= 1 session.auth_two_factor_tries_left -= 1
return form return form
else: else:
return form return form
@@ -2650,7 +2648,7 @@ class Auth(object):
# Clear out 2-step authentication information if user logs # Clear out 2-step authentication information if user logs
# out. This information is also cleared on successful login. # out. This information is also cleared on successful login.
self._reset_2_factor_auth(current.session) self._reset_two_factor_auth(current.session)
if next is DEFAULT: if next is DEFAULT:
next = self.get_vars_next() or self.settings.logout_next next = self.get_vars_next() or self.settings.logout_next