示例#1
0
def test_default_unauthz(app, client):
    """ Test default unauthz handler with and without json """
    authenticate(client, "*****@*****.**", "password")

    response = client.get("/admin")
    # This is the result of abort(403) since there is no UNAUTHORIZED_VIEW
    assert response.status_code == 403

    response = client.get("/admin", headers={"Accept": "application/json"})
    assert response.status_code == 403
    assert response.json["meta"]["code"] == 403
示例#2
0
def test_default_unauthz_myjson(app, client):
    """ Make sure render_json gets called for unauthn errors """
    @app.security.render_json
    def my_json(payload, code, headers=None, user=None):
        return jsonify(dict(myresponse=payload, code=code)), code, headers

    authenticate(client, "*****@*****.**", "password")

    response = client.get("/admin", headers={"Accept": "application/json"})
    assert response.status_code == 403
    assert response.json["code"] == 403
示例#3
0
def test_authn_freshness_callable(app, client, get_message):
    @auth_required(within=lambda: timedelta(minutes=30))
    def myview():
        return Response(status=200)

    app.add_url_rule("/myview", view_func=myview, methods=["GET"])
    authenticate(client)

    # This should work and not be redirected
    response = client.get("/myview", follow_redirects=False)
    assert response.status_code == 200
示例#4
0
def test_roles_accepted(clients):
    # This specificaly tests that we can pass a URL for unauthorized_view.
    for user in ("*****@*****.**", "*****@*****.**"):
        authenticate(clients, user)
        response = clients.get("/admin_or_editor")
        assert b"Admin or Editor Page" in response.data
        logout(clients)

    authenticate(clients, "*****@*****.**")
    response = clients.get("/admin_or_editor", follow_redirects=True)
    assert b"Unauthorized" in response.data
示例#5
0
def test_authr_identity(app, client):
    # Setup authenticator
    headers = {"Accept": "application/json", "Content-Type": "application/json"}
    authenticate(client, email="*****@*****.**")

    setup_data = dict(setup="authenticator")
    response = client.post("/tf-setup", json=setup_data, headers=headers)
    assert response.json["response"]["tf_authr_issuer"] == "service_name"
    assert response.json["response"]["tf_authr_username"] == "jill"
    assert response.json["response"]["tf_state"] == "validating_profile"
    assert "tf_authr_key" in response.json["response"]
示例#6
0
def test_xlation(app, client):
    app.config["BABEL_DEFAULT_LOCALE"] = "fr_FR"
    assert check_xlation(
        app, "fr_FR"), "You must run python setup.py compile_catalog"

    response = client.get("/login")
    assert b'<label for="password">Mot de passe</label>' in response.data
    response = authenticate(client)
    assert response.status_code == 302
    response = authenticate(client, follow_redirects=True)
    assert b"Bienvenue [email protected]" in response.data
示例#7
0
def test_email_salutation(app, client):
    authenticate(client, email="*****@*****.**")
    with app.mail.record_messages() as outbox:
        response = client.post(
            "/tf-setup", data=dict(setup="email"), follow_redirects=True
        )
        msg = b"To complete logging in, please enter the code sent to your mail"
        assert msg in response.data

    assert "*****@*****.**" in outbox[0].send_to
    assert "*****@*****.**" in outbox[0].body
    assert "*****@*****.**" in outbox[0].html
示例#8
0
def test_override_length(app, client, get_message):
    authenticate(client)
    response = client.post(
        "/change",
        data={
            "password": "******",
            "new_password": "******",
            "new_password_confirm": "01234567890",
        },
        follow_redirects=True,
    )
    assert get_message("PASSWORD_INVALID_LENGTH", length=12) in response.data
示例#9
0
def test_authenticated_loop(client):
    # If user is already authenticated say via session, and then hits an endpoint
    # protected with @auth_token_required() - then they will be redirected to the login
    # page which will simply note the current user is already logged in and redirect
    # to POST_LOGIN_VIEW. Between 3.3.0 and 3.4.4 - this redirect would honor the 'next'
    # parameter - thus redirecting back to the endpoint that caused the redirect in the
    # first place - thus an infinite loop.
    authenticate(client)

    response = client.get("/token", follow_redirects=True)
    assert response.status_code == 200
    assert b"Home Page" in response.data
示例#10
0
def test_pwd_no_normalize(app, client):
    """Verify that can log in with original but not normalized if have
    disabled normalization
    """
    authenticate(client)

    data = dict(
        password="******",
        new_password="******",
        new_password_confirm="new strong password\N{ROMAN NUMERAL ONE}",
    )
    response = client.post(
        "/change",
        json=data,
        headers={"Content-Type": "application/json"},
    )
    assert response.status_code == 200
    logout(client)

    # try with normalized password - should fail
    response = client.post(
        "/login",
        json=dict(
            email="*****@*****.**",
            password="******",
        ),
        headers={"Content-Type": "application/json"},
    )
    assert response.status_code == 400

    # use original typed-in pwd
    response = client.post(
        "/login",
        json=dict(
            email="*****@*****.**", password="******"
        ),
        headers={"Content-Type": "application/json"},
    )
    assert response.status_code == 200

    # Verify can change password using original password
    data = dict(
        password="******",
        new_password="******",
        new_password_confirm="new strong password\N{ROMAN NUMERAL TWO}",
    )
    response = client.post(
        "/change",
        json=data,
        headers={"Content-Type": "application/json"},
    )
    assert response.status_code == 200
示例#11
0
def test_basic_custom_forms(app, sqlalchemy_datastore):
    class MyLoginForm(LoginForm):
        email = StringField("My Login Email Address Field")

    class MyRegisterForm(RegisterForm):
        email = StringField("My Register Email Address Field")

    class MyForgotPasswordForm(ForgotPasswordForm):
        email = StringField(
            "My Forgot Email Address Field",
            validators=[email_required, email_validator, valid_user_email],
        )

    class MyResetPasswordForm(ResetPasswordForm):
        password = StringField("My Reset Password Field")

    class MyChangePasswordForm(ChangePasswordForm):
        password = PasswordField("My Change Password Field")

    app.security = Security(
        app,
        datastore=sqlalchemy_datastore,
        login_form=MyLoginForm,
        register_form=MyRegisterForm,
        forgot_password_form=MyForgotPasswordForm,
        reset_password_form=MyResetPasswordForm,
        change_password_form=MyChangePasswordForm,
    )

    populate_data(app)
    client = app.test_client()

    response = client.get("/login")
    assert b"My Login Email Address Field" in response.data

    response = client.get("/register")
    assert b"My Register Email Address Field" in response.data

    response = client.get("/reset")
    assert b"My Forgot Email Address Field" in response.data

    with capture_reset_password_requests() as requests:
        response = client.post("/reset", data=dict(email="*****@*****.**"))

    token = requests[0]["token"]
    response = client.get("/reset/" + token)
    assert b"My Reset Password Field" in response.data

    authenticate(client)

    response = client.get("/change")
    assert b"My Change Password Field" in response.data
示例#12
0
def test_custom_post_change_view(client):
    authenticate(client)
    response = client.post(
        "/change",
        data={
            "password": "******",
            "new_password": "******",
            "new_password_confirm": "new strong password",
        },
        follow_redirects=True,
    )

    assert b"Profile Page" in response.data
示例#13
0
def test_authn_via(app, client, get_message):
    """ Test that we get correct fs_authn_via set in request """
    @auth_required(within=30, grace=0)
    def myview():
        assert get_request_attr("fs_authn_via") == "session"
        return Response(status=200)

    app.add_url_rule("/myview", view_func=myview, methods=["GET"])
    authenticate(client)

    # This should work and not be redirected
    response = client.get("/myview", follow_redirects=False)
    assert response.status_code == 200
示例#14
0
def test_just_authenticator(app, client):
    authenticate(client, email="*****@*****.**")

    response = client.get("/tf-setup", follow_redirects=True)
    assert b"Set up using SMS" not in response.data

    data = dict(setup="authenticator")
    response = client.post("/tf-setup", data=data, follow_redirects=True)
    assert b"Submit Code" in response.data

    # test json
    response = client.post("/tf-setup", json=data)
    assert response.status_code == 200
示例#15
0
def test_my_unauthz_handler_exc(app, client):
    """ Verify that can use exceptions in unauthz handler """
    @app.security.unauthz_handler
    def my_unauthz(func, params):
        raise ValueError("Bad Value")

    @app.errorhandler(ValueError)
    def error_handler(ex):
        return jsonify(dict(code=403)), 403

    authenticate(client, "*****@*****.**", "password")

    response = client.get("/admin", headers={"Accept": "application/json"})
    assert response.status_code == 403
示例#16
0
def test_easy_password(app, client):
    authenticate(client)

    data = ('{"password": "******", '
            '"new_password": "******", '
            '"new_password_confirm": "mattmatt2"}')
    response = client.post("/change",
                           data=data,
                           headers={"Content-Type": "application/json"})
    assert response.headers["Content-Type"] == "application/json"
    assert response.status_code == 400
    # Response from zxcvbn
    assert "Repeats like" in response.json["response"]["errors"][
        "new_password"][0]
示例#17
0
def test_trackable_flag(app, client):
    app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1)
    e = "*****@*****.**"
    authenticate(client, email=e)
    logout(client)
    authenticate(client, email=e, headers={"X-Forwarded-For": "127.0.0.1"})

    with app.app_context():
        user = app.security.datastore.find_user(email=e)
        assert user.last_login_at is not None
        assert user.current_login_at is not None
        assert user.last_login_ip == _client_ip(client)
        assert user.current_login_ip == "127.0.0.1"
        assert user.login_count == 2
示例#18
0
def test_set_unauthorized_handler(app, client):
    @app.security.unauthorized_handler
    def unauthorized():
        app.unauthorized_handler_set = True
        return "unauthorized-handler-set", 401

    app.unauthorized_handler_set = False

    authenticate(client, "*****@*****.**")
    response = client.get("/admin", follow_redirects=True)

    assert app.unauthorized_handler_set is True
    assert b"unauthorized-handler-set" in response.data
    assert response.status_code == 401
def test_allow_null_password_nologin(client, get_message):
    # If unified sign in is enabled - should be able to register w/o password
    # With confirmable false - should be logged in automatically upon register.
    # But shouldn't be able to perform normal login again
    data = dict(email="*****@*****.**", password="")
    response = client.post("/register", data=data, follow_redirects=True)
    assert b"Welcome [email protected]" in response.data
    logout(client)

    # Make sure can't log in
    response = authenticate(client, email="*****@*****.**", password="")
    assert get_message("PASSWORD_NOT_PROVIDED") in response.data

    response = authenticate(client, email="*****@*****.**", password="******")
    assert get_message("INVALID_PASSWORD") in response.data
示例#20
0
def test_logout_methods_none(app, sqlalchemy_datastore):
    init_app_with_options(app, sqlalchemy_datastore,
                          **{"SECURITY_LOGOUT_METHODS": None})

    client = app.test_client()

    authenticate(client)

    response = client.get("/logout", follow_redirects=True)

    assert response.status_code == 404

    response = client.post("/logout", follow_redirects=True)

    assert response.status_code == 404
示例#21
0
def test_evil_validate(app, client):
    """
    Test logged in, and randomly try to validate a token
    """
    signalled_identity = []

    @identity_changed.connect_via(app)
    def on_identity_changed(app, identity):
        signalled_identity.append(identity.id)

    response = authenticate(client, "*****@*****.**")
    session = get_session(response)
    assert "tf_state" not in session
    with app.app_context():
        user = app.security.datastore.find_user(email="*****@*****.**")
        assert signalled_identity[0] == user.fs_uniquifier
    del signalled_identity[:]

    # try to validate
    response = client.post("/tf-validate",
                           data=dict(code="?"),
                           follow_redirects=True)
    # This should log us out since it thinks we are evil
    assert not signalled_identity[0]
    del signalled_identity[:]
示例#22
0
def test_verify_next(app, client, get_message):
    authenticate(client)
    response = client.post(
        "/auth/?next=http://localhost/mynext",
        data=dict(password="******"),
        follow_redirects=False,
    )
    assert response.location == "http://localhost/mynext"

    response = client.post(
        "/auth/?next=http%3A%2F%2F127.0.0.1%3A5000%2Fdashboard%2Fsettings%2F",
        data=dict(password="******"),
        follow_redirects=False,
        base_url="http://127.0.0.1:5000",
    )
    assert response.location == "http://127.0.0.1:5000/dashboard/settings/"
示例#23
0
def test_requires_confirmation_error_redirect(app, clients):
    data = dict(email="*****@*****.**", password="******")
    response = clients.post("/register", data=data)

    response = authenticate(clients, **data, follow_redirects=True)
    assert b"send_confirmation_form" in response.data
    assert b"*****@*****.**" in response.data
示例#24
0
def test_unicode_invalid_length(app, client, get_message):
    # From NIST and OWASP - each unicode code point should count as a character.
    authenticate(client)

    # Emoji's are 4 bytes in utf-8
    data = dict(
        password="******",
        new_password="******",
        new_password_confirm="\N{CYCLONE}\N{CYCLONE}\N{FOGGY}\N{FOGGY}",
    )
    response = client.post("/change",
                           json=data,
                           headers={"Content-Type": "application/json"})
    assert response.headers["Content-Type"] == "application/json"
    assert response.status_code == 400
    assert get_message("PASSWORD_INVALID_LENGTH", length=8) in response.data
示例#25
0
def test_my_unauthz_handler(app, client):
    @app.security.unauthz_handler
    def my_unauthz(func, params):
        return (
            jsonify(
                dict(myresponse={"func": func.__name__, "params": params}, code=403)
            ),
            403,
        )

    authenticate(client, "*****@*****.**", "password")

    response = client.get("/admin", headers={"Accept": "application/json"})
    assert response.status_code == 403
    assert response.json["code"] == 403
    assert response.json["myresponse"]["func"] == "roles_required"
    assert response.json["myresponse"]["params"] == ["admin"]
示例#26
0
def test_default_authn_bp(app, client):
    """Test default reauthn handler with blueprint prefix"""

    @auth_required(within=1, grace=0)
    def myview():
        return Response(status=200)

    app.add_url_rule("/myview", view_func=myview, methods=["GET"])
    authenticate(client, endpoint="/myprefix/login")

    # This should require additional authn and redirect to verify
    reset_fresh(client, within=timedelta(minutes=1))
    response = client.get("/myview", follow_redirects=False)
    assert response.status_code == 302
    assert (
        response.location
        == "http://localhost/myprefix/verify?next=http%3A%2F%2Flocalhost%2Fmyview"
    )
示例#27
0
def test_post_already_authenticated(client):
    response = authenticate(client, follow_redirects=True)
    assert b"Welcome [email protected]" in response.data
    data = dict(email="*****@*****.**", password="******")
    response = client.post("/login", data=data, follow_redirects=True)
    assert b"Post Login" in response.data
    # This should NOT override post_login_view due to potential redirect loops.
    response = client.post("/login?next=/page1", data=data, follow_redirects=True)
    assert b"Post Login" in response.data
示例#28
0
def test_qrcode_identity(app, client):
    # Setup authenticator
    authenticate(client, email="*****@*****.**")

    setup_data = dict(setup="authenticator")
    response = client.post("/tf-setup", data=setup_data, follow_redirects=True)
    assert b"Open your authenticator app on your device" in response.data

    # Now request code. Verify that we get 'username' not email.
    mtf = Mock(wraps=app.security._totp_factory)
    app.security.totp_factory(mtf)
    qrcode_page_response = client.get("/tf-qrcode",
                                      data=setup_data,
                                      follow_redirects=True)
    assert mtf.get_totp_uri.call_count == 1
    (username, totp_secret), _ = mtf.get_totp_uri.call_args
    assert username == "jill"
    assert b"svg" in qrcode_page_response.data
示例#29
0
def test_admin_setup_reset(app, client, get_message):
    # Verify can use administrative datastore method to setup SMS
    # and that administrative reset removes access.
    sms_sender = SmsSenderFactory.createSender("test")

    data = dict(email="*****@*****.**", password="******")
    response = client.post(
        "/login", json=data, headers={"Content-Type": "application/json"}
    )
    assert response.json["response"]["tf_required"]

    # we shouldn't be logged in
    response = client.get("/profile", follow_redirects=False)
    assert response.status_code == 302
    assert response.location == "http://localhost/login?next=%2Fprofile"

    # Use admin to setup gene's SMS/phone.
    with app.app_context():
        user = app.security.datastore.find_user(email="*****@*****.**")
        totp_secret = app.security._totp_factory.generate_totp_secret()
        app.security.datastore.tf_set(user, "sms", totp_secret, phone="+442083661177")
        app.security.datastore.commit()

    response = authenticate(client, "*****@*****.**")
    session = get_session(response)
    assert session["tf_state"] == "ready"

    # Grab code that was sent
    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

    # verify we are logged in
    response = client.get("/profile", follow_redirects=False)
    assert response.status_code == 200

    # logout
    logout(client)

    # use administrative reset method
    with app.app_context():
        user = app.security.datastore.find_user(email="*****@*****.**")
        app.security.datastore.reset_user_access(user)
        app.security.datastore.commit()

    data = dict(email="*****@*****.**", password="******")
    response = client.post(
        "/login", json=data, headers={"Content-Type": "application/json"}
    )
    assert response.json["response"]["tf_required"]
    assert response.json["response"]["tf_state"] == "setup_from_login"

    # we shouldn't be logged in
    response = client.get("/profile", follow_redirects=False)
    assert response.status_code == 302
示例#30
0
def test_trackable_with_multiple_ips_in_headers(app, client):
    app.wsgi_app = ProxyFix(app.wsgi_app, x_for=2)

    e = "*****@*****.**"
    authenticate(client, email=e)
    logout(client)
    authenticate(
        client,
        email=e,
        headers={"X-Forwarded-For": "99.99.99.99, 88.88.88.88, 77.77.77.77"},
    )

    with app.app_context():
        user = app.security.datastore.find_user(email=e)
        assert user.last_login_at is not None
        assert user.current_login_at is not None
        assert user.last_login_ip == _client_ip(client)
        assert user.current_login_ip == "88.88.88.88"
        assert user.login_count == 2