Merge pull request #3670 from alphagov/show-broadcast-tour-when-password-reset

Take user to page they are meant to visit in various sign-in flow scenarios
This commit is contained in:
Pea M. Tyczynska
2020-10-12 12:27:37 +01:00
committed by GitHub
19 changed files with 239 additions and 64 deletions

View File

@@ -2,6 +2,8 @@ import pytest
from bs4 import BeautifulSoup
from flask import url_for
from tests.conftest import SERVICE_ONE_ID
def test_should_render_email_verification_resend_show_email_address_and_resend_verify_email(
client,
@@ -27,23 +29,32 @@ def test_should_render_email_verification_resend_show_email_address_and_resend_v
mock_send_verify_email.assert_called_with(api_user_active['id'], api_user_active['email_address'])
@pytest.mark.parametrize('redirect_url', [
None,
f'/services/{SERVICE_ONE_ID}/templates',
])
def test_should_render_correct_resend_template_for_active_user(
client,
api_user_active,
mock_get_user_by_email,
mock_send_verify_code,
redirect_url
):
with client.session_transaction() as session:
session['user_details'] = {
'id': api_user_active['id'],
'email': api_user_active['email_address']}
response = client.get(url_for('main.check_and_resend_text_code'))
response = client.get(url_for('main.check_and_resend_text_code', next=redirect_url))
assert response.status_code == 200
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert page.h1.string == 'Resend security code'
# there shouldn't be a form for updating mobile number
assert page.find('form') is None
assert page.find('a', class_="govuk-button")['href'] == url_for(
'main.check_and_resend_verification_code',
next=redirect_url
)
def test_should_render_correct_resend_template_for_pending_user(
@@ -71,6 +82,10 @@ def test_should_render_correct_resend_template_for_pending_user(
assert page.find('form').input['value'] == api_user_pending['mobile_number']
@pytest.mark.parametrize('redirect_url', [
None,
f'/services/{SERVICE_ONE_ID}/templates',
])
@pytest.mark.parametrize('phone_number_to_register_with', [
'+447700900460',
'+1800-555-555',
@@ -82,6 +97,7 @@ def test_should_resend_verify_code_and_update_mobile_for_pending_user(
mock_update_user_attribute,
mock_send_verify_code,
phone_number_to_register_with,
redirect_url
):
mocker.patch('app.user_api_client.get_user_by_email', return_value=api_user_pending)
@@ -89,10 +105,10 @@ def test_should_resend_verify_code_and_update_mobile_for_pending_user(
session['user_details'] = {
'id': api_user_pending['id'],
'email': api_user_pending['email_address']}
response = client.post(url_for('main.check_and_resend_text_code'),
response = client.post(url_for('main.check_and_resend_text_code', next=redirect_url),
data={'mobile_number': phone_number_to_register_with})
assert response.status_code == 302
assert response.location == url_for('main.verify', _external=True)
assert response.location == url_for('main.verify', _external=True, next=redirect_url)
mock_update_user_attribute.assert_called_once_with(
api_user_pending['id'],
@@ -105,27 +121,37 @@ def test_should_resend_verify_code_and_update_mobile_for_pending_user(
)
@pytest.mark.parametrize('redirect_url', [
None,
f'/services/{SERVICE_ONE_ID}/templates',
])
def test_check_and_redirect_to_two_factor_if_user_active(
client,
api_user_active,
mock_get_user_by_email,
mock_send_verify_code,
redirect_url
):
with client.session_transaction() as session:
session['user_details'] = {
'id': api_user_active['id'],
'email': api_user_active['email_address']}
response = client.get(url_for('main.check_and_resend_verification_code'))
response = client.get(url_for('main.check_and_resend_verification_code', next=redirect_url))
assert response.status_code == 302
assert response.location == url_for('main.two_factor', _external=True)
assert response.location == url_for('main.two_factor', _external=True, next=redirect_url)
@pytest.mark.parametrize('redirect_url', [
None,
f'/services/{SERVICE_ONE_ID}/templates',
])
def test_check_and_redirect_to_verify_if_user_pending(
client,
mocker,
api_user_pending,
mock_get_user_pending,
mock_send_verify_code,
redirect_url
):
mocker.patch('app.user_api_client.get_user_by_email', return_value=api_user_pending)
@@ -134,9 +160,9 @@ def test_check_and_redirect_to_verify_if_user_pending(
session['user_details'] = {
'id': api_user_pending['id'],
'email': api_user_pending['email_address']}
response = client.get(url_for('main.check_and_resend_verification_code'))
response = client.get(url_for('main.check_and_resend_verification_code', next=redirect_url))
assert response.status_code == 302
assert response.location == url_for('main.verify', _external=True)
assert response.location == url_for('main.verify', _external=True, next=redirect_url)
@pytest.mark.parametrize('endpoint', [
@@ -152,3 +178,28 @@ def test_redirect_to_sign_in_if_not_logged_in(
assert response.location == url_for('main.sign_in', _external=True)
assert response.status_code == 302
@pytest.mark.parametrize('redirect_url', [
None,
f'/services/{SERVICE_ONE_ID}/templates',
])
def test_should_render_correct_email_not_received_template_for_active_user(
client,
api_user_active,
mock_get_user_by_email,
mock_send_verify_code,
redirect_url
):
with client.session_transaction() as session:
session['user_details'] = {
'id': api_user_active['id'],
'email': api_user_active['email_address']}
response = client.get(url_for('main.email_not_received', next=redirect_url))
assert response.status_code == 200
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert page.h1.string == 'Resend email link'
# there shouldn't be a form for updating mobile number
assert page.find('form') is None
assert page.find('a', class_="govuk-button")['href'] == url_for('main.resend_email_link', next=redirect_url)

View File

@@ -4,6 +4,7 @@ from notifications_python_client.errors import HTTPError
import app
from tests import user_json
from tests.conftest import SERVICE_ONE_ID
def test_should_render_forgot_password(client):
@@ -31,7 +32,23 @@ def test_should_redirect_to_password_reset_sent_for_valid_email(
assert response.status_code == 200
assert 'Click the link in the email to reset your password.' \
in response.get_data(as_text=True)
app.user_api_client.send_reset_password_url.assert_called_once_with(sample_user['email_address'])
app.user_api_client.send_reset_password_url.assert_called_once_with(sample_user['email_address'], next_string=None)
def test_forgot_password_sends_next_link_with_reset_password_email_request(
client,
fake_uuid,
mocker,
):
sample_user = user_json(email_address='test@user.gov.uk')
mocker.patch('app.user_api_client.send_reset_password_url', return_value=None)
response = client.post(
url_for('.forgot_password') + f"?next=/services/{SERVICE_ONE_ID}/templates",
data={'email_address': sample_user['email_address']})
assert response.status_code == 200
app.user_api_client.send_reset_password_url.assert_called_once_with(
sample_user['email_address'], next_string=f'/services/{SERVICE_ONE_ID}/templates'
)
def test_should_redirect_to_password_reset_sent_for_missing_email(
@@ -48,4 +65,6 @@ def test_should_redirect_to_password_reset_sent_for_missing_email(
assert response.status_code == 200
assert 'Click the link in the email to reset your password.' \
in response.get_data(as_text=True)
app.user_api_client.send_reset_password_url.assert_called_once_with(api_user_active['email_address'])
app.user_api_client.send_reset_password_url.assert_called_once_with(
api_user_active['email_address'], next_string=None
)

View File

@@ -1,11 +1,12 @@
import json
from datetime import datetime
import pytest
from flask import url_for
from itsdangerous import SignatureExpired
from notifications_utils.url_safe_token import generate_token
from tests.conftest import url_for_endpoint_with_token
from tests.conftest import SERVICE_ONE_ID, url_for_endpoint_with_token
def test_should_render_new_password_template(
@@ -36,21 +37,26 @@ def test_should_return_404_when_email_address_does_not_exist(
assert response.status_code == 404
@pytest.mark.parametrize('redirect_url', [
None,
f'/services/{SERVICE_ONE_ID}/templates',
])
def test_should_redirect_to_two_factor_when_password_reset_is_successful(
app_,
client,
mock_get_user_by_email_request_password_reset,
mock_login,
mock_send_verify_code,
mock_reset_failed_login_count
mock_reset_failed_login_count,
redirect_url
):
user = mock_get_user_by_email_request_password_reset.return_value
data = json.dumps({'email': user['email_address'], 'created_at': str(datetime.utcnow())})
token = generate_token(data, app_.config['SECRET_KEY'], app_.config['DANGEROUS_SALT'])
response = client.post(url_for_endpoint_with_token('.new_password', token=token),
response = client.post(url_for_endpoint_with_token('.new_password', token=token, next=redirect_url),
data={'new_password': 'a-new_password'})
assert response.status_code == 302
assert response.location == url_for('.two_factor', _external=True)
assert response.location == url_for('.two_factor', _external=True, next=redirect_url)
mock_get_user_by_email_request_password_reset.assert_called_once_with(user['email_address'])

View File

@@ -5,7 +5,7 @@ from bs4 import BeautifulSoup
from flask import url_for
from app.models.user import User
from tests.conftest import normalize_spaces
from tests.conftest import SERVICE_ONE_ID, normalize_spaces
def test_render_sign_in_template_for_new_user(
@@ -27,6 +27,20 @@ def test_render_sign_in_template_for_new_user(
assert 'Sign in again' not in normalize_spaces(page.text)
def test_render_sign_in_template_with_next_link_for_password_reset(
client_request
):
client_request.logout()
page = client_request.get(
'main.sign_in',
_optional_args=f"?next=/services/{SERVICE_ONE_ID}/templates",
_test_page_title=False
)
forgot_password_link = page.find('a', class_="govuk-link govuk-link--no-visited-state page-footer-secondary-link")
assert forgot_password_link.text == 'Forgotten your password?'
assert forgot_password_link['href'] == url_for('main.forgot_password', next=f'/services/{SERVICE_ONE_ID}/templates')
def test_sign_in_explains_session_timeout(client):
response = client.get(url_for('main.sign_in', next='/foo'))
assert response.status_code == 200
@@ -92,6 +106,10 @@ def test_logged_in_user_redirects_to_account(
)
@pytest.mark.parametrize('redirect_url', [
None,
f'/services/{SERVICE_ONE_ID}/templates',
])
@pytest.mark.parametrize('email_address, password', [
('valid@example.gov.uk', 'val1dPassw0rd!'),
(' valid@example.gov.uk ', ' val1dPassw0rd! '),
@@ -105,34 +123,40 @@ def test_process_sms_auth_sign_in_return_2fa_template(
mock_verify_password,
email_address,
password,
redirect_url
):
response = client.post(
url_for('main.sign_in'), data={
url_for('main.sign_in', next=redirect_url), data={
'email_address': email_address,
'password': password})
assert response.status_code == 302
assert response.location == url_for('.two_factor', _external=True)
assert response.location == url_for('.two_factor', next=redirect_url, _external=True)
mock_verify_password.assert_called_with(api_user_active['id'], password)
mock_get_user_by_email.assert_called_with('valid@example.gov.uk')
@pytest.mark.parametrize('redirect_url', [
None,
f'/services/{SERVICE_ONE_ID}/templates',
])
def test_process_email_auth_sign_in_return_2fa_template(
client,
api_user_active_email_auth,
mock_send_verify_code,
mock_verify_password,
mocker
mocker,
redirect_url
):
mocker.patch('app.user_api_client.get_user', return_value=api_user_active_email_auth)
mocker.patch('app.user_api_client.get_user_by_email', return_value=api_user_active_email_auth)
response = client.post(
url_for('main.sign_in'), data={
url_for('main.sign_in', next=redirect_url), data={
'email_address': 'valid@example.gov.uk',
'password': 'val1dPassw0rd!'})
assert response.status_code == 302
assert response.location == url_for('.two_factor_email_sent', _external=True)
mock_send_verify_code.assert_called_with(api_user_active_email_auth['id'], 'email', None, None)
assert response.location == url_for('.two_factor_email_sent', _external=True, next=redirect_url)
mock_send_verify_code.assert_called_with(api_user_active_email_auth['id'], 'email', None, redirect_url)
mock_verify_password.assert_called_with(api_user_active_email_auth['id'], 'val1dPassw0rd!')
@@ -175,16 +199,21 @@ def test_should_return_redirect_when_user_is_pending(
assert response.status_code == 200
@pytest.mark.parametrize('redirect_url', [
None,
f'/services/{SERVICE_ONE_ID}/templates',
])
def test_should_attempt_redirect_when_user_is_pending(
client,
mock_get_user_by_email_pending,
mock_verify_password,
redirect_url
):
response = client.post(
url_for('main.sign_in'), data={
url_for('main.sign_in', next=redirect_url), data={
'email_address': 'pending_user@example.gov.uk',
'password': 'val1dPassw0rd!'})
assert response.location == url_for('main.resend_email_verification', _external=True)
assert response.location == url_for('main.resend_email_verification', _external=True, next=redirect_url)
assert response.status_code == 302

View File

@@ -11,11 +11,41 @@ from tests.conftest import (
)
@pytest.mark.parametrize('request_url', ['two_factor_email_sent', 'revalidate_email_sent'])
@pytest.mark.parametrize('redirect_url', [None, f'/services/{SERVICE_ONE_ID}/templates'])
@pytest.mark.parametrize('email_resent, page_title', [
(None, 'Check your email'),
(True, 'Email resent')
])
def test_two_factor_email_sent_page(
client,
email_resent,
page_title,
redirect_url,
request_url
):
response = client.get(url_for(f'main.{request_url}', next=redirect_url, email_resent=email_resent))
assert response.status_code == 200
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert page.h1.string == page_title
# there shouldn't be a form for updating mobile number
assert page.find('form') is None
resend_email_link = page.find('a', class_="govuk-link govuk-link--no-visited-state page-footer-secondary-link")
assert resend_email_link.text == 'Not received an email?'
assert resend_email_link['href'] == url_for('main.email_not_received', next=redirect_url)
@pytest.mark.parametrize('redirect_url', [
None,
f'/services/{SERVICE_ONE_ID}/templates',
])
def test_should_render_two_factor_page(
client,
api_user_active,
mock_get_user_by_email,
mocker
mocker,
redirect_url
):
# TODO this lives here until we work out how to
# reassign the session after it is lost mid register process
@@ -24,7 +54,7 @@ def test_should_render_two_factor_page(
'id': api_user_active['id'],
'email': api_user_active['email_address']}
mocker.patch('app.user_api_client.get_user', return_value=api_user_active)
response = client.get(url_for('main.two_factor'))
response = client.get(url_for('main.two_factor', next=redirect_url))
assert response.status_code == 200
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert page.select_one('main p').text.strip() == (
@@ -36,6 +66,10 @@ def test_should_render_two_factor_page(
assert page.select_one('input')['type'] == 'tel'
assert page.select_one('input')['pattern'] == '[0-9]*'
assert page.select_one(
'a:contains("Not received a text message?")'
)['href'] == url_for('main.check_and_resend_text_code', next=redirect_url)
@freeze_time('2020-01-27T12:00:00')
def test_should_login_user_and_should_redirect_to_next_url(
@@ -78,11 +112,15 @@ def test_should_send_email_and_redirect_to_info_page_if_user_needs_to_revalidate
session['user_details'] = {
'id': api_user_active['id'],
'email': api_user_active['email_address']}
response = client.post(url_for('main.two_factor', next='/services/{}'.format(SERVICE_ONE_ID)),
response = client.post(url_for('main.two_factor', next=f'/services/{SERVICE_ONE_ID}'),
data={'sms_code': '12345'})
assert response.status_code == 302
assert response.location == url_for('main.revalidate_email_sent', _external=True)
assert response.location == url_for(
'main.revalidate_email_sent',
_external=True,
next=f'/services/{SERVICE_ONE_ID}'
)
mock_send_verify_code.assert_called_with(api_user_active['id'], 'email', None, mocker.ANY)
@@ -313,17 +351,22 @@ def test_valid_two_factor_email_link_logs_in_user(
assert response.location == url_for('main.show_accounts_or_dashboard', _external=True)
@pytest.mark.parametrize('redirect_url', [
None,
f'/services/{SERVICE_ONE_ID}/templates',
])
def test_two_factor_email_link_has_expired(
app_,
valid_token,
client,
mock_send_verify_code,
fake_uuid
fake_uuid,
redirect_url
):
with set_config(app_, 'EMAIL_2FA_EXPIRY_SECONDS', -1):
response = client.post(
url_for_endpoint_with_token('main.two_factor_email', token=valid_token),
url_for_endpoint_with_token('main.two_factor_email', token=valid_token, next=redirect_url),
follow_redirects=True,
)
@@ -331,6 +374,8 @@ def test_two_factor_email_link_has_expired(
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
assert page.h1.text.strip() == 'The link has expired'
assert page.select_one('a:contains("Sign in again")')['href'] == url_for('main.sign_in', next=redirect_url)
assert mock_send_verify_code.called is False
@@ -346,20 +391,26 @@ def test_two_factor_email_link_is_invalid(
assert normalize_spaces(
page.select_one('.banner-dangerous').text
) == "Theres something wrong with the link youve used."
assert response.status_code == 404
@pytest.mark.parametrize('redirect_url', [
None,
f'/services/{SERVICE_ONE_ID}/templates',
])
def test_two_factor_email_link_is_already_used(
client,
valid_token,
mocker,
mock_send_verify_code
mock_send_verify_code,
redirect_url
):
mocker.patch('app.user_api_client.check_verify_code', return_value=(False, 'Code has expired'))
response = client.post(
url_for_endpoint_with_token('main.two_factor_email', token=valid_token),
url_for_endpoint_with_token('main.two_factor_email', token=valid_token, next=redirect_url),
follow_redirects=True
)
@@ -367,6 +418,8 @@ def test_two_factor_email_link_is_already_used(
assert response.status_code == 200
assert page.h1.text.strip() == 'The link has expired'
assert page.select_one('a:contains("Sign in again")')['href'] == url_for('main.sign_in', next=redirect_url)
assert mock_send_verify_code.called is False