Ejemplo n.º 1
0
 def totp_hook(self, secret=None):
     nonlocal totp
     if totp is None:
         totp = TOTP(secret)
     if secret:
         return totp.generate().token
     else:
         # on check, take advantage of window because previous token has been
         # "burned" so we can't generate the same, but tour is so fast
         # we're pretty certainly within the same 30s
         return totp.generate(time.time() + 30).token
Ejemplo n.º 2
0
def verify_totp_code(to_verify: str, my_secret: str, length: int, interval: int, hash_type: str) -> bool:
    """
    Verify given time-based one time password using library passlib

    :param to_verify: given time-based one time password 
    :type to_verify: str
    :param my_secret: my local stored secret
    :type my_secret: str
    :param length: time-based one time password length 
        Caution Due to a limitation of the HOTP algorithm, 
        the 10th digit can only take on values 0 .. 2, 
        and thus offers very little extra security.
        Please use maxim lenght of 9 instead 10 
        limitation see here https://passlib.readthedocs.io/en/stable/lib/passlib.totp.html#totptoken        
    :type length: int
    :param interval: totp interval in sec
    :type interval: int
    :param hash_type: hash code type to be used sha1, sha256 or sha512
    :type hash_type: str
    :return: result of totp matches
    :rtype: bool
    """
    logger.debug("verify totp with library passlib")
    totp = TOTP(key=my_secret, digits=length, period=interval, alg=hash_type)
    if totp.generate().token == str(to_verify):
        return True
    else:
        return False
Ejemplo n.º 3
0
def generate_totp_code(my_secret: str, length: int, interval: int,
                       hash_type: str) -> bool:
    """
    Generate a new time-based one time password using library passlib

    :param my_secret: my local stored secret
    :type my_secret: str
    :param length: time-based one time password length 
        Caution Due to a limitation of the HOTP algorithm, 
        the 10th digit can only take on values 0 .. 2, 
        and thus offers very little extra security.
        Please use maxim lenght of 9 instead 10 
        limitation see here:
        https://passlib.readthedocs.io/en/stable/lib/passlib.totp.html#totptoken        
    :type length: int
    :param interval: totp interval in sec
    :type interval: int
    :param hash_type: hash code type to be used sha1, sha256 or sha512
    :type hash_type: str
    :return: totp code
    :rtype: int
    """
    logger.debug("generate new totp code with library passlib")
    totp = TOTP(key=my_secret, digits=length, period=interval, alg=hash_type)
    return totp.generate().token
Ejemplo n.º 4
0
def test_two_factor_flag(app, client):
    # trying to verify code without going through two-factor
    # first login function
    wrong_code = b"000000"
    response = client.post(
        "/tf-validate", data=dict(code=wrong_code), follow_redirects=True
    )

    message = b"You currently do not have permissions to access this page"
    assert message in response.data

    # Test login using invalid email
    data = dict(email="*****@*****.**", password="******")
    response = client.post("/login", data=data, follow_redirects=True)
    assert b"Specified user does not exist" in response.data
    response = client.post(
        "/login",
        json=data,
        headers={"Content-Type": "application/json"},
        follow_redirects=True,
    )
    assert b"Specified user does not exist" in response.data

    # Test login using valid email and invalid password
    data = dict(email="*****@*****.**", password="******")
    response = client.post("/login", data=data, follow_redirects=True)
    assert b"Invalid password" in response.data
    response = client.post(
        "/login",
        json=data,
        headers={"Content-Type": "application/json"},
        follow_redirects=True,
    )
    assert b"Invalid password" in response.data

    # Test two-factor authentication first login
    data = dict(email="*****@*****.**", password="******")
    response = client.post("/login", data=data, follow_redirects=True)
    message = b"Two-factor authentication adds an extra layer of security"
    assert message in response.data
    response = client.post(
        "/tf-setup", data=dict(setup="not_a_method"), follow_redirects=True
    )
    assert b"Marked method is not valid" in response.data
    session = get_session(response)
    assert session["tf_state"] == "setup_from_login"

    # try non-existing setup on setup page (using json)
    data = dict(setup="not_a_method")
    response = client.post(
        "/tf-setup",
        json=data,
        headers={"Content-Type": "application/json"},
        follow_redirects=True,
    )
    assert response.status_code == 400
    assert (
        response.json["response"]["errors"]["setup"][0] == "Marked method is not valid"
    )

    data = dict(setup="email")
    response = client.post(
        "/tf-setup",
        json=data,
        headers={"Content-Type": "application/json"},
        follow_redirects=True,
    )

    # Test for sms in process of valid login
    sms_sender = SmsSenderFactory.createSender("test")
    data = dict(email="*****@*****.**", password="******")
    response = client.post(
        "/login",
        json=data,
        headers={"Content-Type": "application/json"},
        follow_redirects=True,
    )
    assert b'"code": 200' in response.data
    assert sms_sender.get_count() == 1
    session = get_session(response)
    assert session["tf_state"] == "ready"

    code = sms_sender.messages[0].split()[-1]
    # submit bad token to two_factor_token_validation
    response = client.post("/tf-validate", data=dict(code=wrong_code))
    assert b"Invalid Token" in response.data

    # sumbit right token and show appropriate response
    response = client.post("/tf-validate", data=dict(code=code), follow_redirects=True)
    assert b"Your token has been confirmed" in response.data

    # Upon completion, session cookie shouldnt have any two factor stuff in it.
    assert not tf_in_session(get_session(response))

    # Test change two_factor view to from sms to mail
    with app.mail.record_messages() as outbox:
        setup_data = dict(setup="email")
        response = client.post("/tf-setup", data=setup_data, follow_redirects=True)
        msg = b"To complete logging in, please enter the code sent to your mail"
        assert msg in response.data

        # Fetch token validate form
        response = client.get("/tf-validate")
        assert response.status_code == 200
        assert b'name="code"' in response.data

    code = outbox[0].body.split()[-1]
    # sumbit right token and show appropriate response
    response = client.post("/tf-validate", data=dict(code=code), follow_redirects=True)
    assert b"You successfully changed your two-factor method" in response.data

    # Test change two_factor password confirmation view to authenticator
    # Setup authenticator
    setup_data = dict(setup="authenticator")
    response = client.post("/tf-setup", data=setup_data, follow_redirects=True)
    assert b"Open an authenticator app on your device" in response.data
    # verify png QRcode is present
    assert b"data:image/svg+xml;base64," in response.data

    # parse out key
    rd = response.data.decode("utf-8")
    matcher = re.match(r".*((?:\S{4}-){7}\S{4}).*", rd, re.DOTALL)
    totp_secret = matcher.group(1)

    # Generate token from passed totp_secret and confirm setup
    totp = TOTP(totp_secret)
    code = totp.generate().token
    response = client.post("/tf-validate", data=dict(code=code), follow_redirects=True)
    assert b"You successfully changed your two-factor method" in response.data

    logout(client)

    # Test login with remember_token
    assert "remember_token" not in [c.name for c in client.cookie_jar]
    data = dict(email="*****@*****.**", password="******", remember=True)
    response = client.post(
        "/login",
        json=data,
        headers={"Content-Type": "application/json"},
        follow_redirects=True,
    )

    # Generate token from passed totp_secret
    code = totp.generate().token
    response = client.post("/tf-validate", data=dict(code=code), follow_redirects=True)
    assert b"Your token has been confirmed" in response.data

    # Verify that the remember token is properly set
    found = False
    for cookie in client.cookie_jar:
        if cookie.name == "remember_token":
            found = True
            assert cookie.path == "/"
    assert found

    response = logout(client)
    # Verify that logout clears session info
    assert not tf_in_session(get_session(response))

    # Test two-factor authentication first login
    data = dict(email="*****@*****.**", password="******")
    response = client.post("/login", data=data, follow_redirects=True)
    message = b"Two-factor authentication adds an extra layer of security"
    assert message in response.data

    # check availability of qrcode when this option is not picked
    assert b"data:image/png;base64," not in response.data

    # check availability of qrcode page when this option is picked
    setup_data = dict(setup="authenticator")
    response = client.post("/tf-setup", data=setup_data, follow_redirects=True)
    assert b"Open an authenticator app on your device" in response.data
    assert b"data:image/svg+xml;base64," in response.data

    # check appearence of setup page when sms picked and phone number entered
    sms_sender = SmsSenderFactory.createSender("test")
    data = dict(setup="sms", phone="+442083661177")
    response = client.post("/tf-setup", data=data, follow_redirects=True)
    assert b"To Which Phone Number Should We Send Code To" in response.data
    assert sms_sender.get_count() == 1
    code = sms_sender.messages[0].split()[-1]

    response = client.post("/tf-validate", data=dict(code=code), follow_redirects=True)
    assert b"Your token has been confirmed" in response.data
    assert not tf_in_session(get_session(response))

    logout(client)

    # check when two_factor_rescue function should not appear
    rescue_data_json = dict(help_setup="lost_device")
    response = client.post(
        "/tf-rescue",
        json=rescue_data_json,
        headers={"Content-Type": "application/json"},
    )
    assert b'"code": 400' in response.data

    # check when two_factor_rescue function should appear
    data = dict(email="*****@*****.**", password="******")
    response = client.post("/login", data=data, follow_redirects=True)
    assert b"Please enter your authentication code" in response.data
    rescue_data = dict(help_setup="lost_device")
    response = client.post("/tf-rescue", data=rescue_data, follow_redirects=True)
    message = b"The code for authentication was sent to your email address"
    assert message in response.data
    rescue_data = dict(help_setup="no_mail_access")
    response = client.post("/tf-rescue", data=rescue_data, follow_redirects=True)
    message = b"A mail was sent to us in order to reset your application account"
    assert message in response.data