From e9383b733e112891fddd35d2a5987a1fe1389496 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Tue, 15 Dec 2015 11:06:09 +0000 Subject: [PATCH 1/6] 109898688: Implement get method for email-not-received and text-not-received --- app/main/__init__.py | 2 +- app/main/views/code_not_received.py | 23 +++++++++++++++++++ app/main/views/index.py | 10 -------- app/main/views/sign_in.py | 3 +-- .../app/main/views/test_code_not_received.py | 18 +++++++++++++++ 5 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 app/main/views/code_not_received.py create mode 100644 tests/app/main/views/test_code_not_received.py diff --git a/app/main/__init__.py b/app/main/__init__.py index 4025b6dd1..021b5c086 100644 --- a/app/main/__init__.py +++ b/app/main/__init__.py @@ -3,4 +3,4 @@ from flask import Blueprint main = Blueprint('main', __name__) -from app.main.views import index, sign_in, register, two_factor, verify, sms, add_service +from app.main.views import index, sign_in, register, two_factor, verify, sms, add_service, code_not_received diff --git a/app/main/views/code_not_received.py b/app/main/views/code_not_received.py new file mode 100644 index 000000000..c07544bc7 --- /dev/null +++ b/app/main/views/code_not_received.py @@ -0,0 +1,23 @@ +from flask import render_template + +from app.main import main + + +@main.route("/email-not-received", methods=['GET']) +def email_not_received(): + return render_template('views/email-not-received.html') + + +@main.route('/email-not-received', methods=['POST']) +def check_and_resend_email_code(): + return None + + +@main.route("/text-not-received", methods=['GET']) +def text_not_received(): + return render_template('views/text-not-received.html') + + +@main.route('/text-not-received', methods=['POST']) +def check_and_resend_text_code(): + return None diff --git a/app/main/views/index.py b/app/main/views/index.py index 485b4d2d6..c9cdc41dd 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -35,16 +35,6 @@ def dashboard(): return render_template('views/dashboard.html') -@main.route("/email-not-received") -def emailnotreceived(): - return render_template('views/email-not-received.html') - - -@main.route("/text-not-received") -def textnotreceived(): - return render_template('views/text-not-received.html') - - @main.route("/send-email") def sendemail(): return render_template('views/send-email.html') diff --git a/app/main/views/sign_in.py b/app/main/views/sign_in.py index b2b4a34be..a5bb4f6c3 100644 --- a/app/main/views/sign_in.py +++ b/app/main/views/sign_in.py @@ -4,7 +4,6 @@ from flask import session from app.main import main from app.main.dao import users_dao from app.main.encryption import check_hash -from app.main.encryption import hashpw from app.main.forms import LoginForm from app.main.views import send_sms_code @@ -26,7 +25,7 @@ def process_sign_in(): if not user.is_active(): return jsonify(active_user=False), 401 if check_hash(form.password.data, user.password): - sms_code = send_sms_code(user.id, user.mobile_number) + send_sms_code(user.id, user.mobile_number) session['user_id'] = user.id else: users_dao.increment_failed_login_count(user.id) diff --git a/tests/app/main/views/test_code_not_received.py b/tests/app/main/views/test_code_not_received.py new file mode 100644 index 000000000..bab907fdd --- /dev/null +++ b/tests/app/main/views/test_code_not_received.py @@ -0,0 +1,18 @@ +def test_should_render_email_code_not_received_template(notifications_admin): + response = notifications_admin.test_client().get('/email-not-received') + assert response.status_code == 200 + assert 'Check your email address is correct and then resend the confirmation code' \ + in response.get_data(as_text=True) + + +# def test_should_check_and_resend_email_code(notifications_admin, notifications_admin_db, notify_db_session): +# response = notifications_admin.test_client().post('/email-not-received', +# data={'email_adddress': 'test@user.gov.uk'}) +# assert response is None + + +def test_should_render_text_code_not_received_template(notifications_admin): + response = notifications_admin.test_client().get('/text-not-received') + assert response.status_code == 200 + assert 'Check your mobile phone number is correct and then resend the confirmation code.' \ + in response.get_data(as_text=True) From bd8bb3c926999ad1cb722e32d5ae128a882713f9 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Tue, 15 Dec 2015 15:35:30 +0000 Subject: [PATCH 2/6] 109898688: Implementation of text-not-received and email-not-received --- app/main/dao/users_dao.py | 14 +++ app/main/dao/verify_codes_dao.py | 4 +- app/main/forms.py | 15 +++ app/main/views/__init__.py | 2 +- app/main/views/code_not_received.py | 30 ++++- app/templates/views/email-not-received.html | 10 +- app/templates/views/text-not-received.html | 16 +-- tests/app/main/dao/test_users_dao.py | 17 +++ .../app/main/views/test_code_not_received.py | 117 +++++++++++++++--- 9 files changed, 192 insertions(+), 33 deletions(-) diff --git a/app/main/dao/users_dao.py b/app/main/dao/users_dao.py index 83521985e..326bafdc7 100644 --- a/app/main/dao/users_dao.py +++ b/app/main/dao/users_dao.py @@ -37,3 +37,17 @@ def activate_user(id): user.state = 'active' db.session.add(user) db.session.commit() + + +def update_email_address(id, email_address): + user = get_user_by_id(id) + user.email_address = email_address + db.session.add(user) + db.session.commit() + + +def update_mobile_number(id, mobile_number): + user = get_user_by_id(id) + user.mobile_number = mobile_number + db.session.add(user) + db.session.commit() diff --git a/app/main/dao/verify_codes_dao.py b/app/main/dao/verify_codes_dao.py index 049432982..9c48acaec 100644 --- a/app/main/dao/verify_codes_dao.py +++ b/app/main/dao/verify_codes_dao.py @@ -20,8 +20,8 @@ def get_code(user_id, code_type): return verify_code -def get_code_by_code(user_id, code_type): - return VerifyCodes.query.filter_by(user_id=user_id, code_type=code_type).first() +def get_code_by_code(user_id, code, code_type): + return VerifyCodes.query.filter_by(user_id=user_id, code=code, code_type=code_type).first() def use_code(id): diff --git a/app/main/forms.py b/app/main/forms.py index 300b2600f..e9433e176 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -84,6 +84,21 @@ def validate_code(field, code): return False +class EmailNotReceivedForm(Form): + email_address = StringField('Email address', validators=[ + Length(min=5, max=255), + DataRequired(message='Email cannot be empty'), + Email(message='Please enter a valid email address'), + Regexp(regex=gov_uk_email, message='Please enter a gov.uk email address') + ]) + + +class TextNotReceivedForm(Form): + mobile_number = StringField('Mobile phone number', + validators=[DataRequired(message='Please enter your mobile number'), + Regexp(regex=mobile_number, message='Please enter a +44 mobile number')]) + + class AddServiceForm(Form): service_name = StringField(validators=[DataRequired(message='Please enter your service name')]) diff --git a/app/main/views/__init__.py b/app/main/views/__init__.py index e7db79518..e092ba59c 100644 --- a/app/main/views/__init__.py +++ b/app/main/views/__init__.py @@ -12,7 +12,7 @@ def send_sms_code(user_id, mobile_number): sms_code = create_verify_code() try: verify_codes_dao.add_code(user_id=user_id, code=sms_code, code_type='sms') - admin_api_client.send_sms(mobile_number, message=sms_code, token=admin_api_client.auth_token) + admin_api_client.send_sms(mobile_number=mobile_number, message=sms_code, token=admin_api_client.auth_token) except: raise AdminApiClientException('Exception when sending sms.') return sms_code diff --git a/app/main/views/code_not_received.py b/app/main/views/code_not_received.py index c07544bc7..b5ce3bd16 100644 --- a/app/main/views/code_not_received.py +++ b/app/main/views/code_not_received.py @@ -1,23 +1,43 @@ -from flask import render_template +from flask import render_template, redirect, jsonify, session from app.main import main +from app.main.dao import users_dao, verify_codes_dao +from app.main.forms import EmailNotReceivedForm, TextNotReceivedForm +from app.main.views import send_sms_code, send_email_code @main.route("/email-not-received", methods=['GET']) def email_not_received(): - return render_template('views/email-not-received.html') + user = users_dao.get_user_by_id(session['user_id']) + return render_template('views/email-not-received.html', + form=EmailNotReceivedForm(email_address=user.email_address)) @main.route('/email-not-received', methods=['POST']) def check_and_resend_email_code(): - return None + form = EmailNotReceivedForm() + if form.validate_on_submit(): + user = users_dao.get_user_by_id(session['user_id']) + users_dao.update_email_address(id=user.id, email_address=form.email_address.data) + verify_codes_dao.use_code_for_user_and_type(user_id=user.id, code_type='email') + send_email_code(user_id=user.id, email=user.email_address) + return redirect('/verify') + return jsonify(form.errors), 400 @main.route("/text-not-received", methods=['GET']) def text_not_received(): - return render_template('views/text-not-received.html') + user = users_dao.get_user_by_id(session['user_id']) + return render_template('views/text-not-received.html', form=TextNotReceivedForm(mobile_number=user.mobile_number)) @main.route('/text-not-received', methods=['POST']) def check_and_resend_text_code(): - return None + form = TextNotReceivedForm() + if form.validate_on_submit(): + user = users_dao.get_user_by_id(session['user_id']) + users_dao.update_mobile_number(id=user.id, mobile_number=form.mobile_number.data) + verify_codes_dao.use_code_for_user_and_type(user_id=user.id, code_type='sms') + send_sms_code(user_id=user.id, mobile_number=user.mobile_number) + return redirect('/verify') + return jsonify(form.errors), 400 diff --git a/app/templates/views/email-not-received.html b/app/templates/views/email-not-received.html index 758589199..7383f26c3 100644 --- a/app/templates/views/email-not-received.html +++ b/app/templates/views/email-not-received.html @@ -13,9 +13,13 @@ GOV.UK Notify

Check your email address is correct and then resend the confirmation code.

- -
- Your email address must end in .gov.uk +

+ {{ form.hidden_tag() }} + + + {{ form.email_address(class="form-control-2-3", autocomplete="off") }}
+ Your email address must end in .gov.uk +

diff --git a/app/templates/views/text-not-received.html b/app/templates/views/text-not-received.html index 4b332c894..078b5ab53 100644 --- a/app/templates/views/text-not-received.html +++ b/app/templates/views/text-not-received.html @@ -12,15 +12,15 @@ GOV.UK Notify

Check your mobile phone number is correct and then resend the confirmation code.

+

- - -

- - -

- Resend confirmation code -

+ + {{ form.mobile_number(class="form-control-1-4", autocomplete="off") }}
+

+

+ +

+
diff --git a/tests/app/main/dao/test_users_dao.py b/tests/app/main/dao/test_users_dao.py index 10b01052f..f169a7fd9 100644 --- a/tests/app/main/dao/test_users_dao.py +++ b/tests/app/main/dao/test_users_dao.py @@ -144,3 +144,20 @@ def test_should_throws_error_when_id_does_not_exist(notifications_admin, notific with pytest.raises(AttributeError) as error: users_dao.activate_user(123) assert '''object has no attribute 'state''''' in str(error.value) + + +def test_should_update_email_address(notifications_admin, notifications_admin_db, notify_db_session): + user = User(name='Update Email', + password='somepassword', + email_address='test@it.gov.uk', + mobile_number='+441234123412', + created_at=datetime.now(), + role_id=1, + state='inactive') + users_dao.insert_user(user) + + saved = users_dao.get_user_by_id(user.id) + assert saved.email_address == 'test@it.gov.uk' + users_dao.update_email_address(user.id, 'new_email@testit.gov.uk') + updated = users_dao.get_user_by_id(user.id) + assert updated.email_address == 'new_email@testit.gov.uk' diff --git a/tests/app/main/views/test_code_not_received.py b/tests/app/main/views/test_code_not_received.py index bab907fdd..50d2d8806 100644 --- a/tests/app/main/views/test_code_not_received.py +++ b/tests/app/main/views/test_code_not_received.py @@ -1,18 +1,107 @@ -def test_should_render_email_code_not_received_template(notifications_admin): - response = notifications_admin.test_client().get('/email-not-received') - assert response.status_code == 200 - assert 'Check your email address is correct and then resend the confirmation code' \ - in response.get_data(as_text=True) +from app import admin_api_client +from app.main.dao import verify_codes_dao, users_dao +from tests.app.main import create_test_user -# def test_should_check_and_resend_email_code(notifications_admin, notifications_admin_db, notify_db_session): -# response = notifications_admin.test_client().post('/email-not-received', -# data={'email_adddress': 'test@user.gov.uk'}) -# assert response is None +def test_should_render_email_code_not_received_template_and_populate_email_address(notifications_admin, + notifications_admin_db, + notify_db_session): + with notifications_admin.test_client() as client: + with client.session_transaction() as session: + user = create_test_user() + session['user_id'] = user.id + response = client.get('/email-not-received') + assert response.status_code == 200 + assert 'Check your email address is correct and then resend the confirmation code' \ + in response.get_data(as_text=True) + assert 'value="test@user.gov.uk"' in response.get_data(as_text=True) -def test_should_render_text_code_not_received_template(notifications_admin): - response = notifications_admin.test_client().get('/text-not-received') - assert response.status_code == 200 - assert 'Check your mobile phone number is correct and then resend the confirmation code.' \ - in response.get_data(as_text=True) +def test_should_check_and_resend_email_code_redirect_to_verify(notifications_admin, + notifications_admin_db, + notify_db_session, + mocker): + with notifications_admin.test_client() as client: + with client.session_transaction() as session: + _set_up_mocker(mocker) + user = create_test_user() + session['user_id'] = user.id + verify_codes_dao.add_code(user.id, code='12345', code_type='email') + response = client.post('/email-not-received', + data={'email_address': 'test@user.gov.uk'}) + assert response.status_code == 302 + assert response.location == 'http://localhost/verify' + + +def test_should_render_text_code_not_received_template(notifications_admin, + notifications_admin_db, + notify_db_session, + mocker): + with notifications_admin.test_client() as client: + with client.session_transaction() as session: + _set_up_mocker(mocker) + user = create_test_user() + session['user_id'] = user.id + verify_codes_dao.add_code(user.id, code='12345', code_type='email') + response = client.get('/text-not-received') + assert response.status_code == 200 + assert 'Check your mobile phone number is correct and then resend the confirmation code.' \ + in response.get_data(as_text=True) + assert 'value="+441234123412"' + + +def test_should_check_and_redirect_to_verify(notifications_admin, + notifications_admin_db, + notify_db_session, + mocker): + with notifications_admin.test_client() as client: + with client.session_transaction() as session: + _set_up_mocker(mocker) + user = create_test_user() + session['user_id'] = user.id + verify_codes_dao.add_code(user.id, code='12345', code_type='sms') + response = client.post('/text-not-received', + data={'mobile_number': '+441234123412'}) + assert response.status_code == 302 + assert response.location == 'http://localhost/verify' + + +def test_should_update_email_address_resend_code(notifications_admin, + notifications_admin_db, + notify_db_session, + mocker): + with notifications_admin.test_client() as client: + with client.session_transaction() as session: + _set_up_mocker(mocker) + user = create_test_user() + session['user_id'] = user.id + verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='email') + response = client.post('/email-not-received', + data={'email_address': 'new@address.gov.uk'}) + assert response.status_code == 302 + assert response.location == 'http://localhost/verify' + updated_user = users_dao.get_user_by_id(user.id) + assert updated_user.email_address == 'new@address.gov.uk' + + +def test_should_update_mobile_number_resend_code(notifications_admin, + notifications_admin_db, + notify_db_session, + mocker): + with notifications_admin.test_client() as client: + with client.session_transaction() as session: + _set_up_mocker(mocker) + user = create_test_user() + session['user_id'] = user.id + verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') + response = client.post('/text-not-received', + data={'mobile_number': '+443456789012'}) + assert response.status_code == 302 + assert response.location == 'http://localhost/verify' + updated_user = users_dao.get_user_by_id(user.id) + assert updated_user.mobile_number == '+443456789012' + + +def _set_up_mocker(mocker): + mocker.patch("app.admin_api_client.send_sms") + mocker.patch("app.admin_api_client.send_email") From 64812c1614b28a1f693c3670fa38c93daa16ddc1 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Wed, 16 Dec 2015 12:20:25 +0000 Subject: [PATCH 3/6] 109898688: All codes are valid until one code is used, then they are all marked used. Fixed the is_active() method on the Users model, if the user was pending they would come back as active, allowing a user to sign in before being active. There is still a problem with the validate_sms_code and validate_email_code method. --- app/main/dao/verify_codes_dao.py | 13 +- app/main/forms.py | 18 +- app/main/views/code_not_received.py | 2 - app/models.py | 2 +- app/static/stylesheets/govuk-template.css | 276 +++++++++--------- app/templates/views/email-not-received.html | 18 +- app/templates/views/text-not-received.html | 3 +- tests/app/main/__init__.py | 16 +- tests/app/main/dao/test_service_dao.py | 10 +- tests/app/main/dao/test_verify_codes_dao.py | 42 ++- tests/app/main/test_add_service_form.py | 2 +- tests/app/main/test_two_factor_form.py | 5 +- tests/app/main/test_verify_form.py | 4 +- tests/app/main/views/test_add_service.py | 6 +- .../app/main/views/test_code_not_received.py | 13 +- tests/app/main/views/test_sign_in.py | 22 +- tests/app/main/views/test_two_factor.py | 4 +- tests/app/main/views/test_verify.py | 6 +- 18 files changed, 259 insertions(+), 203 deletions(-) diff --git a/app/main/dao/verify_codes_dao.py b/app/main/dao/verify_codes_dao.py index 9c48acaec..c7e325741 100644 --- a/app/main/dao/verify_codes_dao.py +++ b/app/main/dao/verify_codes_dao.py @@ -13,11 +13,11 @@ def add_code(user_id, code, code_type): db.session.add(code) db.session.commit() + return code.id -def get_code(user_id, code_type): - verify_code = VerifyCodes.query.filter_by(user_id=user_id, code_type=code_type, code_used=False).first() - return verify_code +def get_codes(user_id, code_type): + return VerifyCodes.query.filter_by(user_id=user_id, code_type=code_type, code_used=False).all() def get_code_by_code(user_id, code, code_type): @@ -32,9 +32,10 @@ def use_code(id): def use_code_for_user_and_type(user_id, code_type): - verify_code = VerifyCodes.query.filter_by(user_id=user_id, code_type=code_type).first() - verify_code.code_used = True - db.session.add(verify_code) + codes = VerifyCodes.query.filter_by(user_id=user_id, code_type=code_type, code_used=False).all() + for verify_code in codes: + verify_code.code_used = True + db.session.add(verify_code) db.session.commit() diff --git a/app/main/forms.py b/app/main/forms.py index e9433e176..626247525 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -49,8 +49,10 @@ class TwoFactorForm(Form): Regexp(regex=verify_code, message='Code must be 5 digits')]) def validate_sms_code(self, a): - code = verify_codes_dao.get_code(session['user_id'], 'sms') - validate_code(self.sms_code, code) + codes = verify_codes_dao.get_codes(session['user_id'], 'sms') + for code in codes: + if validate_code(self.sms_code, code): + return True class VerifyForm(Form): @@ -62,12 +64,16 @@ class VerifyForm(Form): Regexp(regex=verify_code, message='Code must be 5 digits')]) def validate_email_code(self, a): - code = verify_codes_dao.get_code(session['user_id'], 'email') - validate_code(self.email_code, code) + codes = verify_codes_dao.get_codes(session['user_id'], 'email') + for code in codes: + if validate_code(self.email_code, code): + return True def validate_sms_code(self, a): - code = verify_codes_dao.get_code(session['user_id'], 'sms') - validate_code(self.sms_code, code) + codes = verify_codes_dao.get_codes(session['user_id'], 'sms') + for code in codes: + if validate_code(self.sms_code, code): + return True def validate_code(field, code): diff --git a/app/main/views/code_not_received.py b/app/main/views/code_not_received.py index b5ce3bd16..fc775d21f 100644 --- a/app/main/views/code_not_received.py +++ b/app/main/views/code_not_received.py @@ -19,7 +19,6 @@ def check_and_resend_email_code(): if form.validate_on_submit(): user = users_dao.get_user_by_id(session['user_id']) users_dao.update_email_address(id=user.id, email_address=form.email_address.data) - verify_codes_dao.use_code_for_user_and_type(user_id=user.id, code_type='email') send_email_code(user_id=user.id, email=user.email_address) return redirect('/verify') return jsonify(form.errors), 400 @@ -37,7 +36,6 @@ def check_and_resend_text_code(): if form.validate_on_submit(): user = users_dao.get_user_by_id(session['user_id']) users_dao.update_mobile_number(id=user.id, mobile_number=form.mobile_number.data) - verify_codes_dao.use_code_for_user_and_type(user_id=user.id, code_type='sms') send_sms_code(user_id=user.id, mobile_number=user.mobile_number) return redirect('/verify') return jsonify(form.errors), 400 diff --git a/app/models.py b/app/models.py index d420c9070..fb02ce287 100644 --- a/app/models.py +++ b/app/models.py @@ -60,7 +60,7 @@ class User(db.Model): return True def is_active(self): - if self.state == 'inactive': + if self.state != 'active': return False else: return True diff --git a/app/static/stylesheets/govuk-template.css b/app/static/stylesheets/govuk-template.css index ebda91bf9..079bcd973 100644 --- a/app/static/stylesheets/govuk-template.css +++ b/app/static/stylesheets/govuk-template.css @@ -28,7 +28,7 @@ @-o-viewport { width: device-width; } -/* line 46, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_shims.scss */ +/* line 46, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_shims.scss */ #global-header .header-wrapper:after, #global-header .header-wrapper .header-global:after, #global-header .header-wrapper .header-global .header-logo:after, #global-header .header-proposition #proposition-link:after, #global-header .header-proposition #proposition-links:after, #footer .footer-wrapper:after, #footer .footer-meta:after { content: ""; @@ -36,19 +36,19 @@ clear: both; } -/* line 18, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_grid_layout.scss */ +/* line 18, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_grid_layout.scss */ #global-header-bar, #global-cookie-message p { max-width: 960px; margin: 0 15px; } @media (min-width: 641px) { - /* line 18, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_grid_layout.scss */ + /* line 18, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_grid_layout.scss */ #global-header-bar, #global-cookie-message p { margin: 0 30px; } } @media (min-width: 1020px) { - /* line 18, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_grid_layout.scss */ + /* line 18, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_grid_layout.scss */ #global-header-bar, #global-cookie-message p { margin: 0 auto; } @@ -60,7 +60,7 @@ @-o-viewport { width: device-width; } -/* line 46, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_shims.scss */ +/* line 46, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_shims.scss */ #global-header .header-wrapper:after, #global-header .header-wrapper .header-global:after, #global-header .header-wrapper .header-global .header-logo:after, #global-header .header-proposition #proposition-link:after, #global-header .header-proposition #proposition-links:after, #footer .footer-wrapper:after, #footer .footer-meta:after { content: ""; @@ -76,24 +76,24 @@ } /* local styleguide includes */ /* Old depricated greys, new things should use the toolkit greys */ -/* line 1, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 1, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ html, body, button, input, table, td, th { font-family: "nta", Arial, sans-serif; } -/* line 4, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 4, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ html, body, div, h1, h2, h3, h4, h5, h6, article, aside, footer, header, hgroup, nav, section { margin: 0; padding: 0; vertical-align: baseline; } -/* line 11, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 11, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ main { display: block; } -/* line 16, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 16, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ .group:before, .group:after { content: "\0020"; @@ -102,23 +102,23 @@ main { overflow: hidden; } -/* line 24, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 24, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ .group:after { clear: both; } -/* line 27, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 27, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ .group { zoom: 1; } -/* line 32, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 32, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ .content-fixed { top: 0; position: fixed; } -/* line 36, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 36, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ .shim { display: block; } @@ -127,7 +127,7 @@ main { * 1. Prevents iOS text size adjust after orientation change, without disabling * user zoom. */ -/* line 44, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 44, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ html { -webkit-text-size-adjust: 100%; /* 1 */ @@ -139,12 +139,12 @@ html { /* Force the scrollbar to always display in IE10/11 */ -/* line 54, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 54, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ html { -ms-overflow-style: scrollbar; } -/* line 58, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 58, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ body { background: #fff; color: #0b0c0c; @@ -154,86 +154,86 @@ body { -moz-osx-font-smoothing: grayscale; } -/* line 67, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 67, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ ol, ul, nav ol, nav ul { list-style: inherit; } -/* line 71, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 71, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ fieldset { border: none; padding: 0; } -/* line 76, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 76, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ a:link { color: #005ea5; } -/* line 80, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 80, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ a:visited { color: #4c2c92; } -/* line 84, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 84, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ a:hover { color: #2e8aca; } -/* line 88, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 88, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ a:active { color: #2e8aca; } -/* line 290, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ +/* line 290, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ a[rel="external"]:after { background-image: image-url("external-links/external-link.png"); background-repeat: no-repeat; } @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 20 / 10), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) { - /* line 290, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ + /* line 290, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ a[rel="external"]:after { background-image: image-url("external-links/external-link-24x24.png"); background-size: 12px 400px; } } -/* line 231, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ +/* line 231, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ a[rel="external"]:after { content: "    "; background-position: right 6px; } -/* line 240, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ +/* line 240, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ a[rel="external"]:hover:after { background-position: right -382px; } @media (max-width: 640px) { - /* line 231, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ + /* line 231, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ a[rel="external"]:after { content: "     "; background-position: right 3px; } - /* line 240, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ + /* line 240, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ a[rel="external"]:hover:after { background-position: right -385px; } } -/* line 231, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ +/* line 231, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ .external-link:after { content: "        "; background-position: right 0px; } -/* line 240, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ +/* line 240, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ .external-link:hover:after { background-position: right 0px; } -/* line 302, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ +/* line 302, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ .external-link:after { background-image: image-url("external-links/external-link-black-12x12.png"); background-repeat: no-repeat; } @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 20 / 10), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) { - /* line 302, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ + /* line 302, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_frontend_toolkit/stylesheets/_typography.scss */ .external-link:after { background-image: image-url("external-links/external-link-black-24x24.png"); background-size: 12px 400px; @@ -249,7 +249,7 @@ a[rel="external"]:hover:after { * 3. Removes Android and iOS tap highlight color to prevent entire container being highlighted * www.yuiblog.com/blog/2010/10/01/quick-tip-customizing-the-mobile-safari-tap-highlight-color/ */ -/* line 117, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 117, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ html { font-size: 62.5%; /* 1 */ @@ -265,7 +265,7 @@ html { * (62.5% * 160% = 100%) * 2. Addresses margins handled incorrectly in IE6/7 */ -/* line 130, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 130, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ body { font-size: 160%; /* 1 */ @@ -273,18 +273,18 @@ body { /* 2 */ } -/* line 135, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 135, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ b, strong { font-weight: 600; } -/* line 140, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 140, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ img { border: 0; } -/* line 150, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 150, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ abbr[title] { cursor: help; } @@ -294,7 +294,7 @@ abbr[title] { * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome * (include `-moz` to future-proof). */ -/* line 160, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 160, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ input[type="search"] { -webkit-appearance: textfield; /* 1 */ @@ -304,19 +304,19 @@ input[type="search"] { box-sizing: content-box; } -/* line 166, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 166, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: searchfield-cancel-button; margin-right: 2px; } -/* line 171, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ +/* line 171, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_basic.scss */ input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /* For image replacement */ -/* line 2, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 2, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ .ir { display: block; text-indent: -999em; @@ -325,20 +325,20 @@ input[type="search"]::-webkit-search-decoration { text-align: left; direction: ltr; } -/* line 10, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 10, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ .ir br { display: none; } /* Hide for both screenreaders and browsers */ -/* line 16, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 16, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ .hidden { display: none; visibility: hidden; } /* Hide only visually, but have it available for screenreaders */ -/* line 22, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 22, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ .visually-hidden, .visuallyhidden { position: absolute; @@ -348,7 +348,7 @@ input[type="search"]::-webkit-search-decoration { * focusable when navigated to via the keyboard */ } -/* line 31, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 31, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ .visually-hidden.focusable:active, .visually-hidden.focusable:focus, .visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { @@ -361,54 +361,54 @@ input[type="search"]::-webkit-search-decoration { } /* Hide visually and from screenreaders, but maintain layout */ -/* line 43, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 43, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ .invisible { visibility: hidden; } /* Give a strong clear visual idea as to what is currently in focus */ -/* line 48, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 48, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ a { -webkit-tap-highlight-color: rgba(0, 0, 0, 0.3); } -/* line 52, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 52, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ a:focus { background-color: #ffbf47; outline: 3px solid #ffbf47; } /* Make skiplinks visible when they are tabbed to */ -/* line 59, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 59, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ .skiplink { position: absolute; left: -9999em; } -/* line 64, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 64, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ .skiplink:focus { position: static; } -/* line 68, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 68, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ #skiplink-container { text-align: center; background: #0b0c0c; } -/* line 72, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 72, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ #skiplink-container div { text-align: left; margin: 0 auto; max-width: 1020px; } -/* line 78, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 78, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ #skiplink-container .skiplink { display: -moz-inline-stack; display: inline-block; margin: 0.75em 0 0 30px; } -/* line 84, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 84, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ input:focus, textarea:focus, select:focus, @@ -417,12 +417,12 @@ button:focus, outline: 3px solid #ffbf47; } -/* line 93, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 93, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ #global-header h1 a:focus { background-color: transparent; outline: none; } -/* line 98, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ +/* line 98, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_accessibility.scss */ #global-header a:focus { color: #0b0c0c; } @@ -430,12 +430,12 @@ button:focus, /* @import '_shims'; */ /* @import '_conditionals'; */ /* @import '_measurements'; */ -/* line 5, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 5, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header { background-color: #0b0c0c; width: 100%; } -/* line 9, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 9, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-wrapper { background-color: #0b0c0c; max-width: 990px; @@ -444,61 +444,61 @@ button:focus, padding-bottom: 8px; } @media (min-width: 641px) { - /* line 9, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 9, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-wrapper { padding-left: 15px; padding-right: 15px; } } -/* line 26, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 26, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-wrapper .header-global .header-logo { float: left; } @media (min-width: 769px) { - /* line 26, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 26, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-wrapper .header-global .header-logo { width: 33.33%; } } @media screen and (max-width: 379px) { - /* line 26, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 26, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-wrapper .header-global .header-logo { width: auto; float: none; } } -/* line 38, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 38, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-wrapper .header-global .header-logo .content { margin: 0 15px; } -/* line 42, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 42, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-wrapper .header-global .header-logo { margin: 5px 0 2px; } @media (min-width: 769px) { - /* line 49, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 49, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header.with-proposition .header-wrapper .header-global { float: left; width: 33.33%; } - /* line 54, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 54, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header.with-proposition .header-wrapper .header-global .header-logo, #global-header.with-proposition .header-wrapper .header-global .site-search { width: 100%; } } @media (min-width: 769px) { - /* line 60, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 60, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header.with-proposition .header-wrapper .header-proposition { width: 66.66%; float: left; } } -/* line 65, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 65, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header.with-proposition .header-wrapper .header-proposition .content { margin: 0 15px; } -/* line 72, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 72, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header #logo { float: left; position: relative; @@ -519,7 +519,7 @@ button:focus, background-size: 35px 31px; background-position: 0 0; } -/* line 98, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 98, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header #logo img { position: relative; top: -2px; @@ -532,26 +532,26 @@ button:focus, border: none; /* visibility: hidden; */ } -/* line 115, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 115, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header #logo:hover, #global-header #logo:focus { text-decoration: none; border-bottom-color: #fff; } -/* line 121, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 121, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header #logo:active { color: #2b8cc4; } -/* line 125, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 125, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition { padding-top: 10px; } @media (min-width: 769px) { - /* line 125, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 125, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition { padding-top: 0; } } -/* line 130, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 130, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-name { font-family: "nta", Arial, sans-serif; font-size: 18px; @@ -563,17 +563,17 @@ button:focus, text-decoration: none; } @media (min-width: 641px) { - /* line 130, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 130, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-name { font-size: 24px; line-height: 1.25; } } -/* line 136, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 136, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition a#proposition-name:hover { text-decoration: underline; } -/* line 139, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 139, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition a.menu { font-family: "nta", Arial, sans-serif; font-size: 14px; @@ -587,23 +587,23 @@ button:focus, padding-top: 6px; } @media (min-width: 641px) { - /* line 139, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 139, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition a.menu { font-size: 16px; line-height: 1.25; } } @media (min-width: 769px) { - /* line 139, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 139, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition a.menu { display: none; } } -/* line 149, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 149, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition a.menu:hover { text-decoration: underline; } -/* line 152, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 152, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition a.menu:after { display: inline-block; font-size: 8px; @@ -612,45 +612,45 @@ button:focus, vertical-align: middle; content: " \25BC"; } -/* line 160, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 160, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition a.menu.js-hidden:after { content: " \25B2"; } -/* line 164, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 164, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-menu { margin-top: 5px; } @media (min-width: 769px) { - /* line 168, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 168, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-menu.no-proposition-name { margin-top: 37px; } } -/* line 175, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 175, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-link, #global-header .header-proposition #proposition-links { clear: both; margin: 2px 0 0 0; padding: 0; } -/* line 182, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 182, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ .js-enabled #global-header .header-proposition #proposition-link, .js-enabled #global-header .header-proposition #proposition-links { display: none; } @media (min-width: 769px) { - /* line 182, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 182, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ .js-enabled #global-header .header-proposition #proposition-link, .js-enabled #global-header .header-proposition #proposition-links { display: block; } } -/* line 187, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 187, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ .js-enabled #global-header .header-proposition #proposition-link.js-visible, .js-enabled #global-header .header-proposition #proposition-links.js-visible { display: block; } -/* line 192, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 192, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-link li, #global-header .header-proposition #proposition-links li { float: left; @@ -659,7 +659,7 @@ button:focus, border-bottom: 1px solid #2e3133; } @media (min-width: 769px) { - /* line 192, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 192, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-link li, #global-header .header-proposition #proposition-links li { display: block; @@ -667,13 +667,13 @@ button:focus, padding: 0 15px 0 0; border-bottom: 0; } - /* line 204, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 204, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-link li.clear-child, #global-header .header-proposition #proposition-links li.clear-child { clear: left; } } -/* line 210, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 210, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-link a, #global-header .header-proposition #proposition-links a { color: #fff; @@ -685,7 +685,7 @@ button:focus, text-transform: none; } @media (min-width: 641px) { - /* line 210, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 210, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-link a, #global-header .header-proposition #proposition-links a { font-size: 14px; @@ -693,7 +693,7 @@ button:focus, } } @media (min-width: 769px) { - /* line 210, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 210, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-link a, #global-header .header-proposition #proposition-links a { font-family: "nta", Arial, sans-serif; @@ -705,7 +705,7 @@ button:focus, } } @media (min-width: 769px) and (min-width: 641px) { - /* line 210, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 210, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-link a, #global-header .header-proposition #proposition-links a { font-size: 16px; @@ -713,59 +713,59 @@ button:focus, } } -/* line 220, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 220, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-link a:hover, #global-header .header-proposition #proposition-links a:hover { text-decoration: underline; } -/* line 223, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 223, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-link a.active, #global-header .header-proposition #proposition-links a.active { color: #1d8feb; } -/* line 226, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 226, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-link a:focus, #global-header .header-proposition #proposition-links a:focus { color: #0b0c0c; } -/* line 232, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 232, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-link { float: right; line-height: 22px; } -/* line 235, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 235, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ .js-enabled #global-header .header-proposition #proposition-link { display: block; } @media (min-width: 769px) { - /* line 232, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 232, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header .header-proposition #proposition-link { float: none; } } /* Global header bar */ -/* line 247, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 247, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-header-bar { height: 10px; background-color: #005ea5; } /* Global cookie message */ -/* line 258, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 258, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ .js-enabled #global-cookie-message { display: none; /* shown with JS, always on for non-JS */ } -/* line 262, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 262, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-cookie-message { width: 100%; background-color: #d5e8f3; padding-top: 10px; padding-bottom: 10px; } -/* line 267, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ +/* line 267, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-cookie-message p { font-family: "nta", Arial, sans-serif; font-size: 14px; @@ -776,7 +776,7 @@ button:focus, margin-bottom: 0; } @media (min-width: 641px) { - /* line 267, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ + /* line 267, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_header.scss */ #global-cookie-message p { font-size: 16px; line-height: 1.25; @@ -784,12 +784,12 @@ button:focus, } /* Global footer */ -/* line 3, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 3, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer { background-color: #DEE0E2; border-top: 1px solid #a1acb2; } -/* line 7, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 7, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-wrapper { margin: 0 auto; width: auto; @@ -799,20 +799,20 @@ button:focus, margin: 0 auto; } @media (min-width: 641px) { - /* line 7, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 7, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-wrapper { padding-top: 60px; } } -/* line 17, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 17, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer a { color: #454a4c; } -/* line 20, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 20, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer a:hover { color: #171819; } -/* line 25, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 25, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer h2 { font-family: "nta", Arial, sans-serif; font-size: 18px; @@ -824,17 +824,17 @@ button:focus, margin: 0; } @media (min-width: 641px) { - /* line 25, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 25, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer h2 { font-size: 24px; line-height: 1.25; } } -/* line 31, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 31, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer h2 a { color: inherit; } -/* line 36, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 36, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta { padding-left: 15px; padding-right: 15px; @@ -844,25 +844,25 @@ button:focus, color: #454a4c; } @media (min-width: 641px) { - /* line 36, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 36, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta { padding-left: 30px; padding-right: 30px; } } -/* line 44, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 44, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner { display: inline-block; vertical-align: bottom; width: 100%; } @media (min-width: 641px) { - /* line 44, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 44, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner { width: 75%; } } -/* line 58, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 58, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner ul { font-family: "nta", Arial, sans-serif; font-size: 14px; @@ -875,41 +875,41 @@ button:focus, padding: 0; } @media (min-width: 641px) { - /* line 58, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 58, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner ul { font-size: 16px; line-height: 1.5; } } @media (min-width: 641px) { - /* line 58, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 58, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner ul { margin: 0 0 1em 0; } } -/* line 69, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 69, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner ul li { display: inline-block; margin: 0 15px 0 0; } -/* line 81, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 81, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner .open-government-licence { clear: left; position: relative; } @media (min-width: 641px) { - /* line 81, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 81, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner .open-government-licence { padding-left: 53px; } } -/* line 93, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 93, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner .open-government-licence .logo { margin-bottom: 1em; padding-top: 0; } @media (min-width: 641px) { - /* line 93, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 93, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner .open-government-licence .logo { position: absolute; left: 0; @@ -918,7 +918,7 @@ button:focus, height: 100%; } } -/* line 104, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 104, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner .open-government-licence .logo a { display: block; width: 41px; @@ -928,13 +928,13 @@ button:focus, background: image-url("/static/images/open-government-licence.png") 0 0 no-repeat; } @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 20 / 10), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) { - /* line 104, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 104, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner .open-government-licence .logo a { background-image: image-url("/static/images/open-government-licence_2x.png"); background-size: 41px 17px; } } -/* line 118, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 118, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner .open-government-licence p { font-family: "nta", Arial, sans-serif; font-size: 14px; @@ -945,13 +945,13 @@ button:focus, padding-top: 0.1em; } @media (min-width: 641px) { - /* line 118, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 118, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .footer-meta-inner .open-government-licence p { font-size: 16px; line-height: 1.25; } } -/* line 126, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 126, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .copyright { font-family: "nta", Arial, sans-serif; font-size: 14px; @@ -963,14 +963,14 @@ button:focus, display: block; } @media (min-width: 641px) { - /* line 126, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 126, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .copyright { font-size: 16px; line-height: 1.25; } } @media (min-width: 641px) { - /* line 126, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 126, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .copyright { display: inline-block; text-align: inherit; @@ -979,7 +979,7 @@ button:focus, margin-top: 0; } } -/* line 147, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ +/* line 147, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .copyright a { display: block; background-image: url("/static/images/govuk-crest.png"); @@ -990,20 +990,20 @@ button:focus, padding: 115px 0 0 0; } @media (min-width: 641px) { - /* line 147, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 147, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .copyright a { background-position: 100% 0%; } } @media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 20 / 10), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx) { - /* line 147, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 147, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .copyright a { background-image: url("/static/images/govuk-crest-2x.png"); background-size: 125px 102px; } } @media (min-width: 641px) { - /* line 147, /Users/chs/gdsworkspace/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ + /* line 147, /Users/rebeccalaw/dev-projects/notify/notifications-admin/app/assets/stylesheets/govuk_template/_footer.scss */ #footer .footer-meta .copyright a { text-align: right; } diff --git a/app/templates/views/email-not-received.html b/app/templates/views/email-not-received.html index 7383f26c3..3b970bbf4 100644 --- a/app/templates/views/email-not-received.html +++ b/app/templates/views/email-not-received.html @@ -13,19 +13,17 @@ GOV.UK Notify

Check your email address is correct and then resend the confirmation code.

+

- {{ form.hidden_tag() }} - + {{ form.hidden_tag() }} + - {{ form.email_address(class="form-control-2-3", autocomplete="off") }}
- Your email address must end in .gov.uk + {{ form.email_address(class="form-control-2-3", autocomplete="off") }}
+ Your email address must end in .gov.uk +

+ +

-

- - -

- Resend confirmation code -

diff --git a/app/templates/views/text-not-received.html b/app/templates/views/text-not-received.html index 078b5ab53..e63b7cb5c 100644 --- a/app/templates/views/text-not-received.html +++ b/app/templates/views/text-not-received.html @@ -13,7 +13,8 @@ GOV.UK Notify

Check your mobile phone number is correct and then resend the confirmation code.

-

+ {{ form.hidden_tag() }} +

{{ form.mobile_number(class="form-control-1-4", autocomplete="off") }}

diff --git a/tests/app/main/__init__.py b/tests/app/main/__init__.py index b1fbeaa07..2478de141 100644 --- a/tests/app/main/__init__.py +++ b/tests/app/main/__init__.py @@ -4,13 +4,25 @@ from app.main.dao import users_dao from app.models import User -def create_test_user(): +def create_test_user(state): user = User(name='Test User', password='somepassword', email_address='test@user.gov.uk', mobile_number='+441234123412', created_at=datetime.now(), role_id=1, - state='pending') + state=state) + users_dao.insert_user(user) + return user + + +def create_another_test_user(state): + user = User(name='Another Test User', + password='someOtherpassword', + email_address='another_test@user.gov.uk', + mobile_number='+442233123412', + created_at=datetime.now(), + role_id=1, + state=state) users_dao.insert_user(user) return user diff --git a/tests/app/main/dao/test_service_dao.py b/tests/app/main/dao/test_service_dao.py index e8918eb1f..30762b6ba 100644 --- a/tests/app/main/dao/test_service_dao.py +++ b/tests/app/main/dao/test_service_dao.py @@ -6,7 +6,7 @@ from tests.app.main import create_test_user def test_can_insert_and_retrieve_new_service(notifications_admin, notifications_admin_db, notify_db_session): - user = create_test_user() + user = create_test_user('active') id = services_dao.insert_new_service('testing service', user) saved_service = services_dao.get_service_by_id(id) assert id == saved_service.id @@ -15,7 +15,7 @@ def test_can_insert_and_retrieve_new_service(notifications_admin, notifications_ def test_unrestrict_service_updates_the_service(notifications_admin, notifications_admin_db, notify_db_session): - user = create_test_user() + user = create_test_user('active') id = services_dao.insert_new_service('unrestricted service', user) saved_service = services_dao.get_service_by_id(id) assert saved_service.restricted is True @@ -25,7 +25,7 @@ def test_unrestrict_service_updates_the_service(notifications_admin, notificatio def test_activate_service_update_service(notifications_admin, notifications_admin_db, notify_db_session): - user = create_test_user() + user = create_test_user('active') id = services_dao.insert_new_service('activated service', user) service = services_dao.get_service_by_id(id) assert service.active is False @@ -44,7 +44,7 @@ def test_get_service_returns_none_if_service_does_not_exist(notifications_admin, def test_find_by_service_name_returns_right_service(notifications_admin, notifications_admin_db, notify_db_session): - user = create_test_user() + user = create_test_user('active') id = services_dao.insert_new_service('testing service', user) another = services_dao.insert_new_service('Testing the Service', user) found = services_dao.find_service_by_service_name('testing service') @@ -55,7 +55,7 @@ def test_find_by_service_name_returns_right_service(notifications_admin, def test_should_not_allow_two_services_of_the_same_name(notifications_admin, notifications_admin_db, notify_db_session): - user = create_test_user() + user = create_test_user('active') services_dao.insert_new_service('duplicate service', user) with pytest.raises(sqlalchemy.exc.IntegrityError) as error: services_dao.insert_new_service('duplicate service', user) diff --git a/tests/app/main/dao/test_verify_codes_dao.py b/tests/app/main/dao/test_verify_codes_dao.py index 3bec44812..1b95da1f5 100644 --- a/tests/app/main/dao/test_verify_codes_dao.py +++ b/tests/app/main/dao/test_verify_codes_dao.py @@ -7,10 +7,12 @@ from tests.app.main import create_test_user def test_insert_new_code_and_get_it_back(notifications_admin, notifications_admin_db, notify_db_session): - user = create_test_user() + user = create_test_user('pending') verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='email') - saved_code = verify_codes_dao.get_code(user_id=user.id, code_type='email') + saved_codes = verify_codes_dao.get_codes(user_id=user.id, code_type='email') + assert len(saved_codes) == 1 + saved_code = saved_codes[0] assert saved_code.user_id == user.id assert check_hash('12345', saved_code.code) assert saved_code.code_type == 'email' @@ -20,7 +22,7 @@ def test_insert_new_code_and_get_it_back(notifications_admin, notifications_admi def test_insert_new_code_should_thrw_exception_when_type_does_not_exist(notifications_admin, notifications_admin_db, notify_db_session): - user = create_test_user() + user = create_test_user('pending') try: verify_codes_dao.add_code(user_id=user.id, code='23545', code_type='not_real') fail('Should have thrown an exception') @@ -42,7 +44,7 @@ def test_should_throw_exception_when_user_does_not_exist(notifications_admin, def test_should_return_none_if_code_is_used(notifications_admin, notifications_admin_db, notify_db_session): - user = create_test_user() + user = create_test_user('pending') verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='email') verify_codes_dao.use_code(user_id=user.id, code='12345', code_type='email') @@ -53,10 +55,32 @@ def test_should_return_none_if_code_is_used(notifications_admin, def test_should_return_none_if_code_is_used(notifications_admin, notifications_admin_db, notify_db_session): - user = create_test_user() + user = create_test_user('pending') verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') - code = verify_codes_dao.get_code(user_id=user.id, code_type='sms') - verify_codes_dao.use_code(code.id) - code = verify_codes_dao.get_code(user_id=user.id, code_type='sms') - assert code is None + code = verify_codes_dao.get_codes(user_id=user.id, code_type='sms') + verify_codes_dao.use_code(code[0].id) + used_code = verify_codes_dao.get_codes(user_id=user.id, code_type='sms') + assert used_code == [] + + +def test_should_return_all_unused_code_when_there_are_many(notifications_admin, + notifications_admin_db, + notify_db_session): + user = create_test_user('pending') + another_user = create_test_user('active') + verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') + id = verify_codes_dao.add_code(user_id=user.id, code='09876', code_type='email') + verify_codes_dao.use_code(id) + verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='email') + verify_codes_dao.add_code(user_id=user.id, code='23456', code_type='email') + verify_codes_dao.add_code(user_id=another_user.id, code='12345', code_type='sms') + verify_codes_dao.add_code(user_id=another_user.id, code='12345', code_type='email') + + user_codes = verify_codes_dao.get_codes(user_id=user.id, code_type='email') + assert len(user_codes) == 2 + s = sorted(user_codes, key=lambda r: r.expiry_datetime) + assert check_hash('12345', s[0].code) + assert check_hash('23456', s[1].code) + assert [(code.code_used, code.code_type, code.user_id) for code in user_codes] == \ + [(False, 'email', user.id), (False, 'email', user.id)] diff --git a/tests/app/main/test_add_service_form.py b/tests/app/main/test_add_service_form.py index 87f460c22..53478266d 100644 --- a/tests/app/main/test_add_service_form.py +++ b/tests/app/main/test_add_service_form.py @@ -8,7 +8,7 @@ def test_form_should_have_errors_when_duplicate_service_is_added(notifications_a notify_db_session): with notifications_admin.test_request_context(method='POST', data={'service_name': 'some service'}) as req: - user = create_test_user() + user = create_test_user('active') services_dao.insert_new_service('some service', user) req.session['user_id'] = user.id form = AddServiceForm(req.request.form) diff --git a/tests/app/main/test_two_factor_form.py b/tests/app/main/test_two_factor_form.py index 107f7757c..ebc414b00 100644 --- a/tests/app/main/test_two_factor_form.py +++ b/tests/app/main/test_two_factor_form.py @@ -51,13 +51,12 @@ def test_returns_errors_when_code_contains_letters(notifications_admin, notifica def test_should_return_errors_when_code_is_expired(notifications_admin, notifications_admin_db, notify_db_session): with notifications_admin.test_request_context(method='POST', data={'sms_code': '23456'}) as req: - user = create_test_user() + user = create_test_user('active') req.session['user_id'] = user.id verify_codes_dao.add_code_with_expiry(user_id=user.id, code='23456', code_type='sms', expiry=datetime.now() + timedelta(hours=-2)) - req.session['user_id'] = user.id form = TwoFactorForm(req.request.form) assert form.validate() is False errors = form.errors @@ -66,6 +65,6 @@ def test_should_return_errors_when_code_is_expired(notifications_admin, notifica def set_up_test_data(): - user = create_test_user() + user = create_test_user('active') verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') return user diff --git a/tests/app/main/test_verify_form.py b/tests/app/main/test_verify_form.py index 9c4b7dedd..35425cb23 100644 --- a/tests/app/main/test_verify_form.py +++ b/tests/app/main/test_verify_form.py @@ -65,7 +65,7 @@ def test_should_return_errors_when_code_is_expired(notifications_admin, notifica with notifications_admin.test_request_context(method='POST', data={'sms_code': '23456', 'email_code': '23456'}) as req: - user = create_test_user() + user = create_test_user('pending') req.session['user_id'] = user.id verify_codes_dao.add_code_with_expiry(user_id=user.id, code='23456', @@ -87,7 +87,7 @@ def test_should_return_errors_when_code_is_expired(notifications_admin, notifica def set_up_test_data(): - user = create_test_user() + user = create_test_user('pending') verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='email') verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') return user diff --git a/tests/app/main/views/test_add_service.py b/tests/app/main/views/test_add_service.py index 8ac5755d4..f5f2e3288 100644 --- a/tests/app/main/views/test_add_service.py +++ b/tests/app/main/views/test_add_service.py @@ -5,7 +5,7 @@ from tests.app.main import create_test_user def test_get_should_render_add_service_template(notifications_admin, notifications_admin_db, notify_db_session): with notifications_admin.test_client() as client: with client.session_transaction() as session: - user = create_test_user() + user = create_test_user('active') session['user_id'] = user.id verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') client.post('/two-factor', data={'sms_code': '12345'}) @@ -17,7 +17,7 @@ def test_get_should_render_add_service_template(notifications_admin, notificatio def test_should_add_service_and_redirect_to_next_page(notifications_admin, notifications_admin_db, notify_db_session): with notifications_admin.test_client() as client: with client.session_transaction() as session: - user = create_test_user() + user = create_test_user('active') session['user_id'] = user.id verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') client.post('/two-factor', data={'sms_code': '12345'}) @@ -33,7 +33,7 @@ def test_should_return_form_errors_when_service_name_is_empty(notifications_admi notify_db_session): with notifications_admin.test_client() as client: with client.session_transaction() as session: - user = create_test_user() + user = create_test_user('active') session['user_id'] = user.id verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') client.post('/two-factor', data={'sms_code': '12345'}) diff --git a/tests/app/main/views/test_code_not_received.py b/tests/app/main/views/test_code_not_received.py index 50d2d8806..1b9c50332 100644 --- a/tests/app/main/views/test_code_not_received.py +++ b/tests/app/main/views/test_code_not_received.py @@ -1,4 +1,3 @@ -from app import admin_api_client from app.main.dao import verify_codes_dao, users_dao from tests.app.main import create_test_user @@ -8,7 +7,7 @@ def test_should_render_email_code_not_received_template_and_populate_email_addre notify_db_session): with notifications_admin.test_client() as client: with client.session_transaction() as session: - user = create_test_user() + user = create_test_user('pending') session['user_id'] = user.id response = client.get('/email-not-received') assert response.status_code == 200 @@ -24,7 +23,7 @@ def test_should_check_and_resend_email_code_redirect_to_verify(notifications_adm with notifications_admin.test_client() as client: with client.session_transaction() as session: _set_up_mocker(mocker) - user = create_test_user() + user = create_test_user('pending') session['user_id'] = user.id verify_codes_dao.add_code(user.id, code='12345', code_type='email') response = client.post('/email-not-received', @@ -40,7 +39,7 @@ def test_should_render_text_code_not_received_template(notifications_admin, with notifications_admin.test_client() as client: with client.session_transaction() as session: _set_up_mocker(mocker) - user = create_test_user() + user = create_test_user('pending') session['user_id'] = user.id verify_codes_dao.add_code(user.id, code='12345', code_type='email') response = client.get('/text-not-received') @@ -57,7 +56,7 @@ def test_should_check_and_redirect_to_verify(notifications_admin, with notifications_admin.test_client() as client: with client.session_transaction() as session: _set_up_mocker(mocker) - user = create_test_user() + user = create_test_user('pending') session['user_id'] = user.id verify_codes_dao.add_code(user.id, code='12345', code_type='sms') response = client.post('/text-not-received', @@ -73,7 +72,7 @@ def test_should_update_email_address_resend_code(notifications_admin, with notifications_admin.test_client() as client: with client.session_transaction() as session: _set_up_mocker(mocker) - user = create_test_user() + user = create_test_user('pending') session['user_id'] = user.id verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='email') response = client.post('/email-not-received', @@ -91,7 +90,7 @@ def test_should_update_mobile_number_resend_code(notifications_admin, with notifications_admin.test_client() as client: with client.session_transaction() as session: _set_up_mocker(mocker) - user = create_test_user() + user = create_test_user('pending') session['user_id'] = user.id verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') response = client.post('/text-not-received', diff --git a/tests/app/main/views/test_sign_in.py b/tests/app/main/views/test_sign_in.py index bad65e5c9..cc4778a37 100644 --- a/tests/app/main/views/test_sign_in.py +++ b/tests/app/main/views/test_sign_in.py @@ -20,7 +20,8 @@ def test_process_sign_in_return_2fa_template(notifications_admin, notifications_ mobile_number='+441234123123', name='valid', created_at=datetime.now(), - role_id=1) + role_id=1, + state='active') users_dao.insert_user(user) response = notifications_admin.test_client().post('/sign-in', data={'email_address': 'valid@example.gov.uk', @@ -37,7 +38,8 @@ def test_should_return_locked_out_true_when_user_is_locked(notifications_admin, mobile_number='+441234123123', name='valid', created_at=datetime.now(), - role_id=1) + role_id=1, + state='active') users_dao.insert_user(user) for _ in range(10): notifications_admin.test_client().post('/sign-in', @@ -86,6 +88,22 @@ def test_should_return_401_when_user_does_not_exist(notifications_admin, notific assert response.status_code == 401 +def test_should_return_400_when_user_is_not_active(notifications_admin, notifications_admin_db, notify_db_session): + user = User(email_address='PendingUser@example.gov.uk', + password='val1dPassw0rd!', + mobile_number='+441234123123', + name='pending user', + created_at=datetime.now(), + role_id=1, + state='pending') + users_dao.insert_user(user) + response = notifications_admin.test_client().post('/sign-in', + data={'email_address': 'PendingUser@example.gov.uk', + 'password': 'val1dPassw0rd!'}) + assert response.status_code == 401 + assert '"active_user": false' in response.get_data(as_text=True) + + def _set_up_mocker(mocker): mocker.patch("app.admin_api_client.send_sms") mocker.patch("app.admin_api_client.send_email") diff --git a/tests/app/main/views/test_two_factor.py b/tests/app/main/views/test_two_factor.py index 18992f0d9..205130fb7 100644 --- a/tests/app/main/views/test_two_factor.py +++ b/tests/app/main/views/test_two_factor.py @@ -13,7 +13,7 @@ def test_should_render_two_factor_page(notifications_admin, notifications_admin_ def test_should_login_user_and_redirect_to_dashboard(notifications_admin, notifications_admin_db, notify_db_session): with notifications_admin.test_client() as client: with client.session_transaction() as session: - user = create_test_user() + user = create_test_user('active') session['user_id'] = user.id verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') response = client.post('/two-factor', @@ -28,7 +28,7 @@ def test_should_return_400_with_sms_code_error_when_sms_code_is_wrong(notificati notify_db_session): with notifications_admin.test_client() as client: with client.session_transaction() as session: - user = create_test_user() + user = create_test_user('active') session['user_id'] = user.id verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') response = client.post('/two-factor', diff --git a/tests/app/main/views/test_verify.py b/tests/app/main/views/test_verify.py index 13bb17382..a905420f1 100644 --- a/tests/app/main/views/test_verify.py +++ b/tests/app/main/views/test_verify.py @@ -14,7 +14,7 @@ def test_should_redirect_to_add_service_when_code_are_correct(notifications_admi notify_db_session): with notifications_admin.test_client() as client: with client.session_transaction() as session: - user = create_test_user() + user = create_test_user('pending') session['user_id'] = user.id verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') verify_codes_dao.add_code(user_id=user.id, code='23456', code_type='email') @@ -28,7 +28,7 @@ def test_should_redirect_to_add_service_when_code_are_correct(notifications_admi def test_should_activate_user_after_verify(notifications_admin, notifications_admin_db, notify_db_session): with notifications_admin.test_client() as client: with client.session_transaction() as session: - user = create_test_user() + user = create_test_user('pending') session['user_id'] = user.id verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') verify_codes_dao.add_code(user_id=user.id, code='23456', code_type='email') @@ -43,7 +43,7 @@ def test_should_activate_user_after_verify(notifications_admin, notifications_ad def test_should_return_400_when_codes_are_wrong(notifications_admin, notifications_admin_db, notify_db_session): with notifications_admin.test_client() as client: with client.session_transaction() as session: - user = create_test_user() + user = create_test_user('pending') session['user_id'] = user.id verify_codes_dao.add_code(user_id=user.id, code='23345', code_type='sms') verify_codes_dao.add_code(user_id=user.id, code='98456', code_type='email') From 010be66d31ae027e819efad41957655024066e63 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Thu, 17 Dec 2015 14:25:03 +0000 Subject: [PATCH 4/6] 109898688: Complete the implementation of the did not receive code. --- app/main/dao/verify_codes_dao.py | 6 +++- app/main/forms.py | 51 ++++++++++++++--------------- tests/app/main/test_verify_form.py | 16 +++++++++ tests/app/main/views/test_verify.py | 21 ++++++++++++ 4 files changed, 67 insertions(+), 27 deletions(-) diff --git a/app/main/dao/verify_codes_dao.py b/app/main/dao/verify_codes_dao.py index c7e325741..6f3e21d9d 100644 --- a/app/main/dao/verify_codes_dao.py +++ b/app/main/dao/verify_codes_dao.py @@ -39,9 +39,13 @@ def use_code_for_user_and_type(user_id, code_type): db.session.commit() +def get_code_by_id(id): + return VerifyCodes.query.get(id) + + def add_code_with_expiry(user_id, code, code_type, expiry): code = VerifyCodes(user_id=user_id, - code=code, + code=hashpw(code), code_type=code_type, expiry_datetime=expiry) diff --git a/app/main/forms.py b/app/main/forms.py index 626247525..eddc61bf6 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -49,10 +49,7 @@ class TwoFactorForm(Form): Regexp(regex=verify_code, message='Code must be 5 digits')]) def validate_sms_code(self, a): - codes = verify_codes_dao.get_codes(session['user_id'], 'sms') - for code in codes: - if validate_code(self.sms_code, code): - return True + return validate_codes(self.sms_code, 'sms') class VerifyForm(Form): @@ -64,30 +61,10 @@ class VerifyForm(Form): Regexp(regex=verify_code, message='Code must be 5 digits')]) def validate_email_code(self, a): - codes = verify_codes_dao.get_codes(session['user_id'], 'email') - for code in codes: - if validate_code(self.email_code, code): - return True + return validate_codes(self.email_code, 'email') def validate_sms_code(self, a): - codes = verify_codes_dao.get_codes(session['user_id'], 'sms') - for code in codes: - if validate_code(self.sms_code, code): - return True - - -def validate_code(field, code): - if code.expiry_datetime <= datetime.now(): - field.errors.append('Code has expired') - return False - if field.data is not None: - if check_hash(field.data, code.code) is False: - field.errors.append('Code does not match') - return False - else: - return True - else: - return False + return validate_codes(self.sms_code, 'sms') class EmailNotReceivedForm(Form): @@ -114,3 +91,25 @@ class AddServiceForm(Form): return False else: return True + + +def validate_codes(field, code_type): + codes = verify_codes_dao.get_codes(user_id=session['user_id'], code_type=code_type) + for code in codes: + if validate_code(field, code): + if code.expiry_datetime <= datetime.now(): + field.errors.append('Code has expired') + return False + return True + field.errors.append('Code does not match') + return False + + +def validate_code(field, code): + if field.data is not None: + if check_hash(field.data, code.code) is False: + return False + else: + return True + else: + return False diff --git a/tests/app/main/test_verify_form.py b/tests/app/main/test_verify_form.py index 35425cb23..a72d18fd4 100644 --- a/tests/app/main/test_verify_form.py +++ b/tests/app/main/test_verify_form.py @@ -86,6 +86,22 @@ def test_should_return_errors_when_code_is_expired(notifications_admin, notifica assert set(errors) == set(expected) +def test_should_return_valid_form_when_many_codes_exist(notifications_admin, + notifications_admin_db, + notify_db_session): + with notifications_admin.test_request_context(method='POST', + data={'sms_code': '23456', + 'email_code': '23456'}) as req: + user = set_up_test_data() + verify_codes_dao.add_code(user_id=user.id, code='23456', code_type='email') + verify_codes_dao.add_code(user_id=user.id, code='23456', code_type='sms') + verify_codes_dao.add_code(user_id=user.id, code='60456', code_type='email') + verify_codes_dao.add_code(user_id=user.id, code='27856', code_type='sms') + req.session['user_id'] = user.id + form = VerifyForm(req.request.form) + assert form.validate() is True + + def set_up_test_data(): user = create_test_user('pending') verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='email') diff --git a/tests/app/main/views/test_verify.py b/tests/app/main/views/test_verify.py index a905420f1..9398dff47 100644 --- a/tests/app/main/views/test_verify.py +++ b/tests/app/main/views/test_verify.py @@ -56,3 +56,24 @@ def test_should_return_400_when_codes_are_wrong(notifications_admin, notificatio errors = json.loads(response.get_data(as_text=True)) assert len(errors) == 2 assert set(errors) == set(expected) + + +def test_should_mark_all_codes_as_used_when_many_codes_exist(notifications_admin, + notifications_admin_db, + notify_db_session): + with notifications_admin.test_client() as client: + with client.session_transaction() as session: + user = create_test_user('pending') + session['user_id'] = user.id + code1 = verify_codes_dao.add_code(user_id=user.id, code='23345', code_type='sms') + code2 = verify_codes_dao.add_code(user_id=user.id, code='98456', code_type='email') + code3 = verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') + code4 = verify_codes_dao.add_code(user_id=user.id, code='23412', code_type='email') + response = client.post('/verify', + data={'sms_code': '23345', + 'email_code': '23412'}) + assert response.status_code == 302 + assert verify_codes_dao.get_code_by_id(code1).code_used is True + assert verify_codes_dao.get_code_by_id(code2).code_used is True + assert verify_codes_dao.get_code_by_id(code3).code_used is True + assert verify_codes_dao.get_code_by_id(code4).code_used is True From fe8a1a10c586c0b1c2fd15b60459b1026ccbfd04 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Thu, 17 Dec 2015 15:09:12 +0000 Subject: [PATCH 5/6] 109898688: Refactor the validate codes logic to be more susinct and easier to read. --- app/main/forms.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/app/main/forms.py b/app/main/forms.py index eddc61bf6..f73ac0ef2 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -95,21 +95,18 @@ class AddServiceForm(Form): def validate_codes(field, code_type): codes = verify_codes_dao.get_codes(user_id=session['user_id'], code_type=code_type) - for code in codes: - if validate_code(field, code): - if code.expiry_datetime <= datetime.now(): - field.errors.append('Code has expired') - return False - return True - field.errors.append('Code does not match') - return False + is_valid = len([code for code in codes if validate_code(field, code)]) == 1 + if is_valid: + field.errors.clear() + return is_valid def validate_code(field, code): - if field.data is not None: - if check_hash(field.data, code.code) is False: + if field.data and check_hash(field.data, code.code): + if code.expiry_datetime <= datetime.now(): + field.errors.append('Code has expired') return False - else: - return True + return True else: + field.errors.append('Code does not match') return False From e38df7cda1c3e81218d1a532de3dbd3fa8c05808 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Thu, 17 Dec 2015 16:16:12 +0000 Subject: [PATCH 6/6] 109898688: Fix unit test --- tests/app/main/dao/test_verify_codes_dao.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/app/main/dao/test_verify_codes_dao.py b/tests/app/main/dao/test_verify_codes_dao.py index 1b95da1f5..de9bb404b 100644 --- a/tests/app/main/dao/test_verify_codes_dao.py +++ b/tests/app/main/dao/test_verify_codes_dao.py @@ -3,7 +3,7 @@ from pytest import fail from app.main.dao import verify_codes_dao from app.main.encryption import check_hash -from tests.app.main import create_test_user +from tests.app.main import create_test_user, create_another_test_user def test_insert_new_code_and_get_it_back(notifications_admin, notifications_admin_db, notify_db_session): @@ -68,7 +68,7 @@ def test_should_return_all_unused_code_when_there_are_many(notifications_admin, notifications_admin_db, notify_db_session): user = create_test_user('pending') - another_user = create_test_user('active') + another_user = create_another_test_user('active') verify_codes_dao.add_code(user_id=user.id, code='12345', code_type='sms') id = verify_codes_dao.add_code(user_id=user.id, code='09876', code_type='email') verify_codes_dao.use_code(id)