From 9e2cf2fa4cf9fcfefc6442612f7a34f03a1c7ca1 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Tue, 1 Dec 2015 13:23:54 +0000 Subject: [PATCH 01/29] 108536366: Implement register flow Includes validation for gov.uk email address, mobile number with +44, password at least 10 char. Form validation errors will be added to template in a later story. User is created when form validates. --- app/main/views/register.py | 2 +- tests/conftest.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/main/views/register.py b/app/main/views/register.py index 4892832ea..0c8f1ccbf 100644 --- a/app/main/views/register.py +++ b/app/main/views/register.py @@ -28,6 +28,6 @@ def process_register(): users_dao.insert_user(user) return redirect('/two-factor') except Exception as e: - return jsonify(database_error='encountered database error'), 400 + return jsonify(database_error=e.message), 400 else: return jsonify(form.errors), 400 diff --git a/tests/conftest.py b/tests/conftest.py index 338e4d63b..8712733e3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import pytest +from _pytest.monkeypatch import monkeypatch from sqlalchemy.schema import MetaData, DropConstraint from app import create_app, db From 3b96b6e5ca196ba26ec2894d691995b46d3df862 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Tue, 1 Dec 2015 15:51:09 +0000 Subject: [PATCH 02/29] 108536374: Implement a validator to exclude passwords on a blacklist --- app/main/forms.py | 5 ++++- app/main/validators.py | 12 ++++++++++++ tests/app/main/test_validators.py | 17 +++++++++++++++++ tests/app/main/views/test_register.py | 11 +++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 app/main/validators.py create mode 100644 tests/app/main/test_validators.py diff --git a/app/main/forms.py b/app/main/forms.py index bc013fccc..9cb3fe6de 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -2,6 +2,8 @@ from flask_wtf import Form from wtforms import StringField, PasswordField from wtforms.validators import DataRequired, Email, Length, Regexp +from app.main.validators import Blacklist + class LoginForm(Form): email_address = StringField('Email address', validators=[ @@ -32,4 +34,5 @@ class RegisterUserForm(Form): Regexp(regex=mobile_number, message='Please enter a +44 mobile number')]) password = PasswordField('Password', validators=[DataRequired(message='Please enter your password'), - Length(10, 255, message='Password must be at least 10 characters')]) + Length(10, 255, message='Password must be at least 10 characters'), + Blacklist(message='That password is blacklisted, too common')]) diff --git a/app/main/validators.py b/app/main/validators.py new file mode 100644 index 000000000..2638bd5e2 --- /dev/null +++ b/app/main/validators.py @@ -0,0 +1,12 @@ +from wtforms import ValidationError + + +class Blacklist(object): + def __init__(self, message=None): + if not message: + message = 'Password is blacklisted.' + self.message = message + + def __call__(self, form, field): + if field.data in ['password1234', 'passw0rd1234']: + raise ValidationError(self.message) diff --git a/tests/app/main/test_validators.py b/tests/app/main/test_validators.py new file mode 100644 index 000000000..a1cc52192 --- /dev/null +++ b/tests/app/main/test_validators.py @@ -0,0 +1,17 @@ +from pytest import fail + +from app.main.forms import RegisterUserForm + + +def test_should_raise_validation_error_for_password(notifications_admin): + form = RegisterUserForm() + form.name.data = 'test' + form.email_address.data = 'teset@example.gov.uk' + form.mobile_number.data = '+441231231231' + form.password.data = 'password1234' + + try: + form.validate() + fail() + except: + assert 'That password is blacklisted, too common' in form.errors['password'] diff --git a/tests/app/main/views/test_register.py b/tests/app/main/views/test_register.py index 2c6e384a0..ccafc9629 100644 --- a/tests/app/main/views/test_register.py +++ b/tests/app/main/views/test_register.py @@ -37,3 +37,14 @@ def test_should_return_400_when_email_is_not_gov_uk(notifications_admin, notific assert response.status_code == 400 assert 'Please enter a gov.uk email address' in response.get_data(as_text=True) + + +def test_should_return_400_if_password_is_blacklisted(notifications_admin, notifications_admin_db): + response = notifications_admin.test_client().post('/register', + data={'name': 'Bad Mobile', + 'email_address': 'bad_mobile@example.not.right', + 'mobile_number': '+44123412345', + 'password': 'password'}) + + response.status_code == 400 + assert 'That password is blacklisted, too common' in response.get_data(as_text=True) \ No newline at end of file From 9d9b80bab7834aaaa50cfd570a14ff9281515320 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Tue, 1 Dec 2015 16:40:19 +0000 Subject: [PATCH 03/29] Login user after they register --- app/main/views/register.py | 2 ++ tests/app/main/views/test_register.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/main/views/register.py b/app/main/views/register.py index 0c8f1ccbf..1f426de40 100644 --- a/app/main/views/register.py +++ b/app/main/views/register.py @@ -1,6 +1,7 @@ from datetime import datetime from flask import render_template, redirect, jsonify +from flask_login import login_user from app.main import main from app.main.dao import users_dao @@ -26,6 +27,7 @@ def process_register(): role_id=1) try: users_dao.insert_user(user) + login_user(user) return redirect('/two-factor') except Exception as e: return jsonify(database_error=e.message), 400 diff --git a/tests/app/main/views/test_register.py b/tests/app/main/views/test_register.py index ccafc9629..32da075c2 100644 --- a/tests/app/main/views/test_register.py +++ b/tests/app/main/views/test_register.py @@ -44,7 +44,7 @@ def test_should_return_400_if_password_is_blacklisted(notifications_admin, notif data={'name': 'Bad Mobile', 'email_address': 'bad_mobile@example.not.right', 'mobile_number': '+44123412345', - 'password': 'password'}) + 'password': 'password1234'}) response.status_code == 400 - assert 'That password is blacklisted, too common' in response.get_data(as_text=True) \ No newline at end of file + assert 'That password is blacklisted, too common' in response.get_data(as_text=True) From f6967a8f2368dc38fd97be809cd44489116e04b5 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Wed, 2 Dec 2015 11:56:59 +0000 Subject: [PATCH 04/29] Amend line of text --- app/templates/register.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/register.html b/app/templates/register.html index 7a8f41f8b..e15bc137e 100644 --- a/app/templates/register.html +++ b/app/templates/register.html @@ -12,7 +12,7 @@ GOV.UK Notify | Create an account

If you've used GOV.UK Notify before, sign in to your account.

-

You need to have access to your email account and a mobile phone to register.

+

You need access to your email account and a mobile phone to register.

{{ form.hidden_tag() }} From 058d7c5f9642ebff89b9e0c2e8eee64b48d70be5 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Wed, 2 Dec 2015 13:57:03 +0000 Subject: [PATCH 05/29] Change password label --- app/main/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/main/forms.py b/app/main/forms.py index bc013fccc..44ee24c1a 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -9,7 +9,7 @@ class LoginForm(Form): DataRequired(message='Email cannot be empty'), Email(message='Please enter a valid email address') ]) - password = PasswordField('Password', validators=[ + password = PasswordField('Create a password', validators=[ DataRequired(message='Please enter your password') ]) From 571f09881e518b0d77679fbfe1949879fc0eeefa Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Wed, 2 Dec 2015 15:23:03 +0000 Subject: [PATCH 06/29] Amend name & password labels --- app/main/forms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/main/forms.py b/app/main/forms.py index 44ee24c1a..61f2f4e45 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -9,7 +9,7 @@ class LoginForm(Form): DataRequired(message='Email cannot be empty'), Email(message='Please enter a valid email address') ]) - password = PasswordField('Create a password', validators=[ + password = PasswordField('Password', validators=[ DataRequired(message='Please enter your password') ]) @@ -19,7 +19,7 @@ mobile_number = "^\\+44[\\d]{10}$" class RegisterUserForm(Form): - name = StringField('Name', + name = StringField('Full name', validators=[DataRequired(message='Name can not be empty')]) email_address = StringField('Email address', validators=[ Length(min=5, max=255), @@ -30,6 +30,6 @@ class RegisterUserForm(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')]) - password = PasswordField('Password', + password = PasswordField('Create a password', validators=[DataRequired(message='Please enter your password'), Length(10, 255, message='Password must be at least 10 characters')]) From f4140ea49b8abceed761b956dfc0fc2e03f072d5 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Wed, 2 Dec 2015 15:32:21 +0000 Subject: [PATCH 07/29] Amend line of text --- app/templates/email-not-received.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/email-not-received.html b/app/templates/email-not-received.html index 462fbb961..758589199 100644 --- a/app/templates/email-not-received.html +++ b/app/templates/email-not-received.html @@ -10,7 +10,7 @@ GOV.UK Notify

Check your email address

-

Check your email address is correct and resend a confirmation code.

+

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

@@ -25,4 +25,4 @@ GOV.UK Notify

-{% endblock %} \ No newline at end of file +{% endblock %} From 71f9be2b7e4fb68524b22a290c7a453ca7a9af65 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Wed, 2 Dec 2015 15:33:24 +0000 Subject: [PATCH 08/29] Amend line of text --- app/templates/text-not-received.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/text-not-received.html b/app/templates/text-not-received.html index ce8fff18c..4b332c894 100644 --- a/app/templates/text-not-received.html +++ b/app/templates/text-not-received.html @@ -10,7 +10,7 @@ GOV.UK Notify

Check your mobile number

-

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

+

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

@@ -24,4 +24,4 @@ GOV.UK Notify

-{% endblock %} \ No newline at end of file +{% endblock %} From 47a7b79b21469086db06dba559ef6e1dc3614b6a Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Wed, 2 Dec 2015 15:44:07 +0000 Subject: [PATCH 09/29] Amend heading text New heading corresponds to design pattern for confirming accounts --- app/templates/verify.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/verify.html b/app/templates/verify.html index 729f099c2..2aeec8418 100644 --- a/app/templates/verify.html +++ b/app/templates/verify.html @@ -8,10 +8,10 @@ GOV.UK Notify | Confirm email address and mobile number
-

Confirm your email address and mobile number

+

Activate your account

You need to prove the contact details you gave us are yours.

-

We've sent you an email and a text message containing confirmation codes.

+

We've sent you confirmation codes in an email and a text message.

Activate your account

You need to prove the contact details you gave us are yours.

-

We've sent you confirmation codes in an email and a text message.

+

We've sent you confirmation codes by email and text message. You need to enter both codes here.

+ + BETA + +
+ {% endblock %} + {% set global_header_text = "GOV.UK Notify" %} From 15e5d8f1456adb0db577ef5a0798c8a6356d5e63 Mon Sep 17 00:00:00 2001 From: Chris Heathcote Date: Thu, 3 Dec 2015 16:01:21 +0000 Subject: [PATCH 12/29] Fixed flask-assets to look for css changes and rebuild --- app/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index fc5c73b8e..c0c0cd758 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -96,7 +96,8 @@ def init_asset_environment(app): assets.Bundle( 'govuk_template/govuk-template.scss', filters='scss', - output='stylesheets/govuk-template.css' + output='stylesheets/govuk-template.css', + depends='*.scss' ) ) From 437e4f52bd57282b5aa73a8209780b32e2c1b841 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 12:47:23 +0000 Subject: [PATCH 13/29] Amend intro and sign-in text --- app/templates/signedout.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/signedout.html b/app/templates/signedout.html index d2beafabe..c1e6b26a0 100644 --- a/app/templates/signedout.html +++ b/app/templates/signedout.html @@ -12,13 +12,13 @@ GOV.UK Notify | Get started

Use GOV.UK Notify to send notifications by text message, email and letter.

We're making it easy to keep your users informed.

-

If you work for a central UK Government department or agency you can set up a test account now.

+

If you work for a UK government department or agency you can set up a test account now.

Set up an account -

Sign in

+

If you've used GOV.UK Notify before, sign in to your account

From 8ade276c3c2a0e6ae246c80f719b17f8f570a92a Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 12:49:38 +0000 Subject: [PATCH 14/29] Amend intro and sign-in text --- app/templates/signedout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/signedout.html b/app/templates/signedout.html index c1e6b26a0..49d5d6be8 100644 --- a/app/templates/signedout.html +++ b/app/templates/signedout.html @@ -18,7 +18,7 @@ GOV.UK Notify | Get started -

If you've used GOV.UK Notify before, sign in to your account

+

If you've used GOV.UK Notify before, sign in to your account.

From 7c3ff23ee22fd2f0c83d0ae81f9fdc0aba642199 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 12:51:06 +0000 Subject: [PATCH 15/29] Amend intro text and add password prompt --- app/templates/register.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/templates/register.html b/app/templates/register.html index e15bc137e..99c585714 100644 --- a/app/templates/register.html +++ b/app/templates/register.html @@ -12,9 +12,7 @@ GOV.UK Notify | Create an account

If you've used GOV.UK Notify before, sign in to your account.

-

You need access to your email account and a mobile phone to register.

- - + {{ form.hidden_tag() }}

@@ -32,6 +30,7 @@ GOV.UK Notify | Create an account

{{ form.password(class="form-control-1-4", autocomplete="off") }}
+ Your password must have at least 10 characters

From 0af88bb99fe2bdb5c8d98ccf9a4de053f9cfc249 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 12:53:25 +0000 Subject: [PATCH 16/29] Amend intro text & delete email/phone prompts --- app/templates/verify.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/templates/verify.html b/app/templates/verify.html index 42235ee56..a6ab5b8f5 100644 --- a/app/templates/verify.html +++ b/app/templates/verify.html @@ -10,18 +10,15 @@ GOV.UK Notify | Confirm email address and mobile number

Activate your account

-

You need to prove the contact details you gave us are yours.

We've sent you confirmation codes by email and text message. You need to enter both codes here.


I haven't received an email


I haven't received a text

From b2cae0d1628d6e61094d2d9e859f9b1f6db904a3 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 14:46:01 +0000 Subject: [PATCH 17/29] Amend text --- app/templates/add-service.html | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/templates/add-service.html b/app/templates/add-service.html index dd2f3b1a0..0924f35aa 100644 --- a/app/templates/add-service.html +++ b/app/templates/add-service.html @@ -9,6 +9,12 @@ GOV.UK Notify | Set up service

Set up notifications for your service

+ +

Users will see your service name:

+
    +
  • at the start of every text message, eg 'Vehicle tax: we received your payment, thank you'
  • +
  • as your email sender name
  • +

@@ -17,11 +23,6 @@ GOV.UK Notify | Set up service For example, 'Vehicle tax' or 'Carer's allowance'

-

We'll create your service in test mode

-

In test mode you can only send notifications to people in your team.

-

When you're ready to go live we'll remove this restriction.

- -

Continue

From 7836241f488557670b358a0e9365c608aceeb566 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 14:50:23 +0000 Subject: [PATCH 18/29] Amend text --- app/templates/signin.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/signin.html b/app/templates/signin.html index b47f53475..c116cd7e5 100644 --- a/app/templates/signin.html +++ b/app/templates/signin.html @@ -10,7 +10,7 @@ Sign in

Sign in

-

If you do not have an account, you can register.

+

If you do not have an account, you can register for one.

{{ form.hidden_tag() }} @@ -33,4 +33,4 @@ Sign in
-{% endblock %} \ No newline at end of file +{% endblock %} From 9be1612c0942da1d2ae965fa0bfa171066432b0c Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 14:54:40 +0000 Subject: [PATCH 19/29] Amend text --- app/templates/two-factor.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/templates/two-factor.html b/app/templates/two-factor.html index 293679b0c..9f23831af 100644 --- a/app/templates/two-factor.html +++ b/app/templates/two-factor.html @@ -10,11 +10,10 @@ GOV.UK Notify | Text verification

Text verification

-

We've sent you a text message containing a verification code.

+

We've sent you a text message with a verification code.

- +

From dc313a601cb81dac3f8d677565e10a9fc0ca2922 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 14:56:23 +0000 Subject: [PATCH 20/29] Amend text --- app/templates/verification-not-received.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/templates/verification-not-received.html b/app/templates/verification-not-received.html index b21b16850..c151f7b21 100644 --- a/app/templates/verification-not-received.html +++ b/app/templates/verification-not-received.html @@ -10,15 +10,15 @@ GOV.UK Notify

Resend verification code

-

Text messages sometimes take a few minutes to be received.
If you have not received a text message, you can resend.

+

Text messages sometimes take a few minutes to arrive, but if you do not receive a text message, you can resend it.

-

If you no longer have access to your mobile phone and registered number, get in contact with your service manager to reset your number.

P> +

If you no longer have access to the phone with the number you registered for this service, speak to your service manager to reset the number.

- Resend confirmation code + Resend verification code

-{% endblock %} \ No newline at end of file +{% endblock %} From 3c99eb2d3aa827ac8ffad5740eea11bac4a12822 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 14:57:16 +0000 Subject: [PATCH 21/29] Amend text --- app/templates/verification-not-received.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/verification-not-received.html b/app/templates/verification-not-received.html index c151f7b21..de1d9204f 100644 --- a/app/templates/verification-not-received.html +++ b/app/templates/verification-not-received.html @@ -10,7 +10,7 @@ GOV.UK Notify

Resend verification code

-

Text messages sometimes take a few minutes to arrive, but if you do not receive a text message, you can resend it.

+

Text messages sometimes take a few minutes to arrive. If you do not receive the text message, you can resend it.

If you no longer have access to the phone with the number you registered for this service, speak to your service manager to reset the number.

From e8839c3300d8b417036a4ef7411f62773dc61316 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 15:02:00 +0000 Subject: [PATCH 22/29] Amend text --- app/templates/new-password.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/templates/new-password.html b/app/templates/new-password.html index 0d2c811f7..f64c68eeb 100644 --- a/app/templates/new-password.html +++ b/app/templates/new-password.html @@ -9,10 +9,13 @@ GOV.UK Notify

Create a new password

+ +

You can now create a new password for your account.

- + + Your password must have at least 10 characters

@@ -21,4 +24,4 @@ GOV.UK Notify

-{% endblock %} \ No newline at end of file +{% endblock %} From aadad35d715a1634edac2033591fead19d3f7794 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 15:05:51 +0000 Subject: [PATCH 23/29] Amend text and add password prompt --- app/templates/register-from-invite.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/templates/register-from-invite.html b/app/templates/register-from-invite.html index 73169cc06..c1565d7f9 100644 --- a/app/templates/register-from-invite.html +++ b/app/templates/register-from-invite.html @@ -13,7 +13,7 @@ GOV.UK Notify | Create a user account

If you've used GOV.UK Notify before, sign in to your account.

- +

@@ -21,8 +21,9 @@ GOV.UK Notify | Create a user account

- + + Your password must have at least 10 characters

From 30f55321b28e4e3923a27bf02db90c5fe19c07b9 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 15:07:21 +0000 Subject: [PATCH 24/29] Amend text --- app/templates/verify-mobile.html | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/templates/verify-mobile.html b/app/templates/verify-mobile.html index 10a82b3d5..ae66bbecf 100644 --- a/app/templates/verify-mobile.html +++ b/app/templates/verify-mobile.html @@ -10,12 +10,10 @@ GOV.UK Notify | Confirm mobile number

Confirm your mobile number

-

You need to prove the contact details you gave us are yours.

-

We've sent you an email and a text message containing confirmation codes.

+

We've sent you a confirmation code by text message.

- +

From 619df26690c5c24137767b641be785343ec136e0 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 15:08:41 +0000 Subject: [PATCH 25/29] Amend text --- app/templates/text-not-received-2.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/text-not-received-2.html b/app/templates/text-not-received-2.html index c5ffb8e8e..ea6e87eca 100644 --- a/app/templates/text-not-received-2.html +++ b/app/templates/text-not-received-2.html @@ -12,7 +12,7 @@ GOV.UK Notify

Check your mobile number

-

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

+

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

@@ -26,4 +26,4 @@ GOV.UK Notify

-{% endblock %} \ No newline at end of file +{% endblock %} From e9c9b8c271336724180ee5eecf665b6029ad9e98 Mon Sep 17 00:00:00 2001 From: Lorena Sutherland Date: Fri, 4 Dec 2015 15:12:48 +0000 Subject: [PATCH 26/29] Amend text --- app/templates/signin.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/signin.html b/app/templates/signin.html index c116cd7e5..0c3d34531 100644 --- a/app/templates/signin.html +++ b/app/templates/signin.html @@ -10,7 +10,7 @@ Sign in

Sign in

-

If you do not have an account, you can register for one.

+

If you do not have an account, you can register for one now.

{{ form.hidden_tag() }} From a741c128da0217ee3109baf0cb84fb92c8aa11ad Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Fri, 4 Dec 2015 14:40:16 +0000 Subject: [PATCH 27/29] 108537814: Implementation of 3 factor authentication. The post register endpoint will send a random 5 digit code via sms and another via email. If either code fails to send, the user will not be created and the person can register again. The codes are saved to the session cookie, and expire in 1 hour. Another iteration of this story will save the codes to a database. --- app/__init__.py | 4 +++ app/main/exceptions.py | 5 +++ app/main/views/index.py | 4 --- app/main/views/register.py | 51 +++++++++++++++++++++++---- app/notify_client/__init__.py | 0 app/notify_client/api_client.py | 8 +++++ config.py | 10 ++++++ requirements.txt | 4 ++- requirements_for_test.txt | 3 +- tests/app/main/views/test_register.py | 31 +++++++++++++--- 10 files changed, 103 insertions(+), 17 deletions(-) create mode 100644 app/main/exceptions.py create mode 100644 app/notify_client/__init__.py create mode 100644 app/notify_client/api_client.py diff --git a/app/__init__.py b/app/__init__.py index c0c0cd758..98fd560b9 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -9,6 +9,7 @@ from flask_wtf import CsrfProtect from webassets.filter import get_filter from werkzeug.exceptions import abort +from app.notify_client.api_client import AdminAPIClient from app.its_dangerous_session import ItsdangerousSessionInterface import app.proxy_fix from config import configs @@ -18,6 +19,8 @@ db = SQLAlchemy() login_manager = LoginManager() csrf = CsrfProtect() +admin_api_client = AdminAPIClient() + def create_app(config_name): application = Flask(__name__) @@ -37,6 +40,7 @@ def create_app(config_name): proxy_fix.init_app(application) application.session_interface = ItsdangerousSessionInterface() + admin_api_client.init_app(application) return application diff --git a/app/main/exceptions.py b/app/main/exceptions.py new file mode 100644 index 000000000..45f0515c0 --- /dev/null +++ b/app/main/exceptions.py @@ -0,0 +1,5 @@ + + +class AdminApiClientException(Exception): + def __init__(self, message): + self.value = message diff --git a/app/main/views/index.py b/app/main/views/index.py index 2ffe73da4..f664ee835 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -15,19 +15,16 @@ def govuk(): @main.route("/register-from-invite") -@login_required def registerfrominvite(): return render_template('register-from-invite.html') @main.route("/verify") -@login_required def verify(): return render_template('verify.html') @main.route("/verify-mobile") -@login_required def verifymobile(): return render_template('verify-mobile.html') @@ -50,7 +47,6 @@ def addservice(): @main.route("/two-factor") -@login_required def twofactor(): return render_template('two-factor.html') diff --git a/app/main/views/register.py b/app/main/views/register.py index 1f426de40..34f781157 100644 --- a/app/main/views/register.py +++ b/app/main/views/register.py @@ -1,10 +1,14 @@ -from datetime import datetime +from datetime import datetime, timedelta +from random import randint -from flask import render_template, redirect, jsonify -from flask_login import login_user +from flask import render_template, redirect, jsonify, session +from sqlalchemy.exc import SQLAlchemyError +from app import admin_api_client from app.main import main from app.main.dao import users_dao +from app.main.encryption import hashpw +from app.main.exceptions import AdminApiClientException from app.main.forms import RegisterUserForm from app.models import User @@ -26,10 +30,43 @@ def process_register(): created_at=datetime.now(), role_id=1) try: + sms_code = send_sms_code(form.mobile_number.data) + email_code = send_email_code(form.email_address.data) + session['sms_code'] = hashpw(sms_code) + session['email_code'] = hashpw(email_code) + session['expiry_date'] = str(datetime.now() + timedelta(hours=2)) users_dao.insert_user(user) - login_user(user) - return redirect('/two-factor') - except Exception as e: - return jsonify(database_error=e.message), 400 + except AdminApiClientException as e: + return jsonify(admin_api_client_error=e.value) + except SQLAlchemyError: + return jsonify(database_error='encountered database error'), 400 else: return jsonify(form.errors), 400 + return redirect('/verify') + + +def send_sms_code(mobile_number): + sms_code = _create_code() + try: + admin_api_client.send_sms(mobile_number, message=sms_code, token=admin_api_client.auth_token) + except: + raise AdminApiClientException('Exception when sending sms.') + return sms_code + + +def send_email_code(email): + email_code = _create_code() + try: + admin_api_client.send_email(email_address=email, + from_str='notify@digital.cabinet-office.gov.uk', + message=email_code, + subject='Verification code', + token=admin_api_client.auth_token) + except: + raise AdminApiClientException('Exception when sending email.') + + return email_code + + +def _create_code(): + return ''.join(["%s" % randint(0, 9) for _ in range(0, 5)]) diff --git a/app/notify_client/__init__.py b/app/notify_client/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/notify_client/api_client.py b/app/notify_client/api_client.py new file mode 100644 index 000000000..e32e8e37e --- /dev/null +++ b/app/notify_client/api_client.py @@ -0,0 +1,8 @@ +from __future__ import unicode_literals +from notify_client import NotifyAPIClient + + +class AdminAPIClient(NotifyAPIClient): + def init_app(self, app): + self.base_url = app.config['NOTIFY_DATA_API_URL'] + self.auth_token = app.config['NOTIFY_DATA_API_AUTH_TOKEN'] diff --git a/config.py b/config.py index 6c799b561..f373ed27a 100644 --- a/config.py +++ b/config.py @@ -1,3 +1,5 @@ +import os + class Config(object): DEBUG = False @@ -11,6 +13,14 @@ class Config(object): MAX_FAILED_LOGIN_COUNT = 10 PASS_SECRET_KEY = 'secret-key-unique-changeme' + SESSION_COOKIE_NAME = 'notify_admin_session' + SESSION_COOKIE_PATH = '/admin' + SESSION_COOKIE_HTTPONLY = True + SESSION_COOKIE_SECURE = True + + NOTIFY_DATA_API_URL = os.getenv('NOTIFY_API_URL', "http://localhost:6001") + NOTIFY_DATA_API_AUTH_TOKEN = os.getenv('NOTIFY_API_TOKEN', "pLuj5kat5auC9Ve") + WTF_CSRF_ENABLED = True SECRET_KEY = 'secret-key' HTTP_PROTOCOL = 'http' diff --git a/requirements.txt b/requirements.txt index 9ac4a3971..aaa53cebc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,6 @@ SQLAlchemy==1.0.5 SQLAlchemy-Utils==0.30.5 Flask-WTF==0.11 Flask-Login==0.2.11 -Flask-Bcrypt==0.6.2 \ No newline at end of file +Flask-Bcrypt==0.6.2 + +git+https://github.com/alphagov/notify-api-client.git@0.1.4#egg=notify-api-client==0.1.4 \ No newline at end of file diff --git a/requirements_for_test.txt b/requirements_for_test.txt index f8aeacc70..ebea6362b 100644 --- a/requirements_for_test.txt +++ b/requirements_for_test.txt @@ -1,3 +1,4 @@ -r requirements.txt pep8==1.5.7 -pytest==2.8.1 \ No newline at end of file +pytest==2.8.1 +pytest-mock==0.8.1 diff --git a/tests/app/main/views/test_register.py b/tests/app/main/views/test_register.py index 32da075c2..a40b93481 100644 --- a/tests/app/main/views/test_register.py +++ b/tests/app/main/views/test_register.py @@ -7,17 +7,22 @@ def test_render_register_returns_template_with_form(notifications_admin, notific assert 'Create an account' in response.get_data(as_text=True) -def test_process_register_creates_new_user(notifications_admin, notifications_admin_db): +def test_process_register_creates_new_user(notifications_admin, notifications_admin_db, mocker): + _set_up_mocker(mocker) + response = notifications_admin.test_client().post('/register', data={'name': 'Some One Valid', 'email_address': 'someone@example.gov.uk', 'mobile_number': '+441231231231', 'password': 'validPassword!'}) assert response.status_code == 302 - assert response.location == 'http://localhost/two-factor' + assert response.location == 'http://localhost/verify' -def test_process_register_returns_400_when_mobile_number_is_invalid(notifications_admin, notifications_admin_db): +def test_process_register_returns_400_when_mobile_number_is_invalid(notifications_admin, + notifications_admin_db, + mocker): + _set_up_mocker(mocker) response = notifications_admin.test_client().post('/register', data={'name': 'Bad Mobile', 'email_address': 'bad_mobile@example.gov.uk', @@ -28,7 +33,8 @@ def test_process_register_returns_400_when_mobile_number_is_invalid(notification assert 'Please enter a +44 mobile number' in response.get_data(as_text=True) -def test_should_return_400_when_email_is_not_gov_uk(notifications_admin, notifications_admin_db): +def test_should_return_400_when_email_is_not_gov_uk(notifications_admin, notifications_admin_db, mocker): + _set_up_mocker(mocker) response = notifications_admin.test_client().post('/register', data={'name': 'Bad Mobile', 'email_address': 'bad_mobile@example.not.right', @@ -39,6 +45,23 @@ def test_should_return_400_when_email_is_not_gov_uk(notifications_admin, notific assert 'Please enter a gov.uk email address' in response.get_data(as_text=True) +def test_should_add_verify_codes_on_session(notifications_admin, notifications_admin_db, mocker): + _set_up_mocker(mocker) + with notifications_admin.test_client() as client: + response = client.post('/register', + data={'name': 'Test Codes', + 'email_address': 'test_codes@example.gov.uk', + 'mobile_number': '+441234567890', + 'password': 'validPassword!'}) + assert response.status_code == 302 + assert 'notify_admin_session' in response.headers.get('Set-Cookie') + + +def _set_up_mocker(mocker): + mocker.patch("app.admin_api_client.send_sms") + mocker.patch("app.admin_api_client.send_email") + + def test_should_return_400_if_password_is_blacklisted(notifications_admin, notifications_admin_db): response = notifications_admin.test_client().post('/register', data={'name': 'Bad Mobile', From 29354859772e1debcecadf0d47cf3bfd7c89a94c Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Fri, 4 Dec 2015 14:42:29 +0000 Subject: [PATCH 28/29] 108537814: Set session expiry to 1 hour --- app/main/views/register.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/main/views/register.py b/app/main/views/register.py index 34f781157..48c9d68fa 100644 --- a/app/main/views/register.py +++ b/app/main/views/register.py @@ -34,7 +34,7 @@ def process_register(): email_code = send_email_code(form.email_address.data) session['sms_code'] = hashpw(sms_code) session['email_code'] = hashpw(email_code) - session['expiry_date'] = str(datetime.now() + timedelta(hours=2)) + session['expiry_date'] = str(datetime.now() + timedelta(hours=1)) users_dao.insert_user(user) except AdminApiClientException as e: return jsonify(admin_api_client_error=e.value) From 7c7d0701dddf54006ba87e3e785caab6e53bd68a Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Fri, 4 Dec 2015 16:22:57 +0000 Subject: [PATCH 29/29] Use a dev api token --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index f373ed27a..0bc3a4c4a 100644 --- a/config.py +++ b/config.py @@ -19,7 +19,7 @@ class Config(object): SESSION_COOKIE_SECURE = True NOTIFY_DATA_API_URL = os.getenv('NOTIFY_API_URL', "http://localhost:6001") - NOTIFY_DATA_API_AUTH_TOKEN = os.getenv('NOTIFY_API_TOKEN', "pLuj5kat5auC9Ve") + NOTIFY_DATA_API_AUTH_TOKEN = os.getenv('NOTIFY_API_TOKEN', "dev-token") WTF_CSRF_ENABLED = True SECRET_KEY = 'secret-key'