Example #1
0
def test_store_recovery_codes():
    user_id = db_utils.create_user()
    valid_code_string = "01234567890123456789,02234567890123456789,03234567890123456789,04234567890123456789,05234567890123456789,06234567890123456789,07234567890123456789,08234567890123456789,09234567890123456789,10234567890123456789"

    _insert_recovery_code(user_id)

    # store_recovery_codes() will not accept a string of codes where the total code count is not 10
    invalid_codes = valid_code_string.split(',').pop()
    assert not tfa.store_recovery_codes(user_id, ','.join(invalid_codes))

    # store_recovery_codes() will not accept a string of codes when the code length is not tfa.LENGTH_RECOVERY_CODE
    invalid_codes = "01,02,03,04,05,06,07,08,09,10"
    assert not tfa.store_recovery_codes(user_id, invalid_codes)

    # When a correct code list is provided, the codes will be stored successfully in the database
    assert tfa.store_recovery_codes(user_id, valid_code_string)

    # Extract the current hashed recovery codes
    query = d.engine.execute("""
        SELECT recovery_code_hash
        FROM twofa_recovery_codes
        WHERE userid = %(userid)s
    """, userid=user_id).fetchall()

    # Ensure that the recovery codes can be hashed to the corresponding bcrypt hash
    valid_code_list = valid_code_string.split(',')
    for row in query:
        code_status = False
        for code in valid_code_list:
            if bcrypt.checkpw(code.encode('utf-8'), row['recovery_code_hash'].encode('utf-8')):
                # If the code matches the hash, then the recovery code stored successfully
                code_status = True
                break
        # The code must be valid
        assert code_status
Example #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"))
Example #3
0
def tfa_init_verify_post_(request):
    # Extract parameters from the form
    verify_checkbox = 'verify' in request.params
    tfasecret = _get_totp_code_from_session()
    tfarecoverycodes = _get_recovery_codes_from_session()

    # Does the user want to proceed with enabling 2FA?
    if verify_checkbox and tfa.store_recovery_codes(request.userid,
                                                    tfarecoverycodes):
        # Strip any spaces from the TOTP code (some authenticators display the digits like '123 456')
        tfaresponse = request.params['tfaresponse'].replace(' ', '')

        # TOTP+2FA Secret validates (activate & redirect to status page)
        if tfa.activate(request.userid, tfasecret, tfaresponse):
            # Invalidate all other login sessions
            invalidate_other_sessions(request.userid)
            # Clean up the stored session variables
            _cleanup_session()
            raise HTTPSeeOther(location="/control/2fa/status")
        # TOTP+2FA Secret did not validate
        else:
            return Response(
                define.webpage(request.userid,
                               "control/2fa/init_verify.html",
                               [tfarecoverycodes.split(','), "2fa"],
                               title="Enable 2FA: Final Step"))
    # The user didn't check the verification checkbox (despite HTML5's client-side check); regenerate codes & redisplay
    elif not verify_checkbox:
        return Response(
            define.webpage(request.userid,
                           "control/2fa/init_verify.html",
                           [tfarecoverycodes.split(','), "verify"],
                           title="Enable 2FA: Final Step"))
Example #4
0
def test_2fa_changes_token(app):
    user = db_utils.create_user(username='******', password='******')

    resp = app.get('/')
    csrf = resp.html.find('html')['data-csrf-token']

    assert tfa.store_recovery_codes(user,
                                    ','.join(tfa.generate_recovery_codes()))
    tfa_secret = pyotp.random_base32()
    totp = pyotp.TOTP(tfa_secret)
    tfa_response = totp.now()
    assert tfa.activate(user, tfa_secret, tfa_response)

    old_cookie = app.cookies['WZL']
    resp = app.post('/signin', {
        'token': csrf,
        'username': '******',
        'password': '******'
    })
    new_csrf = resp.html.find('html')['data-csrf-token']
    assert app.cookies['WZL'] != old_cookie
    assert new_csrf != csrf
    assert not d.engine.scalar(
        "SELECT EXISTS (SELECT 0 FROM sessions WHERE userid = %(user)s)",
        user=user)
    assert d.engine.scalar(
        "SELECT EXISTS (SELECT 0 FROM sessions WHERE additional_data->'2fa_pwd_auth_userid' = %(user)s::text)",
        user=user)
Example #5
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"))
Example #6
0
def tfa_init_verify_post_(request):
    # Extract parameters from the form
    verify_checkbox = 'verify' in request.params
    tfasecret = _get_totp_code_from_session()
    tfaresponse = request.params['tfaresponse']
    tfarecoverycodes = _get_recovery_codes_from_session()

    # Does the user want to proceed with enabling 2FA?
    if verify_checkbox and tfa.store_recovery_codes(request.userid, tfarecoverycodes):
        # Strip any spaces from the TOTP code (some authenticators display the digits like '123 456')
        tfaresponse = request.params['tfaresponse'].replace(' ', '')

        # TOTP+2FA Secret validates (activate & redirect to status page)
        if tfa.activate(request.userid, tfasecret, tfaresponse):
            # Invalidate all other login sessions
            invalidate_other_sessions(request.userid)
            # Clean up the stored session variables
            _cleanup_session()
            raise HTTPSeeOther(location="/control/2fa/status")
        # TOTP+2FA Secret did not validate
        else:
            return Response(define.webpage(request.userid, "control/2fa/init_verify.html", [
                tfarecoverycodes.split(','),
                "2fa"
            ], title="Enable 2FA: Final Step"))
    # The user didn't check the verification checkbox (despite HTML5's client-side check); regenerate codes & redisplay
    elif not verify_checkbox:
        return Response(define.webpage(request.userid, "control/2fa/init_verify.html", [
            tfarecoverycodes.split(','),
            "verify"
        ], title="Enable 2FA: Final Step"))
Example #7
0
def test_store_recovery_codes():
    user_id = db_utils.create_user()
    valid_code_string = "01234567890123456789,02234567890123456789,03234567890123456789,04234567890123456789,05234567890123456789,06234567890123456789,07234567890123456789,08234567890123456789,09234567890123456789,10234567890123456789"

    _insert_recovery_code(user_id)

    # store_recovery_codes() will not accept a string of codes where the total code count is not 10
    invalid_codes = valid_code_string.split(',').pop()
    assert not tfa.store_recovery_codes(user_id, ','.join(invalid_codes))

    # store_recovery_codes() will not accept a string of codes when the code length is not tfa.LENGTH_RECOVERY_CODE
    invalid_codes = "01,02,03,04,05,06,07,08,09,10"
    assert not tfa.store_recovery_codes(user_id, invalid_codes)

    # When a correct code list is provided, the codes will be stored successfully in the database
    assert tfa.store_recovery_codes(user_id, valid_code_string)

    # Extract the current hashed recovery codes
    query = d.engine.execute("""
        SELECT recovery_code_hash
        FROM twofa_recovery_codes
        WHERE userid = %(userid)s
    """,
                             userid=user_id).fetchall()

    # Ensure that the recovery codes can be hashed to the corresponding bcrypt hash
    valid_code_list = valid_code_string.split(',')
    for row in query:
        code_status = False
        for code in valid_code_list:
            if bcrypt.checkpw(code.encode('utf-8'),
                              row['recovery_code_hash'].encode('utf-8')):
                # If the code matches the hash, then the recovery code stored successfully
                code_status = True
                break
        # The code must be valid
        assert code_status
Example #8
0
def test_2fa_changes_token(app):
    user = db_utils.create_user(username='******', password='******')

    resp = app.get('/')
    csrf = resp.html.find('html')['data-csrf-token']

    assert tfa.store_recovery_codes(user, ','.join(tfa.generate_recovery_codes()))
    tfa_secret = pyotp.random_base32()
    totp = pyotp.TOTP(tfa_secret)
    tfa_response = totp.now()
    assert tfa.activate(user, tfa_secret, tfa_response)

    old_cookie = app.cookies['WZL']
    resp = app.post('/signin', {'token': csrf, 'username': '******', 'password': '******'})
    new_csrf = resp.html.find('html')['data-csrf-token']
    assert app.cookies['WZL'] != old_cookie
    assert new_csrf != csrf
    assert not d.engine.scalar("SELECT EXISTS (SELECT 0 FROM sessions WHERE userid = %(user)s)", user=user)
    assert d.engine.scalar("SELECT EXISTS (SELECT 0 FROM sessions WHERE additional_data->'2fa_pwd_auth_userid' = %(user)s::text)", user=user)