def test_spa_get(app, client): """ Test 'single-page-application' style redirects This uses json only. """ with capture_flashes() as flashes: with capture_registrations() as registrations: response = client.post( "/register", data='{"email": "*****@*****.**",\ "password": "******"}', headers={"Content-Type": "application/json"}, ) assert response.headers["Content-Type"] == "application/json" token = registrations[0]["confirm_token"] response = client.get("/confirm/" + token) assert response.status_code == 302 split = urlsplit(response.headers["Location"]) assert "localhost:8081" == split.netloc assert "/confirm-redirect" == split.path qparams = dict(parse_qsl(split.query)) assert qparams["email"] == "*****@*****.**" # Arguably for json we shouldn't have any - this is buried in register_user # but really shouldn't be. assert len(flashes) == 1
def test_bad_sender(app, client, get_message): # If SMS sender fails - make sure propagated # Test form, json, x signin, setup headers = {"Accept": "application/json", "Content-Type": "application/json"} # test normal, already setup up login. with capture_flashes() as flashes: data = {"email": "*****@*****.**", "password": "******"} response = client.post("login", data=data, follow_redirects=False) assert response.status_code == 302 assert response.location == "http://localhost/login" assert get_message("FAILED_TO_SEND_CODE") in flashes[0]["message"].encode("utf-8") # test w/ JSON data = dict(email="*****@*****.**", password="******") response = client.post("login", json=data, headers=headers) assert response.status_code == 500 assert response.json["response"]["error"].encode("utf-8") == get_message( "FAILED_TO_SEND_CODE" ) # Now test setup tf_authenticate(app, client) client.post("/tf-confirm", data=dict(password="******"), follow_redirects=True) data = dict(setup="sms", phone="+442083661188") response = client.post("tf-setup", data=data) assert get_message("FAILED_TO_SEND_CODE") in response.data response = client.post("tf-setup", json=data, headers=headers) assert response.status_code == 500 assert response.json["response"]["errors"]["setup"][0].encode( "utf-8" ) == get_message("FAILED_TO_SEND_CODE")
def test_expired_reset_token(client, get_message): with capture_reset_password_requests() as requests: client.post("/reset", data=dict(email="*****@*****.**"), follow_redirects=True) user = requests[0]["user"] token = requests[0]["token"] time.sleep(1) with capture_flashes() as flashes: msg = get_message("PASSWORD_RESET_EXPIRED", within="1 milliseconds", email=user.email) # Test getting reset form with expired token response = client.get("/reset/" + token, follow_redirects=True) assert msg in response.data # Test trying to reset password with expired token response = client.post( "/reset/" + token, data={ "password": "******", "password_confirm": "newpassword" }, follow_redirects=True, ) assert msg in response.data assert len(flashes) == 2
def test_verify_fresh(app, client, get_message): # Hit a fresh-required endpoint and walk through verify authenticate(client) with capture_flashes() as flashes: response = client.get("/fresh", follow_redirects=True) assert b"Please Enter Your Password" in response.data assert flashes[0]["category"] == "error" assert flashes[0]["message"].encode("utf-8") == get_message( "REAUTHENTICATION_REQUIRED") form_response = response.data.decode("utf-8") matcher = re.match(r'.*action="([^"]*)".*', form_response, re.IGNORECASE | re.DOTALL) verify_url = matcher.group(1) response = client.get(verify_url) assert b"Please Enter Your Password" in response.data response = client.post(verify_url, data=dict(password="******"), follow_redirects=False) assert b"Please Enter Your Password" in response.data response = client.post(verify_url, data=dict(password="******"), follow_redirects=False) assert response.location == "http://localhost/fresh" # should be fine now response = client.get("/fresh", follow_redirects=True) assert b"Fresh Only" in response.data
def test_two_factor_two_factor_setup_anonymous(app, client, get_message): # trying to pick method without doing earlier stage data = dict(setup="mail") with capture_flashes() as flashes: response = client.post("/tf-setup", data=data) assert response.status_code == 302 assert flashes[0]["category"] == "error" assert flashes[0]["message"].encode("utf-8") == get_message( "TWO_FACTOR_PERMISSION_DENIED")
def test_spa_get_bad_token(app, client, get_message): """ Test expired and invalid token""" with capture_flashes() as flashes: with capture_reset_password_requests() as requests: response = client.post( "/reset", data='{"email": "*****@*****.**"}', headers={"Content-Type": "application/json"}, ) assert response.headers["Content-Type"] == "application/json" assert "user" not in response.jdata["response"] token = requests[0]["token"] time.sleep(1) response = client.get("/reset/" + token) assert response.status_code == 302 split = urlsplit(response.headers["Location"]) assert "localhost:8081" == split.netloc assert "/reset-error" == split.path qparams = dict(parse_qsl(split.query)) assert len(qparams) == 2 assert all(k in qparams for k in ["email", "error"]) msg = get_message( "PASSWORD_RESET_EXPIRED", within="1 milliseconds", email="*****@*****.**" ) assert msg == qparams["error"].encode("utf-8") # Test mangled token token = ( "WyIxNjQ2MzYiLCIxMzQ1YzBlZmVhM2VhZjYwODgwMDhhZGU2YzU0MzZjMiJd." "BZEw_Q.lQyo3npdPZtcJ_sNHVHP103syjM" "&url_id=fbb89a8328e58c181ea7d064c2987874bc54a23d" ) response = client.get("/reset/" + token) assert response.status_code == 302 split = urlsplit(response.headers["Location"]) assert "localhost:8081" == split.netloc assert "/reset-error" == split.path qparams = dict(parse_qsl(split.query)) assert len(qparams) == 1 assert all(k in qparams for k in ["error"]) msg = get_message("INVALID_RESET_PASSWORD_TOKEN") assert msg == qparams["error"].encode("utf-8") assert len(flashes) == 0
def test_authn_freshness(app, client, get_message): """ Test freshness using default reauthn_handler """ @auth_required(within=30, grace=0) def myview(): return Response(status=200) @auth_required(within=0.001, grace=0) def myspecialview(): return Response(status=200) app.add_url_rule("/myview", view_func=myview, methods=["GET"]) app.add_url_rule("/myspecialview", view_func=myspecialview, methods=["GET"]) authenticate(client) # This should work and not be redirected response = client.get("/myview", follow_redirects=False) assert response.status_code == 200 # This should require additional authn and redirect to verify time.sleep(0.1) with capture_flashes() as flashes: response = client.get("/myspecialview", follow_redirects=False) assert response.status_code == 302 assert ( response.location == "http://localhost/verify?next=http%3A%2F%2Flocalhost%2Fmyspecialview" ) assert flashes[0]["category"] == "error" assert flashes[0]["message"].encode("utf-8") == get_message( "REAUTHENTICATION_REQUIRED") # Test json error response response = client.get("/myspecialview", headers={"accept": "application/json"}) assert response.status_code == 401 assert response.json["response"]["error"].encode("utf-8") == get_message( "REAUTHENTICATION_REQUIRED")
def test_spa_get(app, client): """ Test 'single-page-application' style redirects This uses json only. """ with capture_flashes() as flashes: with capture_passwordless_login_requests() as requests: response = client.post( "/login", json=dict(email="*****@*****.**"), headers={"Content-Type": "application/json"}, ) assert response.headers["Content-Type"] == "application/json" token = requests[0]["login_token"] response = client.get("/login/" + token) assert response.status_code == 302 split = urlsplit(response.headers["Location"]) assert "localhost:8081" == split.netloc assert "/login-redirect" == split.path qparams = dict(parse_qsl(split.query)) assert qparams["email"] == "*****@*****.**" assert len(flashes) == 0
def test_recoverable_json(app, client, get_message): recorded_resets = [] recorded_instructions_sent = [] @password_reset.connect_via(app) def on_password_reset(app, user): recorded_resets.append(user) @reset_password_instructions_sent.connect_via(app) def on_instructions_sent(app, user, token): recorded_instructions_sent.append(user) with capture_flashes() as flashes: # Test reset password creates a token and sends email with capture_reset_password_requests() as requests: with app.mail.record_messages() as outbox: response = client.post( "/reset", json=dict(email="*****@*****.**"), headers={"Content-Type": "application/json"}, ) assert response.headers["Content-Type"] == "application/json" assert len(recorded_instructions_sent) == 1 assert len(outbox) == 1 assert response.status_code == 200 token = requests[0]["token"] # Test invalid email response = client.post( "/reset", json=dict(email="*****@*****.**"), headers={"Content-Type": "application/json"}, ) assert response.status_code == 400 assert response.json["response"]["errors"]["email"][0].encode( "utf-8") == get_message("USER_DOES_NOT_EXIST") # Test submitting a new password but leave out 'confirm' response = client.post( "/reset/" + token, data='{"password": "******"}', headers={"Content-Type": "application/json"}, ) assert response.status_code == 400 assert response.json["response"]["errors"]["password_confirm"][ 0].encode("utf-8") == get_message("PASSWORD_NOT_PROVIDED") # Test submitting a new password response = client.post( "/reset/" + token + "?include_auth_token", json=dict(password="******", password_confirm="awesome sunset"), headers={"Content-Type": "application/json"}, ) assert all(k in response.json["response"]["user"] for k in ["id", "authentication_token"]) assert len(recorded_resets) == 1 # reset automatically logs user in logout(client) # Test logging in with the new password response = client.post( "/login?include_auth_token", json=dict(email="*****@*****.**", password="******"), headers={"Content-Type": "application/json"}, ) assert all(k in response.json["response"]["user"] for k in ["id", "authentication_token"]) logout(client) # Use token again - should fail since already have set new password. response = client.post( "/reset/" + token, json=dict(password="******", password_confirm="newpassword"), headers={"Content-Type": "application/json"}, ) assert response.status_code == 400 assert len(recorded_resets) == 1 # Test invalid token response = client.post( "/reset/bogus", json=dict(password="******", password_confirm="newpassword"), headers={"Content-Type": "application/json"}, ) assert response.json["response"]["error"].encode( "utf-8") == get_message("INVALID_RESET_PASSWORD_TOKEN") assert len(flashes) == 0
def test_simple_login_json(app, client_nc, get_message): auths = [] @user_authenticated.connect_via(app) def authned(myapp, user, **extra_args): auths.append((user.email, extra_args["authn_via"])) headers = { "Accept": "application/json", "Content-Type": "application/json" } with capture_flashes() as flashes: response = client_nc.get("/us-signin", headers=headers) assert (response.json["response"]["methods"] == app.config["SECURITY_US_ENABLED_METHODS"]) assert (response.json["response"]["identity_attributes"] == app.config["SECURITY_USER_IDENTITY_ATTRIBUTES"]) with capture_send_code_requests() as requests: with app.mail.record_messages() as outbox: response = client_nc.post( "/us-send-code", json=dict(identity="*****@*****.**", chosen_method="email"), headers=headers, follow_redirects=True, ) assert response.status_code == 200 assert "csrf_token" in response.json["response"] assert "user" not in response.json["response"] assert len(requests) == 1 assert len(outbox) == 1 # try bad code response = client_nc.post( "/us-signin", json=dict(identity="*****@*****.**", passcode="blahblah"), headers=headers, follow_redirects=True, ) assert response.status_code == 400 assert response.json["response"]["errors"]["passcode"][0].encode( "utf-8") == get_message("INVALID_PASSWORD") # Login successfully with code response = client_nc.post( "/us-signin?include_auth_token", json=dict(identity="*****@*****.**", passcode=requests[0]["token"]), headers=headers, follow_redirects=True, ) assert response.status_code == 200 assert "authentication_token" in response.json["response"]["user"] assert "email" in auths[0][1] logout(client_nc) response = client_nc.get("/profile", headers=headers, follow_redirects=False) assert response.status_code == 401 # login via SMS sms_sender = SmsSenderFactory.createSender("test") set_phone(app) response = client_nc.post( "/us-send-code", json=dict(identity="*****@*****.**", chosen_method="sms"), headers=headers, follow_redirects=True, ) assert response.status_code == 200 code = sms_sender.messages[0].split()[-1].strip(".") response = client_nc.post( "/us-signin?include_auth_token", json=dict(identity="*****@*****.**", passcode=code), headers=headers, follow_redirects=True, ) assert response.status_code == 200 assert "authentication_token" in response.json["response"]["user"] assert len(flashes) == 0