Ejemplo n.º 1
0
def tfa_generate_recovery_codes_post_(request):
    # Extract parameters from the form
    verify_checkbox = 'verify' in request.params
    tfaresponse = request.params['tfaresponse']
    tfarecoverycodes = _get_recovery_codes_from_session()

    # Does the user want to save the new recovery codes?
    if verify_checkbox:
        if tfa.verify(request.userid, tfaresponse,
                      consume_recovery_code=False):
            if tfa.store_recovery_codes(request.userid, tfarecoverycodes):
                # Clean up the stored session variables
                _cleanup_session()
                # Successfuly stored new recovery codes.
                raise HTTPSeeOther(location="/control/2fa/status")
            else:
                # Recovery code string was corrupted or otherwise altered.
                raise WeasylError("Unexpected")
        else:
            return Response(
                define.webpage(
                    request.userid,
                    "control/2fa/generate_recovery_codes.html",
                    [tfarecoverycodes.split(','), "2fa"],
                    title="Generate Recovery Codes: Save New Recovery Codes"))
    elif not verify_checkbox:
        return Response(
            define.webpage(
                request.userid,
                "control/2fa/generate_recovery_codes.html",
                [tfarecoverycodes.split(','), "verify"],
                title="Generate Recovery Codes: Save New Recovery Codes"))
Ejemplo n.º 2
0
def tfa_generate_recovery_codes_post_(request):
    # Extract parameters from the form
    verify_checkbox = 'verify' in request.params
    tfaresponse = request.params['tfaresponse']
    tfarecoverycodes = _get_recovery_codes_from_session()

    # Does the user want to save the new recovery codes?
    if verify_checkbox:
        if tfa.verify(request.userid, tfaresponse, consume_recovery_code=False):
            if tfa.store_recovery_codes(request.userid, tfarecoverycodes):
                # Clean up the stored session variables
                _cleanup_session()
                # Successfuly stored new recovery codes.
                raise HTTPSeeOther(location="/control/2fa/status")
            else:
                # Recovery code string was corrupted or otherwise altered.
                raise WeasylError("Unexpected")
        else:
            return Response(define.webpage(request.userid, "control/2fa/generate_recovery_codes.html", [
                tfarecoverycodes.split(','),
                "2fa"
            ], title="Generate Recovery Codes: Save New Recovery Codes"))
    elif not verify_checkbox:
        return Response(define.webpage(request.userid, "control/2fa/generate_recovery_codes.html", [
            tfarecoverycodes.split(','),
            "verify"
        ], title="Generate Recovery Codes: Save New Recovery Codes"))
Ejemplo n.º 3
0
def test_verify():
    user_id = db_utils.create_user()
    tfa_secret = pyotp.random_base32()
    tfa_secret_encrypted = tfa._encrypt_totp_secret(tfa_secret)
    totp = pyotp.TOTP(tfa_secret)

    _insert_2fa_secret(user_id, tfa_secret_encrypted)
    _insert_recovery_code(user_id)

    # Codes of any other length than tfa.LENGTH_TOTP_CODE or tfa.LENGTH_RECOVERY_CODE returns False
    assert not tfa.verify(user_id, "a" * 5)
    assert not tfa.verify(user_id, "a" * 21)

    # TOTP token matches current expected value (Successful Verification)
    tfa_response = totp.now()
    assert tfa.verify(user_id, tfa_response)

    # TOTP token with space successfully verifies, as some authenticators show codes like
    #   "123 456"; verify strips all spaces. (Successful Verification)
    tfa_response = totp.now()
    # Now split the code into a space separated string (e.g., u"123 456")
    tfa_response = tfa_response[:3] + ' ' + tfa_response[3:]
    assert tfa.verify(user_id, tfa_response)

    # TOTP token does not match current expected value (Unsuccessful Verification)
    assert not tfa.verify(user_id, "000000")

    # Recovery code does not match stored value (Unsuccessful Verification)
    assert not tfa.verify(user_id, "z" * tfa.LENGTH_RECOVERY_CODE)

    # Recovery code matches a stored recovery code (Successful Verification)
    assert tfa.verify(user_id, recovery_code)

    # Recovery codes are case-insensitive (Successful Verification)
    _insert_recovery_code(user_id)
    assert tfa.verify(user_id, recovery_code.lower())

    # Recovery codes are consumed upon use (consumed previously) (Unsuccessful Verification)
    assert not tfa.verify(user_id, recovery_code)

    # When parameter `consume_recovery_code` is set to False, a recovery code is not consumed.
    _insert_recovery_code(user_id)
    assert tfa.get_number_of_recovery_codes(user_id) == 1
    assert tfa.verify(user_id,
                      'a' * tfa.LENGTH_RECOVERY_CODE,
                      consume_recovery_code=False)
    assert tfa.get_number_of_recovery_codes(user_id) == 1
Ejemplo n.º 4
0
def test_verify():
    user_id = db_utils.create_user()
    tfa_secret = pyotp.random_base32()
    tfa_secret_encrypted = tfa._encrypt_totp_secret(tfa_secret)
    totp = pyotp.TOTP(tfa_secret)

    _insert_2fa_secret(user_id, tfa_secret_encrypted)
    _insert_recovery_code(user_id)

    # Codes of any other length than tfa.LENGTH_TOTP_CODE or tfa.LENGTH_RECOVERY_CODE returns False
    assert not tfa.verify(user_id, "a" * 5)
    assert not tfa.verify(user_id, "a" * 21)

    # TOTP token matches current expected value (Successful Verification)
    tfa_response = totp.now()
    assert tfa.verify(user_id, tfa_response)

    # TOTP token with space successfully verifies, as some authenticators show codes like
    #   "123 456"; verify strips all spaces. (Successful Verification)
    tfa_response = totp.now()
    # Now split the code into a space separated string (e.g., u"123 456")
    tfa_response = tfa_response[:3] + ' ' + tfa_response[3:]
    assert tfa.verify(user_id, tfa_response)

    # TOTP token does not match current expected value (Unsuccessful Verification)
    assert not tfa.verify(user_id, "000000")

    # Recovery code does not match stored value (Unsuccessful Verification)
    assert not tfa.verify(user_id, "z" * tfa.LENGTH_RECOVERY_CODE)

    # Recovery code matches a stored recovery code (Successful Verification)
    assert tfa.verify(user_id, recovery_code)

    # Recovery codes are case-insensitive (Successful Verification)
    _insert_recovery_code(user_id)
    assert tfa.verify(user_id, recovery_code.lower())

    # Recovery codes are consumed upon use (consumed previously) (Unsuccessful Verification)
    assert not tfa.verify(user_id, recovery_code)

    # When parameter `consume_recovery_code` is set to False, a recovery code is not consumed.
    _insert_recovery_code(user_id)
    assert tfa.get_number_of_recovery_codes(user_id) == 1
    assert tfa.verify(user_id, 'a' * tfa.LENGTH_RECOVERY_CODE, consume_recovery_code=False)
    assert tfa.get_number_of_recovery_codes(user_id) == 1
Ejemplo n.º 5
0
def signin_2fa_auth_post_(request):
    sess = define.get_weasyl_session()

    # Only render page if the password has been authenticated (we have a UserID stored in the session)
    if '2fa_pwd_auth_userid' not in sess.additional_data:
        return Response(define.errorpage(request.userid, errorcode.permission))
    tfa_userid = sess.additional_data['2fa_pwd_auth_userid']

    session_life = arrow.now(
    ).timestamp - sess.additional_data['2fa_pwd_auth_timestamp']
    if session_life > 300:
        # Maximum secondary authentication time: 5 minutes
        _cleanup_2fa_session()
        return Response(
            define.errorpage(
                request.userid, errorcode.
                error_messages['TwoFactorAuthenticationAuthenticationTimeout'],
                [["Sign In", "/signin"], ["Return to the Home Page", "/"]]))
    elif two_factor_auth.verify(tfa_userid, request.params["tfaresponse"]):
        # 2FA passed, so login and cleanup.
        _cleanup_2fa_session()
        login.signin(tfa_userid)
        ref = request.params["referer"] or "/"
        # User is out of recovery codes, so force-deactivate 2FA
        if two_factor_auth.get_number_of_recovery_codes(tfa_userid) == 0:
            two_factor_auth.force_deactivate(tfa_userid)
            return Response(
                define.errorpage(
                    tfa_userid, errorcode.error_messages[
                        'TwoFactorAuthenticationZeroRecoveryCodesRemaining'],
                    [["2FA Dashboard", "/control/2fa/status"],
                     ["Return to the Home Page", "/"]]))
        # Return to the target page, restricting to the path portion of 'ref' per urlparse.
        raise HTTPSeeOther(location=urlparse.urlparse(ref).path)
    elif sess.additional_data['2fa_pwd_auth_attempts'] >= 5:
        # Hinder brute-forcing the 2FA token or recovery code by enforcing an upper-bound on 2FA auth attempts.
        _cleanup_2fa_session()
        return Response(
            define.errorpage(
                request.userid, errorcode.error_messages[
                    'TwoFactorAuthenticationAuthenticationAttemptsExceeded'],
                [["Sign In", "/signin"], ["Return to the Home Page", "/"]]))
    else:
        # Log the failed authentication attempt to the session and save
        sess.additional_data['2fa_pwd_auth_attempts'] += 1
        sess.save = True
        # 2FA failed; redirect to 2FA input page & inform user that authentication failed.
        return Response(
            define.webpage(
                request.userid,
                "etc/signin_2fa_auth.html", [
                    define.get_display_name(tfa_userid),
                    request.params["referer"],
                    two_factor_auth.get_number_of_recovery_codes(tfa_userid),
                    "2fa"
                ],
                title="Sign In - 2FA"))
Ejemplo n.º 6
0
def signin_2fa_auth_post_(request):
    sess = define.get_weasyl_session()

    # Only render page if the session exists //and// the password has
    # been authenticated (we have a UserID stored in the session)
    if not sess.additional_data or '2fa_pwd_auth_userid' not in sess.additional_data:
        return Response(define.errorpage(request.userid, errorcode.permission))
    tfa_userid = sess.additional_data['2fa_pwd_auth_userid']

    session_life = arrow.now().timestamp - sess.additional_data['2fa_pwd_auth_timestamp']
    if session_life > 300:
        # Maximum secondary authentication time: 5 minutes
        _cleanup_2fa_session()
        return Response(define.errorpage(
            request.userid,
            errorcode.error_messages['TwoFactorAuthenticationAuthenticationTimeout'],
            [["Sign In", "/signin"], ["Return to the Home Page", "/"]]
        ))
    elif two_factor_auth.verify(tfa_userid, request.params["tfaresponse"]):
        # 2FA passed, so login and cleanup.
        _cleanup_2fa_session()
        login.signin(request, tfa_userid, ip_address=request.client_addr, user_agent=request.user_agent)
        ref = request.params["referer"] or "/"
        # User is out of recovery codes, so force-deactivate 2FA
        if two_factor_auth.get_number_of_recovery_codes(tfa_userid) == 0:
            two_factor_auth.force_deactivate(tfa_userid)
            return Response(define.errorpage(
                tfa_userid,
                errorcode.error_messages['TwoFactorAuthenticationZeroRecoveryCodesRemaining'],
                [["2FA Dashboard", "/control/2fa/status"], ["Return to the Home Page", "/"]]
            ))
        # Return to the target page, restricting to the path portion of 'ref' per urlparse.
        raise HTTPSeeOther(location=urlparse.urlparse(ref).path)
    elif sess.additional_data['2fa_pwd_auth_attempts'] >= 5:
        # Hinder brute-forcing the 2FA token or recovery code by enforcing an upper-bound on 2FA auth attempts.
        _cleanup_2fa_session()
        return Response(define.errorpage(
            request.userid,
            errorcode.error_messages['TwoFactorAuthenticationAuthenticationAttemptsExceeded'],
            [["Sign In", "/signin"], ["Return to the Home Page", "/"]]
        ))
    else:
        # Log the failed authentication attempt to the session and save
        sess.additional_data['2fa_pwd_auth_attempts'] += 1
        sess.save = True
        # 2FA failed; redirect to 2FA input page & inform user that authentication failed.
        return Response(define.webpage(
            request.userid,
            "etc/signin_2fa_auth.html",
            [define.get_display_name(tfa_userid), request.params["referer"], two_factor_auth.get_number_of_recovery_codes(tfa_userid),
             "2fa"], title="Sign In - 2FA"))