mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-11 18:37:33 -04:00
Merge branch 'master' of https://github.com/alphagov/notifications-admin into vb-free-sms-history
This commit is contained in:
+27
-5
@@ -5,6 +5,7 @@ from time import monotonic
|
|||||||
|
|
||||||
import itertools
|
import itertools
|
||||||
import ago
|
import ago
|
||||||
|
from itsdangerous import BadSignature
|
||||||
from flask import (
|
from flask import (
|
||||||
Flask,
|
Flask,
|
||||||
session,
|
session,
|
||||||
@@ -13,7 +14,8 @@ from flask import (
|
|||||||
current_app,
|
current_app,
|
||||||
request,
|
request,
|
||||||
g,
|
g,
|
||||||
url_for
|
url_for,
|
||||||
|
flash
|
||||||
)
|
)
|
||||||
from flask._compat import string_types
|
from flask._compat import string_types
|
||||||
from flask.globals import _lookup_req_object, _request_ctx_stack
|
from flask.globals import _lookup_req_object, _request_ctx_stack
|
||||||
@@ -404,10 +406,23 @@ def load_service_before_request():
|
|||||||
if '/static/' in request.url:
|
if '/static/' in request.url:
|
||||||
_request_ctx_stack.top.service = None
|
_request_ctx_stack.top.service = None
|
||||||
return
|
return
|
||||||
service_id = request.view_args.get('service_id', session.get('service_id')) if request.view_args \
|
|
||||||
else session.get('service_id')
|
|
||||||
if _request_ctx_stack.top is not None:
|
if _request_ctx_stack.top is not None:
|
||||||
_request_ctx_stack.top.service = service_api_client.get_service(service_id)['data'] if service_id else None
|
_request_ctx_stack.top.service = None
|
||||||
|
|
||||||
|
if request.view_args:
|
||||||
|
service_id = request.view_args.get('service_id', session.get('service_id'))
|
||||||
|
else:
|
||||||
|
service_id = session.get('service_id')
|
||||||
|
|
||||||
|
if service_id:
|
||||||
|
try:
|
||||||
|
_request_ctx_stack.top.service = service_api_client.get_service(service_id)['data']
|
||||||
|
except HTTPError as exc:
|
||||||
|
# if service id isn't real, then 404 rather than 500ing later because we expect service to be set
|
||||||
|
if exc.status_code == 404:
|
||||||
|
abort(404)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def save_service_after_request(response):
|
def save_service_after_request(response):
|
||||||
@@ -426,6 +441,7 @@ def useful_headers_after_request(response):
|
|||||||
response.headers.add('Content-Security-Policy', (
|
response.headers.add('Content-Security-Policy', (
|
||||||
"default-src 'self' 'unsafe-inline';"
|
"default-src 'self' 'unsafe-inline';"
|
||||||
"script-src 'self' *.google-analytics.com 'unsafe-inline' 'unsafe-eval' data:;"
|
"script-src 'self' *.google-analytics.com 'unsafe-inline' 'unsafe-eval' data:;"
|
||||||
|
"connect-src 'self' *.google-analytics.com;"
|
||||||
"object-src 'self';"
|
"object-src 'self';"
|
||||||
"font-src 'self' data:;"
|
"font-src 'self' data:;"
|
||||||
"img-src 'self' *.google-analytics.com *.notifications.service.gov.uk {} data:;"
|
"img-src 'self' *.google-analytics.com *.notifications.service.gov.uk {} data:;"
|
||||||
@@ -438,7 +454,7 @@ def useful_headers_after_request(response):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def register_errorhandlers(application):
|
def register_errorhandlers(application): # noqa (C901 too complex)
|
||||||
def _error_response(error_code):
|
def _error_response(error_code):
|
||||||
application.logger.exception('Admin app errored with %s', error_code)
|
application.logger.exception('Admin app errored with %s', error_code)
|
||||||
resp = make_response(render_template("error/{0}.html".format(error_code)), error_code)
|
resp = make_response(render_template("error/{0}.html".format(error_code)), error_code)
|
||||||
@@ -492,6 +508,12 @@ def register_errorhandlers(application):
|
|||||||
raise error
|
raise error
|
||||||
return _error_response(500)
|
return _error_response(500)
|
||||||
|
|
||||||
|
@application.errorhandler(BadSignature)
|
||||||
|
def handle_bad_token(error):
|
||||||
|
# if someone has a malformed token
|
||||||
|
flash('There’s something wrong with the link you’ve used.')
|
||||||
|
return _error_response(404)
|
||||||
|
|
||||||
|
|
||||||
def setup_event_handlers():
|
def setup_event_handlers():
|
||||||
from flask_login import user_logged_in
|
from flask_login import user_logged_in
|
||||||
|
|||||||
@@ -63,6 +63,11 @@
|
|||||||
right: -135px;
|
right: -135px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-hint {
|
||||||
|
color: #6F777B;
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -41,7 +41,8 @@ class Config(object):
|
|||||||
'local': 25000,
|
'local': 25000,
|
||||||
'nhs': 25000,
|
'nhs': 25000,
|
||||||
}
|
}
|
||||||
EMAIL_EXPIRY_SECONDS = 3600 * 24 * 7 # one week
|
EMAIL_EXPIRY_SECONDS = 3600 # 1 hour
|
||||||
|
INVITATION_EXPIRY_SECONDS = 3600 * 24 * 2 # 2 days - also set on api
|
||||||
HEADER_COLOUR = '#FFBF47' # $yellow
|
HEADER_COLOUR = '#FFBF47' # $yellow
|
||||||
HTTP_PROTOCOL = 'http'
|
HTTP_PROTOCOL = 'http'
|
||||||
MAX_FAILED_LOGIN_COUNT = 10
|
MAX_FAILED_LOGIN_COUNT = 10
|
||||||
@@ -56,7 +57,6 @@ class Config(object):
|
|||||||
SHOW_STYLEGUIDE = True
|
SHOW_STYLEGUIDE = True
|
||||||
# TODO: move to utils
|
# TODO: move to utils
|
||||||
SMS_CHAR_COUNT_LIMIT = 459
|
SMS_CHAR_COUNT_LIMIT = 459
|
||||||
TOKEN_MAX_AGE_SECONDS = 3600
|
|
||||||
WTF_CSRF_ENABLED = True
|
WTF_CSRF_ENABLED = True
|
||||||
WTF_CSRF_TIME_LIMIT = None
|
WTF_CSRF_TIME_LIMIT = None
|
||||||
CSV_UPLOAD_BUCKET_NAME = 'local-notifications-csv-upload'
|
CSV_UPLOAD_BUCKET_NAME = 'local-notifications-csv-upload'
|
||||||
|
|||||||
+20
-2
@@ -186,6 +186,14 @@ class PermissionsForm(Form):
|
|||||||
manage_templates = BooleanField("Add and edit templates")
|
manage_templates = BooleanField("Add and edit templates")
|
||||||
manage_service = BooleanField("Modify this service and its team")
|
manage_service = BooleanField("Modify this service and its team")
|
||||||
manage_api_keys = BooleanField("Create and revoke API keys")
|
manage_api_keys = BooleanField("Create and revoke API keys")
|
||||||
|
login_authentication = RadioField(
|
||||||
|
'Sign in using',
|
||||||
|
choices=[
|
||||||
|
('sms_auth', 'Text message code'),
|
||||||
|
('email_auth', 'Email link'),
|
||||||
|
],
|
||||||
|
validators=[DataRequired()]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InviteUserForm(PermissionsForm):
|
class InviteUserForm(PermissionsForm):
|
||||||
@@ -693,10 +701,10 @@ class ServiceInboundNumberForm(Form):
|
|||||||
|
|
||||||
|
|
||||||
class ServiceInboundApiForm(Form):
|
class ServiceInboundApiForm(Form):
|
||||||
url = StringField("Inbound sms url",
|
url = StringField("Callback URL",
|
||||||
validators=[DataRequired(message='Can’t be empty'),
|
validators=[DataRequired(message='Can’t be empty'),
|
||||||
Regexp(regex="^https.*",
|
Regexp(regex="^https.*",
|
||||||
message='Must be a valid https url')]
|
message='Must be a valid https URL')]
|
||||||
)
|
)
|
||||||
bearer_token = PasswordFieldShowHasContent("Bearer token",
|
bearer_token = PasswordFieldShowHasContent("Bearer token",
|
||||||
validators=[DataRequired(message='Can’t be empty'),
|
validators=[DataRequired(message='Can’t be empty'),
|
||||||
@@ -713,6 +721,16 @@ class InternationalSMSForm(Form):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SMSPrefixForm(Form):
|
||||||
|
enabled = RadioField(
|
||||||
|
'',
|
||||||
|
choices=[
|
||||||
|
('on', 'On'),
|
||||||
|
('off', 'Off'),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_placeholder_form_instance(
|
def get_placeholder_form_instance(
|
||||||
placeholder_name,
|
placeholder_name,
|
||||||
dict_to_populate_from,
|
dict_to_populate_from,
|
||||||
|
|||||||
@@ -135,3 +135,8 @@ def using_notify():
|
|||||||
@main.route('/information-risk-management')
|
@main.route('/information-risk-management')
|
||||||
def information_risk_management():
|
def information_risk_management():
|
||||||
return render_template('views/information-risk-management.html')
|
return render_template('views/information-risk-management.html')
|
||||||
|
|
||||||
|
|
||||||
|
@main.route('/callbacks')
|
||||||
|
def callbacks():
|
||||||
|
return render_template('views/callbacks.html')
|
||||||
|
|||||||
@@ -4,23 +4,37 @@ from flask import (
|
|||||||
session,
|
session,
|
||||||
flash,
|
flash,
|
||||||
render_template,
|
render_template,
|
||||||
abort
|
abort,
|
||||||
|
current_app
|
||||||
)
|
)
|
||||||
|
from itsdangerous import SignatureExpired
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
|
from notifications_utils.url_safe_token import check_token
|
||||||
|
from flask_login import current_user
|
||||||
|
|
||||||
from app.main import main
|
from app.main import main
|
||||||
|
|
||||||
from app import (
|
from app import (
|
||||||
invite_api_client,
|
invite_api_client,
|
||||||
user_api_client,
|
user_api_client,
|
||||||
service_api_client
|
service_api_client
|
||||||
)
|
)
|
||||||
|
|
||||||
from flask_login import current_user
|
|
||||||
|
|
||||||
|
|
||||||
@main.route("/invitation/<token>")
|
@main.route("/invitation/<token>")
|
||||||
def accept_invite(token):
|
def accept_invite(token):
|
||||||
|
try:
|
||||||
|
check_token(
|
||||||
|
token,
|
||||||
|
current_app.config['SECRET_KEY'],
|
||||||
|
current_app.config['DANGEROUS_SALT'],
|
||||||
|
current_app.config['INVITATION_EXPIRY_SECONDS']
|
||||||
|
)
|
||||||
|
except SignatureExpired:
|
||||||
|
errors = [
|
||||||
|
'Your invitation to GOV.UK Notify has expired. '
|
||||||
|
'Please ask the person that invited you to send you another one'
|
||||||
|
]
|
||||||
|
return render_template("error/400.html", message=errors), 400
|
||||||
|
|
||||||
invited_user = invite_api_client.check_token(token)
|
invited_user = invite_api_client.check_token(token)
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from app.main.forms import (
|
|||||||
InviteUserForm,
|
InviteUserForm,
|
||||||
PermissionsForm
|
PermissionsForm
|
||||||
)
|
)
|
||||||
from app import (user_api_client, service_api_client, invite_api_client)
|
from app import (user_api_client, current_service, service_api_client, invite_api_client)
|
||||||
from app.utils import user_has_permissions
|
from app.utils import user_has_permissions
|
||||||
|
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ def manage_users(service_id):
|
|||||||
users = user_api_client.get_users_for_service(service_id=service_id)
|
users = user_api_client.get_users_for_service(service_id=service_id)
|
||||||
invited_users = [invite for invite in invite_api_client.get_invites_for_service(service_id=service_id)
|
invited_users = [invite for invite in invite_api_client.get_invites_for_service(service_id=service_id)
|
||||||
if invite.status != 'accepted']
|
if invite.status != 'accepted']
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/manage-users.html',
|
'views/manage-users.html',
|
||||||
users=users,
|
users=users,
|
||||||
@@ -51,7 +52,13 @@ def manage_users(service_id):
|
|||||||
@user_has_permissions('manage_users', admin_override=True)
|
@user_has_permissions('manage_users', admin_override=True)
|
||||||
def invite_user(service_id):
|
def invite_user(service_id):
|
||||||
|
|
||||||
form = InviteUserForm(invalid_email_address=current_user.email_address)
|
form = InviteUserForm(
|
||||||
|
invalid_email_address=current_user.email_address
|
||||||
|
)
|
||||||
|
|
||||||
|
service_has_email_auth = 'email_auth' in current_service['permissions']
|
||||||
|
if not service_has_email_auth:
|
||||||
|
form.login_authentication.data = 'sms_auth'
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
email_address = form.email_address.data
|
email_address = form.email_address.data
|
||||||
@@ -60,7 +67,8 @@ def invite_user(service_id):
|
|||||||
current_user.id,
|
current_user.id,
|
||||||
service_id,
|
service_id,
|
||||||
email_address,
|
email_address,
|
||||||
permissions
|
permissions,
|
||||||
|
form.login_authentication.data
|
||||||
)
|
)
|
||||||
|
|
||||||
flash('Invite sent to {}'.format(invited_user.email_address), 'default_with_tick')
|
flash('Invite sent to {}'.format(invited_user.email_address), 'default_with_tick')
|
||||||
@@ -68,7 +76,8 @@ def invite_user(service_id):
|
|||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/invite-user.html',
|
'views/invite-user.html',
|
||||||
form=form
|
form=form,
|
||||||
|
service_has_email_auth=service_has_email_auth
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -76,26 +85,35 @@ def invite_user(service_id):
|
|||||||
@login_required
|
@login_required
|
||||||
@user_has_permissions('manage_users', admin_override=True)
|
@user_has_permissions('manage_users', admin_override=True)
|
||||||
def edit_user_permissions(service_id, user_id):
|
def edit_user_permissions(service_id, user_id):
|
||||||
|
service_has_email_auth = 'email_auth' in current_service['permissions']
|
||||||
# TODO we should probably using the service id here in the get user
|
# TODO we should probably using the service id here in the get user
|
||||||
# call as well. eg. /user/<user_id>?&service=service_id
|
# call as well. eg. /user/<user_id>?&service=service_id
|
||||||
user = user_api_client.get_user(user_id)
|
user = user_api_client.get_user(user_id)
|
||||||
# Need to make the email address read only, or a disabled field?
|
user_has_no_mobile_number = user.mobile_number is None
|
||||||
# Do it through the template or the form class?
|
|
||||||
form = PermissionsForm(**{
|
form = PermissionsForm(
|
||||||
role: user.has_permissions(permissions=permissions) for role, permissions in roles.items()
|
**{role: user.has_permissions(permissions=permissions) for role, permissions in roles.items()},
|
||||||
})
|
login_authentication=user.auth_type
|
||||||
|
)
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
user_api_client.set_user_permissions(
|
user_api_client.set_user_permissions(
|
||||||
user_id, service_id,
|
user_id, service_id,
|
||||||
permissions=set(get_permissions_from_form(form)),
|
permissions=set(get_permissions_from_form(form)),
|
||||||
)
|
)
|
||||||
|
if service_has_email_auth:
|
||||||
|
user_api_client.update_user_attribute(
|
||||||
|
user_id,
|
||||||
|
auth_type=form.login_authentication.data
|
||||||
|
)
|
||||||
return redirect(url_for('.manage_users', service_id=service_id))
|
return redirect(url_for('.manage_users', service_id=service_id))
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'views/edit-user-permissions.html',
|
'views/edit-user-permissions.html',
|
||||||
user=user,
|
user=user,
|
||||||
form=form
|
form=form,
|
||||||
|
service_has_email_auth=service_has_email_auth,
|
||||||
|
user_has_no_mobile_number=user_has_no_mobile_number
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from flask import (render_template, url_for, redirect, flash, session, current_app)
|
from flask import (render_template, url_for, redirect, flash, session, current_app)
|
||||||
from itsdangerous import SignatureExpired
|
from itsdangerous import SignatureExpired
|
||||||
|
from notifications_utils.url_safe_token import check_token
|
||||||
|
|
||||||
|
from app import user_api_client
|
||||||
from app.main import main
|
from app.main import main
|
||||||
from app.main.forms import NewPasswordForm
|
from app.main.forms import NewPasswordForm
|
||||||
from datetime import datetime
|
|
||||||
from app import user_api_client
|
|
||||||
|
|
||||||
|
|
||||||
@main.route('/new-password/<path:token>', methods=['GET', 'POST'])
|
@main.route('/new-password/<path:token>', methods=['GET', 'POST'])
|
||||||
def new_password(token):
|
def new_password(token):
|
||||||
from notifications_utils.url_safe_token import check_token
|
|
||||||
try:
|
try:
|
||||||
token_data = check_token(token, current_app.config['SECRET_KEY'], current_app.config['DANGEROUS_SALT'],
|
token_data = check_token(token, current_app.config['SECRET_KEY'], current_app.config['DANGEROUS_SALT'],
|
||||||
current_app.config['TOKEN_MAX_AGE_SECONDS'])
|
current_app.config['EMAIL_EXPIRY_SECONDS'])
|
||||||
except SignatureExpired:
|
except SignatureExpired:
|
||||||
flash('The link in the email we sent you has expired. Enter your email address to resend.')
|
flash('The link in the email we sent you has expired. Enter your email address to resend.')
|
||||||
return redirect(url_for('.forgot_password'))
|
return redirect(url_for('.forgot_password'))
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ def create_global_stats(services):
|
|||||||
'requested': 0
|
'requested': 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for service in services:
|
for service in services:
|
||||||
for msg_type, status in itertools.product(('sms', 'email', 'letter'), ('delivered', 'failed', 'requested')):
|
for msg_type, status in itertools.product(('sms', 'email', 'letter'), ('delivered', 'failed', 'requested')):
|
||||||
stats[msg_type][status] += service['statistics'][msg_type][status]
|
stats[msg_type][status] += service['statistics'][msg_type][status]
|
||||||
|
|||||||
+53
-9
@@ -178,7 +178,7 @@ def set_sender(service_id, template_id):
|
|||||||
|
|
||||||
template = service_api_client.get_service_template(service_id, template_id)['data']
|
template = service_api_client.get_service_template(service_id, template_id)['data']
|
||||||
|
|
||||||
if template['template_type'] != 'email':
|
if template['template_type'] == 'letter':
|
||||||
return redirect_to_one_off
|
return redirect_to_one_off
|
||||||
|
|
||||||
sender_details = get_sender_details(service_id, template['template_type'])
|
sender_details = get_sender_details(service_id, template['template_type'])
|
||||||
@@ -192,7 +192,12 @@ def set_sender(service_id, template_id):
|
|||||||
sender_choices=sender_context['value_and_label'],
|
sender_choices=sender_context['value_and_label'],
|
||||||
sender_label=sender_context['description']
|
sender_label=sender_context['description']
|
||||||
)
|
)
|
||||||
option_hints = {sender_context['default_id']: 'Default'}
|
option_hints = {sender_context['default_id']: '(Default)',
|
||||||
|
}
|
||||||
|
if sender_context.get('receives_text_message', None):
|
||||||
|
option_hints.update({sender_context['receives_text_message']: '(Receives replies)'})
|
||||||
|
if sender_context.get('default_and_receives', None):
|
||||||
|
option_hints = {sender_context['default_and_receives']: '(Default and receives replies)'}
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
session['sender_id'] = form.sender.data
|
session['sender_id'] = form.sender.data
|
||||||
@@ -220,12 +225,24 @@ def get_sender_context(sender_details, template_type):
|
|||||||
'title': 'Choose sender address',
|
'title': 'Choose sender address',
|
||||||
'description': 'Select an address that recipients can reply to',
|
'description': 'Select an address that recipients can reply to',
|
||||||
'field_name': 'contact_block'
|
'field_name': 'contact_block'
|
||||||
|
},
|
||||||
|
'sms': {
|
||||||
|
'title': 'Chose text message sender',
|
||||||
|
'description': 'Select a text message sender that the recipients can reply to',
|
||||||
|
'field_name': 'sms_sender'
|
||||||
}
|
}
|
||||||
}[template_type]
|
}[template_type]
|
||||||
|
|
||||||
sender_format = context['field_name']
|
sender_format = context['field_name']
|
||||||
|
|
||||||
context['default_id'] = next(sender['id'] for sender in sender_details if sender['is_default'])
|
context['default_id'] = next(sender['id'] for sender in sender_details if sender['is_default'])
|
||||||
|
if template_type == 'sms':
|
||||||
|
inbound = [sender['id'] for sender in sender_details if sender['inbound_number_id']]
|
||||||
|
if inbound:
|
||||||
|
context['receives_text_message'] = next(iter(inbound))
|
||||||
|
if context['default_id'] == context.get('receives_text_message', None):
|
||||||
|
context['default_and_receives'] = context['default_id']
|
||||||
|
|
||||||
context['value_and_label'] = [(sender['id'], sender[sender_format]) for sender in sender_details]
|
context['value_and_label'] = [(sender['id'], sender[sender_format]) for sender in sender_details]
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@@ -233,7 +250,8 @@ def get_sender_context(sender_details, template_type):
|
|||||||
def get_sender_details(service_id, template_type):
|
def get_sender_details(service_id, template_type):
|
||||||
api_call = {
|
api_call = {
|
||||||
'email': service_api_client.get_reply_to_email_addresses,
|
'email': service_api_client.get_reply_to_email_addresses,
|
||||||
'letter': service_api_client.get_letter_contacts
|
'letter': service_api_client.get_letter_contacts,
|
||||||
|
'sms': service_api_client.get_sms_senders
|
||||||
}[template_type]
|
}[template_type]
|
||||||
return api_call(service_id)
|
return api_call(service_id)
|
||||||
|
|
||||||
@@ -248,7 +266,7 @@ def send_test(service_id, template_id):
|
|||||||
session['send_test_letter_page_count'] = None
|
session['send_test_letter_page_count'] = None
|
||||||
|
|
||||||
db_template = service_api_client.get_service_template(service_id, template_id)['data']
|
db_template = service_api_client.get_service_template(service_id, template_id)['data']
|
||||||
if db_template['template_type'] != 'email':
|
if db_template['template_type'] == 'letter':
|
||||||
session['sender_id'] = None
|
session['sender_id'] = None
|
||||||
|
|
||||||
if email_or_sms_not_enabled(db_template['template_type'], current_service['permissions']):
|
if email_or_sms_not_enabled(db_template['template_type'], current_service['permissions']):
|
||||||
@@ -311,7 +329,12 @@ def send_test_step(service_id, template_id, step_index):
|
|||||||
|
|
||||||
if not session.get('send_test_letter_page_count'):
|
if not session.get('send_test_letter_page_count'):
|
||||||
session['send_test_letter_page_count'] = get_page_count_for_letter(db_template)
|
session['send_test_letter_page_count'] = get_page_count_for_letter(db_template)
|
||||||
|
email_reply_to = None
|
||||||
|
sms_sender = None
|
||||||
|
if db_template['template_type'] == 'email':
|
||||||
|
email_reply_to = get_email_reply_to_address_from_session(service_id)
|
||||||
|
elif db_template['template_type'] == 'sms':
|
||||||
|
sms_sender = get_sms_sender_from_session(service_id)
|
||||||
template = get_template(
|
template = get_template(
|
||||||
db_template,
|
db_template,
|
||||||
current_service,
|
current_service,
|
||||||
@@ -324,7 +347,8 @@ def send_test_step(service_id, template_id, step_index):
|
|||||||
filetype='png',
|
filetype='png',
|
||||||
),
|
),
|
||||||
page_count=session['send_test_letter_page_count'],
|
page_count=session['send_test_letter_page_count'],
|
||||||
email_reply_to=get_email_reply_to_address_from_session(service_id),
|
email_reply_to=email_reply_to,
|
||||||
|
sms_sender=sms_sender
|
||||||
)
|
)
|
||||||
|
|
||||||
placeholders = fields_to_fill_in(
|
placeholders = fields_to_fill_in(
|
||||||
@@ -449,6 +473,12 @@ def _check_messages(service_id, template_type, upload_id, letters_as_pdf=False):
|
|||||||
remaining_messages = (current_service['message_limit'] - sum(stat['requested'] for stat in statistics.values()))
|
remaining_messages = (current_service['message_limit'] - sum(stat['requested'] for stat in statistics.values()))
|
||||||
|
|
||||||
contents = s3download(service_id, upload_id)
|
contents = s3download(service_id, upload_id)
|
||||||
|
email_reply_to = None
|
||||||
|
sms_sender = None
|
||||||
|
if template_type == 'email':
|
||||||
|
email_reply_to = get_email_reply_to_address_from_session(service_id)
|
||||||
|
elif template_type == 'sms':
|
||||||
|
sms_sender = get_sms_sender_from_session(service_id)
|
||||||
template = get_template(
|
template = get_template(
|
||||||
service_api_client.get_service_template(
|
service_api_client.get_service_template(
|
||||||
service_id,
|
service_id,
|
||||||
@@ -463,7 +493,8 @@ def _check_messages(service_id, template_type, upload_id, letters_as_pdf=False):
|
|||||||
upload_id=upload_id,
|
upload_id=upload_id,
|
||||||
filetype='png',
|
filetype='png',
|
||||||
) if not letters_as_pdf else None,
|
) if not letters_as_pdf else None,
|
||||||
email_reply_to=get_email_reply_to_address_from_session(service_id),
|
email_reply_to=email_reply_to,
|
||||||
|
sms_sender=sms_sender
|
||||||
)
|
)
|
||||||
recipients = RecipientCSV(
|
recipients = RecipientCSV(
|
||||||
contents,
|
contents,
|
||||||
@@ -723,12 +754,18 @@ def check_notification(service_id, template_id):
|
|||||||
|
|
||||||
def _check_notification(service_id, template_id, exception=None):
|
def _check_notification(service_id, template_id, exception=None):
|
||||||
db_template = service_api_client.get_service_template(service_id, template_id)['data']
|
db_template = service_api_client.get_service_template(service_id, template_id)['data']
|
||||||
|
email_reply_to = None
|
||||||
|
sms_sender = None
|
||||||
|
if db_template['template_type'] == 'email':
|
||||||
|
email_reply_to = get_email_reply_to_address_from_session(service_id)
|
||||||
|
elif db_template['template_type'] == 'sms':
|
||||||
|
sms_sender = get_sms_sender_from_session(service_id)
|
||||||
template = get_template(
|
template = get_template(
|
||||||
db_template,
|
db_template,
|
||||||
current_service,
|
current_service,
|
||||||
show_recipient=True,
|
show_recipient=True,
|
||||||
email_reply_to=get_email_reply_to_address_from_session(service_id),
|
email_reply_to=email_reply_to,
|
||||||
|
sms_sender=sms_sender
|
||||||
)
|
)
|
||||||
|
|
||||||
# go back to start of process
|
# go back to start of process
|
||||||
@@ -814,3 +851,10 @@ def get_email_reply_to_address_from_session(service_id):
|
|||||||
return service_api_client.get_reply_to_email_address(
|
return service_api_client.get_reply_to_email_address(
|
||||||
service_id, session['sender_id']
|
service_id, session['sender_id']
|
||||||
)['email_address']
|
)['email_address']
|
||||||
|
|
||||||
|
|
||||||
|
def get_sms_sender_from_session(service_id):
|
||||||
|
if session.get('sender_id'):
|
||||||
|
return service_api_client.get_sms_sender(
|
||||||
|
service_id=service_id, sms_sender_id=session['sender_id']
|
||||||
|
)['sms_sender']
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ from app.main.forms import (
|
|||||||
OrganisationTypeForm,
|
OrganisationTypeForm,
|
||||||
FreeSMSAllowance,
|
FreeSMSAllowance,
|
||||||
ServiceEditInboundNumberForm,
|
ServiceEditInboundNumberForm,
|
||||||
|
SMSPrefixForm,
|
||||||
)
|
)
|
||||||
from app import user_api_client, current_service, organisations_client, inbound_number_client, billing_api_client
|
from app import user_api_client, current_service, organisations_client, inbound_number_client, billing_api_client
|
||||||
from notifications_utils.formatters import formatted_list
|
from notifications_utils.formatters import formatted_list
|
||||||
@@ -108,7 +109,9 @@ def service_settings(service_id):
|
|||||||
letter_contact_details_count=letter_contact_details_count,
|
letter_contact_details_count=letter_contact_details_count,
|
||||||
default_sms_sender=default_sms_sender,
|
default_sms_sender=default_sms_sender,
|
||||||
sms_sender_count=sms_sender_count,
|
sms_sender_count=sms_sender_count,
|
||||||
free_sms_fragment_limit=free_sms_fragment_limit
|
free_sms_fragment_limit=free_sms_fragment_limit,
|
||||||
|
prefix_sms_with_service_name=current_service['prefix_sms_with_service_name'],
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -491,6 +494,30 @@ def service_set_sms(service_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@main.route("/services/<service_id>/service-settings/sms-prefix", methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
@user_has_permissions('manage_settings', admin_override=True)
|
||||||
|
def service_set_sms_prefix(service_id):
|
||||||
|
|
||||||
|
form = SMSPrefixForm(enabled=(
|
||||||
|
'on' if current_service['prefix_sms_with_service_name'] else 'off'
|
||||||
|
))
|
||||||
|
|
||||||
|
form.enabled.label.text = 'Start all text messages with ‘{}:’'.format(current_service['name'])
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
service_api_client.update_service(
|
||||||
|
current_service['id'],
|
||||||
|
prefix_sms=(form.enabled.data == 'on')
|
||||||
|
)
|
||||||
|
return redirect(url_for('.service_settings', service_id=service_id))
|
||||||
|
|
||||||
|
return render_template(
|
||||||
|
'views/service-settings/sms-prefix.html',
|
||||||
|
form=form
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@main.route("/services/<service_id>/service-settings/set-international-sms", methods=['GET', 'POST'])
|
@main.route("/services/<service_id>/service-settings/set-international-sms", methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@user_has_permissions('manage_settings', admin_override=True)
|
@user_has_permissions('manage_settings', admin_override=True)
|
||||||
@@ -595,7 +622,7 @@ def service_sms_senders(service_id):
|
|||||||
if sender['is_default']:
|
if sender['is_default']:
|
||||||
hints += ["default"]
|
hints += ["default"]
|
||||||
if sender['inbound_number_id']:
|
if sender['inbound_number_id']:
|
||||||
hints += ["recieves replies"]
|
hints += ["receives replies"]
|
||||||
if hints:
|
if hints:
|
||||||
sender['hint'] = "(" + " and ".join(hints) + ")"
|
sender['hint'] = "(" + " and ".join(hints) + ")"
|
||||||
|
|
||||||
|
|||||||
+21
-29
@@ -50,34 +50,26 @@ def verify():
|
|||||||
@main.route('/verify-email/<token>')
|
@main.route('/verify-email/<token>')
|
||||||
def verify_email(token):
|
def verify_email(token):
|
||||||
try:
|
try:
|
||||||
token_data = check_token(token,
|
token_data = check_token(
|
||||||
current_app.config['SECRET_KEY'],
|
token,
|
||||||
current_app.config['DANGEROUS_SALT'],
|
current_app.config['SECRET_KEY'],
|
||||||
current_app.config['EMAIL_EXPIRY_SECONDS'])
|
current_app.config['DANGEROUS_SALT'],
|
||||||
|
current_app.config['EMAIL_EXPIRY_SECONDS']
|
||||||
token_data = json.loads(token_data)
|
)
|
||||||
verified = user_api_client.check_verify_code(token_data['user_id'], token_data['secret_code'], 'email')
|
|
||||||
user = user_api_client.get_user(token_data['user_id'])
|
|
||||||
if not user:
|
|
||||||
abort(404)
|
|
||||||
|
|
||||||
if user.is_active:
|
|
||||||
flash("That verification link has expired.")
|
|
||||||
return redirect(url_for('main.sign_in'))
|
|
||||||
|
|
||||||
session['user_details'] = {"email": user.email_address, "id": user.id}
|
|
||||||
if verified[0]:
|
|
||||||
user_api_client.send_verify_code(user.id, 'sms', user.mobile_number)
|
|
||||||
return redirect('verify')
|
|
||||||
else:
|
|
||||||
if verified[1] == 'Code has expired':
|
|
||||||
flash("The link in the email we sent you has expired. We've sent you a new one.")
|
|
||||||
return redirect(url_for('main.resend_email_verification'))
|
|
||||||
else:
|
|
||||||
message = "There was a problem verifying your account. Error message: '{}'".format(verified[1])
|
|
||||||
flash(message)
|
|
||||||
return redirect(url_for('main.index'))
|
|
||||||
|
|
||||||
except SignatureExpired:
|
except SignatureExpired:
|
||||||
flash('The link in the email we sent you has expired')
|
flash("The link in the email we sent you has expired. We've sent you a new one.")
|
||||||
return redirect(url_for('main.resend_email_verification'))
|
return redirect(url_for('main.resend_email_verification'))
|
||||||
|
|
||||||
|
# token contains json blob of format: {'user_id': '...', 'secret_code': '...'} (secret_code is unused)
|
||||||
|
token_data = json.loads(token_data)
|
||||||
|
user = user_api_client.get_user(token_data['user_id'])
|
||||||
|
if not user:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
if user.is_active:
|
||||||
|
flash("That verification link has expired.")
|
||||||
|
return redirect(url_for('main.sign_in'))
|
||||||
|
|
||||||
|
session['user_details'] = {"email": user.email_address, "id": user.id}
|
||||||
|
user_api_client.send_verify_code(user.id, 'sms', user.mobile_number)
|
||||||
|
return redirect('verify')
|
||||||
|
|||||||
@@ -12,12 +12,13 @@ class InviteApiClient(NotifyAdminAPIClient):
|
|||||||
self.service_id = app.config['ADMIN_CLIENT_USER_NAME']
|
self.service_id = app.config['ADMIN_CLIENT_USER_NAME']
|
||||||
self.api_key = app.config['ADMIN_CLIENT_SECRET']
|
self.api_key = app.config['ADMIN_CLIENT_SECRET']
|
||||||
|
|
||||||
def create_invite(self, invite_from_id, service_id, email_address, permissions):
|
def create_invite(self, invite_from_id, service_id, email_address, permissions, auth_type):
|
||||||
data = {
|
data = {
|
||||||
'service': str(service_id),
|
'service': str(service_id),
|
||||||
'email_address': email_address,
|
'email_address': email_address,
|
||||||
'from_user': invite_from_id,
|
'from_user': invite_from_id,
|
||||||
'permissions': permissions
|
'permissions': permissions,
|
||||||
|
'auth_type': auth_type
|
||||||
}
|
}
|
||||||
data = _attach_current_user(data)
|
data = _attach_current_user(data)
|
||||||
resp = self.post(url='/service/{}/invite'.format(service_id), data=data)
|
resp = self.post(url='/service/{}/invite'.format(service_id), data=data)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ class User(UserMixin):
|
|||||||
self._mobile_number = fields.get('mobile_number')
|
self._mobile_number = fields.get('mobile_number')
|
||||||
self._password_changed_at = fields.get('password_changed_at')
|
self._password_changed_at = fields.get('password_changed_at')
|
||||||
self._permissions = fields.get('permissions')
|
self._permissions = fields.get('permissions')
|
||||||
|
self._auth_type = fields.get('auth_type')
|
||||||
self._failed_login_count = fields.get('failed_login_count')
|
self._failed_login_count = fields.get('failed_login_count')
|
||||||
self._state = fields.get('state')
|
self._state = fields.get('state')
|
||||||
self.max_failed_login_count = max_failed_login_count
|
self.max_failed_login_count = max_failed_login_count
|
||||||
@@ -108,6 +109,14 @@ class User(UserMixin):
|
|||||||
return set(self._permissions[service_id]) >= set(permissions)
|
return set(self._permissions[service_id]) >= set(permissions)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def auth_type(self):
|
||||||
|
return self._auth_type
|
||||||
|
|
||||||
|
@auth_type.setter
|
||||||
|
def auth_type(self, auth_type):
|
||||||
|
self._auth_type = auth_type
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def failed_login_count(self):
|
def failed_login_count(self):
|
||||||
return self._failed_login_count
|
return self._failed_login_count
|
||||||
@@ -155,6 +164,7 @@ class InvitedUser(object):
|
|||||||
self.permissions = []
|
self.permissions = []
|
||||||
self.status = status
|
self.status = status
|
||||||
self.created_at = created_at
|
self.created_at = created_at
|
||||||
|
self.auth_type = auth_type
|
||||||
|
|
||||||
def has_permissions(self, permissions):
|
def has_permissions(self, permissions):
|
||||||
return set(self.permissions) > set(permissions)
|
return set(self.permissions) > set(permissions)
|
||||||
@@ -164,10 +174,12 @@ class InvitedUser(object):
|
|||||||
self.service,
|
self.service,
|
||||||
self.from_user,
|
self.from_user,
|
||||||
self.email_address,
|
self.email_address,
|
||||||
|
self.auth_type,
|
||||||
self.status) == (other.id,
|
self.status) == (other.id,
|
||||||
other.service,
|
other.service,
|
||||||
other.from_user,
|
other.from_user,
|
||||||
other.email_address,
|
other.email_address,
|
||||||
|
other.auth_type,
|
||||||
other.status))
|
other.status))
|
||||||
|
|
||||||
def serialize(self, permissions_as_string=False):
|
def serialize(self, permissions_as_string=False):
|
||||||
@@ -176,7 +188,8 @@ class InvitedUser(object):
|
|||||||
'from_user': self.from_user,
|
'from_user': self.from_user,
|
||||||
'email_address': self.email_address,
|
'email_address': self.email_address,
|
||||||
'status': self.status,
|
'status': self.status,
|
||||||
'created_at': str(self.created_at)
|
'created_at': str(self.created_at),
|
||||||
|
'auth_type': self.auth_type
|
||||||
}
|
}
|
||||||
if permissions_as_string:
|
if permissions_as_string:
|
||||||
data['permissions'] = ','.join(self.permissions)
|
data['permissions'] = ','.join(self.permissions)
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ class ServiceAPIClient(NotifyAdminAPIClient):
|
|||||||
'permissions',
|
'permissions',
|
||||||
'organisation_type',
|
'organisation_type',
|
||||||
'free_sms_fragment_limit',
|
'free_sms_fragment_limit',
|
||||||
|
'prefix_sms',
|
||||||
}
|
}
|
||||||
if disallowed_attributes:
|
if disallowed_attributes:
|
||||||
raise TypeError('Not allowed to update service attributes: {}'.format(
|
raise TypeError('Not allowed to update service attributes: {}'.format(
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ from app.notify_client.models import User
|
|||||||
ALLOWED_ATTRIBUTES = {
|
ALLOWED_ATTRIBUTES = {
|
||||||
'name',
|
'name',
|
||||||
'email_address',
|
'email_address',
|
||||||
'mobile_number'
|
'mobile_number',
|
||||||
|
'auth_type',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
{% from "components/table.html" import mapping_table, row, text_field, edit_field, field, row_heading%}
|
||||||
|
{% extends "withoutnav_template.html" %}
|
||||||
|
|
||||||
|
{% block per_page_title %}
|
||||||
|
Callbacks
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="column-two-thirds">
|
||||||
|
<h1 class="heading-large">Callbacks for received text messages</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Text messages you receive can be forwarded to a URL that you specify, using our callback feature.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Messages are forwarded as they are received.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To protect your service, we require you to provide a bearer token. We put this token in the authorisation header of the callback requests.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Once you have ‘receive text messages’ enabled, you can set up your callback on the settings page of your service.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
If you don’t have ‘receive text messages’ enabled for your service, <a href="{{ url_for('.support') }}">get in touch</a> and we can turn it on for you.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2 class="heading-medium">Format of the callback</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The format of the callback message you receive is JSON.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="bottom-gutter-3-2">
|
||||||
|
{% call mapping_table(
|
||||||
|
caption='Callback message format',
|
||||||
|
field_headings=['Key', 'Description', 'Format'],
|
||||||
|
field_headings_visible=True,
|
||||||
|
caption_visible=False
|
||||||
|
) %}
|
||||||
|
{% for key, description, format in [
|
||||||
|
('id', 'Notify’s id for the received message', 'UUID'),
|
||||||
|
('source_number', 'The phone number the message was sent from', '447700912345'),
|
||||||
|
('destination_number', 'The number the message was sent to (your number)', '07700987654'),
|
||||||
|
('message', 'The received message', 'Hello Notify!'),
|
||||||
|
('date_received', 'The UTC datetime that the message was received by Notify', '2017-05-14T12:15:30.000000Z')
|
||||||
|
] %}
|
||||||
|
{% call row() %}
|
||||||
|
{% call row_heading() %} {{ key }} {% endcall %}
|
||||||
|
{{ text_field(description) }}
|
||||||
|
{{ text_field(format) }}
|
||||||
|
{% endcall %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endcall %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<li>formal risk assessments using a methodology based on
|
<li>formal risk assessments using a methodology based on
|
||||||
<a href="http://www.iso.org/iso/catalogue_detail?csnumber=56742">ISO 27005</a>:2011
|
<a href="http://www.iso.org/iso/catalogue_detail?csnumber=56742">ISO 27005</a>:2011
|
||||||
and supplemented by reference to NCSC standards and guidance documentation</li>
|
and supplemented by reference to NCSC standards and guidance documentation</li>
|
||||||
<li><a href="https://www.cesg.gov.uk/articles/check-fundamental-principles">CHECK</a>-based
|
<li><a href="https://www.ncsc.gov.uk/articles/check-fundamental-principles">CHECK</a>-based
|
||||||
IT Health Check (ITHC) testing (annual and on major change)</li>
|
IT Health Check (ITHC) testing (annual and on major change)</li>
|
||||||
<li>residual risk statement preparation and active management of the risk treatment plan</li>
|
<li>residual risk statement preparation and active management of the risk treatment plan</li>
|
||||||
<li>regular updates to the Privacy Impact Assessment</li>
|
<li>regular updates to the Privacy Impact Assessment</li>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
<p>
|
<p>
|
||||||
You should revoke and re-create these keys on a regular basis. You can have more than one active key at a time. To revoke a key click the revoke button on the API Key page.
|
You should revoke and re-create these keys on a regular basis. You can have more than one active key at a time. To revoke a key click the revoke button on the API Key page.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="panel panel-border-wide">
|
<div class="panel panel-border-wide">
|
||||||
<p>
|
<p>
|
||||||
You should never send test messages to invalid numbers or addresses using a live key. This will lead to your API key being revoked.
|
You should never send test messages to invalid numbers or addresses using a live key. This will lead to your API key being revoked.
|
||||||
@@ -73,7 +73,7 @@
|
|||||||
Use a test key to test the performance of your service and its integration with GOV.UK Notify.
|
Use a test key to test the performance of your service and its integration with GOV.UK Notify.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Test keys don’t send real messages but do generates realistic responses.
|
Test keys don’t send real messages but do generate realistic responses.
|
||||||
There’s no restriction on who you can send to.
|
There’s no restriction on who you can send to.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -63,6 +63,15 @@
|
|||||||
user.has_permissions(permissions=['manage_api_keys']),
|
user.has_permissions(permissions=['manage_api_keys']),
|
||||||
'Access API keys'
|
'Access API keys'
|
||||||
) }}
|
) }}
|
||||||
|
{% if 'email_auth' in current_service['permissions'] %}
|
||||||
|
<div class="tick-cross-list-hint">
|
||||||
|
{% if user.auth_type == 'sms_auth' %}
|
||||||
|
Signs in with a text message code
|
||||||
|
{% else %}
|
||||||
|
Signs in with an email link
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if current_user.has_permissions(['manage_users'], admin_override=True) %}
|
{% if current_user.has_permissions(['manage_users'], admin_override=True) %}
|
||||||
{% if current_user.id != user.id %}
|
{% if current_user.id != user.id %}
|
||||||
@@ -104,6 +113,15 @@
|
|||||||
user.has_permissions(permissions=['manage_api_keys']),
|
user.has_permissions(permissions=['manage_api_keys']),
|
||||||
'Access API keys'
|
'Access API keys'
|
||||||
) }}
|
) }}
|
||||||
|
{% if 'email_auth' in current_service['permissions'] %}
|
||||||
|
<div class="tick-cross-list-hint">
|
||||||
|
{% if user.auth_type == 'sms_auth' %}
|
||||||
|
Signs in with a text message code
|
||||||
|
{% else %}
|
||||||
|
Signs in with an email link
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<li class="tick-cross-list-edit-link">
|
<li class="tick-cross-list-edit-link">
|
||||||
{% if user.status == 'pending' and current_user.has_permissions(['manage_users']) %}
|
{% if user.status == 'pending' and current_user.has_permissions(['manage_users']) %}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{% from "components/checkbox.html" import checkbox %}
|
{% from "components/checkbox.html" import checkbox %}
|
||||||
|
{% from "components/radios.html" import radios %}
|
||||||
|
|
||||||
<fieldset class="form-group">
|
<fieldset class="form-group">
|
||||||
<legend class="form-label">
|
<legend class="form-label">
|
||||||
@@ -20,3 +21,7 @@
|
|||||||
<li>who the other team members are</li>
|
<li>who the other team members are</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if service_has_email_auth %}
|
||||||
|
{{ radios(form.login_authentication, disable=['sms_auth' if user_has_no_mobile_number]) }}
|
||||||
|
{% endif %}
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
action="{{ url_for('.view_notifications', service_id=current_service.id, message_type=message_type) }}"
|
action="{{ url_for('.view_notifications', service_id=current_service.id, message_type=message_type) }}"
|
||||||
class="grid-row"
|
class="grid-row"
|
||||||
>
|
>
|
||||||
<div class="column-three-quarters">
|
<div class="column-three-quarters {% if message_type == 'sms' %}extra-tracking{% endif %}">
|
||||||
{{ textbox(
|
{{ textbox(
|
||||||
search_form.to,
|
search_form.to,
|
||||||
width='1-1',
|
width='1-1',
|
||||||
|
|||||||
@@ -23,8 +23,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="column-third">
|
<div class="column-third">
|
||||||
{{ big_number_with_status(
|
{{ big_number_with_status(
|
||||||
global_stats.letter.delivered + global_stats.letter.failed,
|
global_stats.letter.requested,
|
||||||
message_count_label(global_stats.letter.delivered, 'letter'),
|
message_count_label(global_stats.letter.requested, 'letter'),
|
||||||
global_stats.letter.failed,
|
global_stats.letter.failed,
|
||||||
global_stats.letter.failure_rate,
|
global_stats.letter.failure_rate,
|
||||||
global_stats.letter.failure_rate|float > 3,
|
global_stats.letter.failure_rate|float > 3,
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
|
|
||||||
{% call row() %}
|
{% call row() %}
|
||||||
{% if not service['active'] %}
|
{% if not service['active'] %}
|
||||||
{% call field(status='default') %}
|
{% call field(status='default', border=False) %}
|
||||||
<span class="heading-medium">archived</span>
|
<span class="heading-medium">archived</span>
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
{% elif service['research_mode'] %}
|
{% elif service['research_mode'] %}
|
||||||
@@ -62,16 +62,26 @@
|
|||||||
<span class="research-mode">research mode</span>
|
<span class="research-mode">research mode</span>
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
{% elif not service['restricted'] %}
|
{% elif not service['restricted'] %}
|
||||||
{% call field(status='error') %}
|
{% call field(status='error', border=False) %}
|
||||||
<span class="heading-medium">Live</span>
|
<span class="heading-medium">Live</span>
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ text_field('') }}
|
{% call field(border=False) %}
|
||||||
|
{% endcall %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{{ stats_fields('sms', service['stats']) }}
|
{{ stats_fields('sms', service['stats']) }}
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
|
|
||||||
|
{% call row() %}
|
||||||
|
|
||||||
|
{% call field(border=False) %}
|
||||||
|
|
||||||
|
{% endcall %}
|
||||||
|
{{ stats_fields('letter', service['stats']) }}
|
||||||
|
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -92,6 +92,12 @@
|
|||||||
{{ edit_field('Manage' if sms_sender_count else 'Change', url_for('.service_sms_senders', service_id=current_service.id)) }}
|
{{ edit_field('Manage' if sms_sender_count else 'Change', url_for('.service_sms_senders', service_id=current_service.id)) }}
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
|
|
||||||
|
{% call row() %}
|
||||||
|
{{ text_field('Text messages start with service name') }}
|
||||||
|
{{ boolean_field(prefix_sms_with_service_name) }}
|
||||||
|
{{ edit_field('Change', url_for('.service_set_sms_prefix', service_id=current_service.id)) }}
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
{% call row() %}
|
{% call row() %}
|
||||||
{{ text_field('International text messages') }}
|
{{ text_field('International text messages') }}
|
||||||
{{ boolean_field('international_sms' in current_service.permissions) }}
|
{{ boolean_field('international_sms' in current_service.permissions) }}
|
||||||
@@ -106,7 +112,7 @@
|
|||||||
|
|
||||||
{% if can_receive_inbound %}
|
{% if can_receive_inbound %}
|
||||||
{% call row() %}
|
{% call row() %}
|
||||||
{{ text_field('API endpoint for received text messages') }}
|
{{ text_field('Callback URL for received text messages') }}
|
||||||
{{ optional_text_field(inbound_api_url) }}
|
{{ optional_text_field(inbound_api_url) }}
|
||||||
{{ edit_field('Change', url_for('.service_set_inbound_api', service_id=current_service.id)) }}
|
{{ edit_field('Change', url_for('.service_set_inbound_api', service_id=current_service.id)) }}
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
|
|||||||
@@ -3,27 +3,27 @@
|
|||||||
{% from "components/page-footer.html" import page_footer %}
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
{% block service_page_title %}
|
{% block service_page_title %}
|
||||||
Inbound api
|
Callback URL
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block maincolumn_content %}
|
{% block maincolumn_content %}
|
||||||
<div class="grid-row">
|
<div class="grid-row">
|
||||||
<div class="column-five-sixths">
|
<div class="column-five-sixths">
|
||||||
<h1 class="heading-large">API endpoint for received text messages</h1>
|
<h1 class="heading-large">Callback URL for received text messages</h1>
|
||||||
<p>
|
<p>
|
||||||
This is the https url that the inbound SMS messages will be posted to
|
Text messages you receive can be forwarded to your systems with our callback feature.
|
||||||
and the bearer token used in the authorisation header of the request.
|
See our <a href="{{ url_for('.callbacks') }}">documentation on the format of the callback</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{{ textbox(
|
{{ textbox(
|
||||||
form.url,
|
form.url,
|
||||||
width='2-3',
|
width='2-3',
|
||||||
hint='Valid https url'
|
hint='Valid https URL'
|
||||||
) }}
|
) }}
|
||||||
{{ textbox(
|
{{ textbox(
|
||||||
form.bearer_token,
|
form.bearer_token,
|
||||||
width='1-4',
|
width='2-3',
|
||||||
hint='At least 10 characters'
|
hint='At least 10 characters'
|
||||||
) }}
|
) }}
|
||||||
{{ page_footer(
|
{{ page_footer(
|
||||||
@@ -35,4 +35,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{% extends "withnav_template.html" %}
|
||||||
|
{% from "components/radios.html" import radios %}
|
||||||
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
|
{% block service_page_title %}
|
||||||
|
Text messages start with service name
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
|
<h1 class="heading-large">Text messages start with service name</h1>
|
||||||
|
<form method="post">
|
||||||
|
{{ radios(form.enabled) }}
|
||||||
|
{{ page_footer(
|
||||||
|
button_text="Save",
|
||||||
|
back_link=url_for('.service_settings', service_id=current_service.id),
|
||||||
|
back_link_text='Back to settings'
|
||||||
|
) }}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
<div class="grid-row bottom-gutter">
|
<div class="grid-row bottom-gutter">
|
||||||
<div class="column-half">
|
<div class="column-half">
|
||||||
<h3 class="visually-hidden">Services</h3>
|
<h3 class="visually-hidden">Services</h3>
|
||||||
<div class="product-page-big-number">98</div>
|
<div class="product-page-big-number">104</div>
|
||||||
services
|
services
|
||||||
</div>
|
</div>
|
||||||
<div class="column-half">
|
<div class="column-half">
|
||||||
|
|||||||
+2
-1
@@ -272,6 +272,7 @@ def get_template(
|
|||||||
page_count=1,
|
page_count=1,
|
||||||
redact_missing_personalisation=False,
|
redact_missing_personalisation=False,
|
||||||
email_reply_to=None,
|
email_reply_to=None,
|
||||||
|
sms_sender=None
|
||||||
):
|
):
|
||||||
if 'email' == template['template_type']:
|
if 'email' == template['template_type']:
|
||||||
return EmailPreviewTemplate(
|
return EmailPreviewTemplate(
|
||||||
@@ -287,7 +288,7 @@ def get_template(
|
|||||||
return SMSPreviewTemplate(
|
return SMSPreviewTemplate(
|
||||||
template,
|
template,
|
||||||
prefix=service['name'],
|
prefix=service['name'],
|
||||||
sender=(service['sms_sender'] not in {'GOVUK', None}),
|
sender=not service['prefix_sms_with_service_name'],
|
||||||
show_recipient=show_recipient,
|
show_recipient=show_recipient,
|
||||||
redact_missing_personalisation=redact_missing_personalisation,
|
redact_missing_personalisation=redact_missing_personalisation,
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -138,7 +138,7 @@ gulp.task('lint:sass', () => gulp
|
|||||||
|
|
||||||
gulp.task('lint:js', () => gulp
|
gulp.task('lint:js', () => gulp
|
||||||
.src(paths.src + 'javascripts/**/*.js')
|
.src(paths.src + 'javascripts/**/*.js')
|
||||||
.pipe(plugins.jshint({'esversion': 6, 'esnext': false}))
|
.pipe(plugins.jshint())
|
||||||
.pipe(plugins.jshint.reporter(stylish))
|
.pipe(plugins.jshint.reporter(stylish))
|
||||||
.pipe(plugins.jshint.reporter('fail'))
|
.pipe(plugins.jshint.reporter('fail'))
|
||||||
);
|
);
|
||||||
|
|||||||
+3
-3
@@ -8,15 +8,15 @@ boto3==1.4.7
|
|||||||
blinker==1.4
|
blinker==1.4
|
||||||
pyexcel==0.5.6
|
pyexcel==0.5.6
|
||||||
pyexcel-io==0.5.3
|
pyexcel-io==0.5.3
|
||||||
pyexcel-xls==0.5.2
|
pyexcel-xls==0.5.4
|
||||||
pyexcel-xlsx==0.5.2
|
pyexcel-xlsx==0.5.4
|
||||||
pyexcel-ods3==0.5.2
|
pyexcel-ods3==0.5.2
|
||||||
pytz==2017.3
|
pytz==2017.3
|
||||||
gunicorn==19.7.1
|
gunicorn==19.7.1
|
||||||
whitenoise==3.3.1 #manages static assets
|
whitenoise==3.3.1 #manages static assets
|
||||||
|
|
||||||
# pin to minor version 3.1.x
|
# pin to minor version 3.1.x
|
||||||
notifications-python-client==4.5.0
|
notifications-python-client==4.6.0
|
||||||
|
|
||||||
# PaaS
|
# PaaS
|
||||||
awscli>=1.11,<1.12
|
awscli>=1.11,<1.12
|
||||||
|
|||||||
+6
-2
@@ -53,16 +53,19 @@ def service_json(
|
|||||||
created_at=None,
|
created_at=None,
|
||||||
letter_contact_block=None,
|
letter_contact_block=None,
|
||||||
inbound_api=None,
|
inbound_api=None,
|
||||||
permissions=['email', 'sms'],
|
permissions=None,
|
||||||
organisation_type='central',
|
organisation_type='central',
|
||||||
free_sms_fragment_limit=250000,
|
free_sms_fragment_limit=250000,
|
||||||
|
prefix_sms_with_service_name='Treat as None',
|
||||||
):
|
):
|
||||||
if users is None:
|
if users is None:
|
||||||
users = []
|
users = []
|
||||||
if permissions is None:
|
if permissions is None:
|
||||||
permissions = []
|
permissions = ['email', 'sms']
|
||||||
if inbound_api is None:
|
if inbound_api is None:
|
||||||
inbound_api = []
|
inbound_api = []
|
||||||
|
if prefix_sms_with_service_name == 'Treat as None':
|
||||||
|
prefix_sms_with_service_name = (sms_sender == 'GOVUK')
|
||||||
return {
|
return {
|
||||||
'id': id_,
|
'id': id_,
|
||||||
'name': name,
|
'name': name,
|
||||||
@@ -83,6 +86,7 @@ def service_json(
|
|||||||
'dvla_organisation': '001',
|
'dvla_organisation': '001',
|
||||||
'permissions': permissions,
|
'permissions': permissions,
|
||||||
'inbound_api': inbound_api,
|
'inbound_api': inbound_api,
|
||||||
|
'prefix_sms_with_service_name': prefix_sms_with_service_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
from flask import Response
|
||||||
|
import pytest
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
from notifications_python_client.errors import HTTPError
|
||||||
|
|
||||||
|
|
||||||
def test_bad_url_returns_page_not_found(client):
|
def test_bad_url_returns_page_not_found(client):
|
||||||
@@ -6,3 +9,32 @@ def test_bad_url_returns_page_not_found(client):
|
|||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||||
assert page.h1.string.strip() == 'Page could not be found'
|
assert page.h1.string.strip() == 'Page could not be found'
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_service_before_request_handles_404(client_request, mocker):
|
||||||
|
exc = HTTPError(Response(status=404), 'Not found')
|
||||||
|
get_service = mocker.patch('app.service_api_client.get_service', side_effect=exc)
|
||||||
|
|
||||||
|
client_request.get(
|
||||||
|
'main.service_dashboard',
|
||||||
|
service_id='00000000-0000-0000-0000-000000000000',
|
||||||
|
_expected_status=404
|
||||||
|
)
|
||||||
|
|
||||||
|
get_service.assert_called_once_with('00000000-0000-0000-0000-000000000000')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('url', [
|
||||||
|
'/invitation/MALFORMED_TOKEN',
|
||||||
|
'/new-password/MALFORMED_TOKEN',
|
||||||
|
'/user-profile/email/confirm/MALFORMED_TOKEN',
|
||||||
|
'/verify-email/MALFORMED_TOKEN'
|
||||||
|
])
|
||||||
|
def test_malformed_token_returns_page_not_found(logged_in_client, url):
|
||||||
|
response = logged_in_client.get(url)
|
||||||
|
|
||||||
|
assert response.status_code == 404
|
||||||
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||||
|
assert page.h1.string.strip() == 'Page could not be found'
|
||||||
|
flash_banner = page.find('div', class_='banner-dangerous').string.strip()
|
||||||
|
assert flash_banner == "There’s something wrong with the link you’ve used."
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from flask import url_for
|
from flask import url_for
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from unittest.mock import ANY
|
from unittest.mock import ANY
|
||||||
|
from itsdangerous import SignatureExpired
|
||||||
|
|
||||||
import app
|
import app
|
||||||
|
|
||||||
@@ -13,14 +14,14 @@ def test_existing_user_accept_invite_calls_api_and_redirects_to_dashboard(
|
|||||||
client,
|
client,
|
||||||
service_one,
|
service_one,
|
||||||
api_user_active,
|
api_user_active,
|
||||||
sample_invite,
|
|
||||||
mock_get_service,
|
|
||||||
mock_check_invite_token,
|
mock_check_invite_token,
|
||||||
mock_get_user_by_email,
|
mock_get_user_by_email,
|
||||||
mock_get_users_by_service,
|
mock_get_users_by_service,
|
||||||
mock_accept_invite,
|
mock_accept_invite,
|
||||||
mock_add_user_to_service,
|
mock_add_user_to_service,
|
||||||
|
mocker,
|
||||||
):
|
):
|
||||||
|
mocker.patch('app.main.views.invites.check_token')
|
||||||
|
|
||||||
expected_service = service_one['id']
|
expected_service = service_one['id']
|
||||||
expected_redirect_location = 'http://localhost/services/{}/dashboard'.format(expected_service)
|
expected_redirect_location = 'http://localhost/services/{}/dashboard'.format(expected_service)
|
||||||
@@ -47,8 +48,8 @@ def test_existing_user_with_no_permissions_accept_invite(
|
|||||||
mock_get_user_by_email,
|
mock_get_user_by_email,
|
||||||
mock_get_users_by_service,
|
mock_get_users_by_service,
|
||||||
mock_add_user_to_service,
|
mock_add_user_to_service,
|
||||||
mock_get_service,
|
|
||||||
):
|
):
|
||||||
|
mocker.patch('app.main.views.invites.check_token')
|
||||||
|
|
||||||
expected_service = service_one['id']
|
expected_service = service_one['id']
|
||||||
sample_invite['permissions'] = ''
|
sample_invite['permissions'] = ''
|
||||||
@@ -67,6 +68,7 @@ def test_if_existing_user_accepts_twice_they_redirect_to_sign_in(
|
|||||||
sample_invite,
|
sample_invite,
|
||||||
mock_get_service,
|
mock_get_service,
|
||||||
):
|
):
|
||||||
|
mocker.patch('app.main.views.invites.check_token')
|
||||||
|
|
||||||
sample_invite['status'] = 'accepted'
|
sample_invite['status'] = 'accepted'
|
||||||
invite = InvitedUser(**sample_invite)
|
invite = InvitedUser(**sample_invite)
|
||||||
@@ -93,6 +95,7 @@ def test_existing_user_of_service_get_redirected_to_signin(
|
|||||||
mock_get_user_by_email,
|
mock_get_user_by_email,
|
||||||
mock_accept_invite,
|
mock_accept_invite,
|
||||||
):
|
):
|
||||||
|
mocker.patch('app.main.views.invites.check_token')
|
||||||
sample_invite['email_address'] = api_user_active.email_address
|
sample_invite['email_address'] = api_user_active.email_address
|
||||||
invite = InvitedUser(**sample_invite)
|
invite = InvitedUser(**sample_invite)
|
||||||
mocker.patch('app.invite_api_client.check_token', return_value=invite)
|
mocker.patch('app.invite_api_client.check_token', return_value=invite)
|
||||||
@@ -122,7 +125,9 @@ def test_existing_signed_out_user_accept_invite_redirects_to_sign_in(
|
|||||||
mock_add_user_to_service,
|
mock_add_user_to_service,
|
||||||
mock_accept_invite,
|
mock_accept_invite,
|
||||||
mock_get_service,
|
mock_get_service,
|
||||||
|
mocker,
|
||||||
):
|
):
|
||||||
|
mocker.patch('app.main.views.invites.check_token')
|
||||||
|
|
||||||
expected_service = service_one['id']
|
expected_service = service_one['id']
|
||||||
expected_permissions = ['send_messages', 'manage_service', 'manage_api_keys']
|
expected_permissions = ['send_messages', 'manage_service', 'manage_api_keys']
|
||||||
@@ -153,7 +158,9 @@ def test_new_user_accept_invite_calls_api_and_redirects_to_registration(
|
|||||||
mock_add_user_to_service,
|
mock_add_user_to_service,
|
||||||
mock_get_users_by_service,
|
mock_get_users_by_service,
|
||||||
mock_get_service,
|
mock_get_service,
|
||||||
|
mocker,
|
||||||
):
|
):
|
||||||
|
mocker.patch('app.main.views.invites.check_token')
|
||||||
|
|
||||||
expected_redirect_location = 'http://localhost/register-from-invite'
|
expected_redirect_location = 'http://localhost/register-from-invite'
|
||||||
|
|
||||||
@@ -174,7 +181,9 @@ def test_new_user_accept_invite_calls_api_and_views_registration_page(
|
|||||||
mock_add_user_to_service,
|
mock_add_user_to_service,
|
||||||
mock_get_users_by_service,
|
mock_get_users_by_service,
|
||||||
mock_get_service,
|
mock_get_service,
|
||||||
|
mocker,
|
||||||
):
|
):
|
||||||
|
mocker.patch('app.main.views.invites.check_token')
|
||||||
|
|
||||||
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'), follow_redirects=True)
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'), follow_redirects=True)
|
||||||
|
|
||||||
@@ -209,6 +218,7 @@ def test_cancelled_invited_user_accepts_invited_redirect_to_cancelled_invitation
|
|||||||
mock_get_user,
|
mock_get_user,
|
||||||
mock_get_service,
|
mock_get_service,
|
||||||
):
|
):
|
||||||
|
mocker.patch('app.main.views.invites.check_token')
|
||||||
cancelled_invitation = create_sample_invite(mocker, service_one, status='cancelled')
|
cancelled_invitation = create_sample_invite(mocker, service_one, status='cancelled')
|
||||||
mock_check_token_invite(mocker, cancelled_invitation)
|
mock_check_token_invite(mocker, cancelled_invitation)
|
||||||
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
|
||||||
@@ -233,7 +243,9 @@ def test_new_user_accept_invite_completes_new_registration_redirects_to_verify(
|
|||||||
mock_get_users_by_service,
|
mock_get_users_by_service,
|
||||||
mock_add_user_to_service,
|
mock_add_user_to_service,
|
||||||
mock_get_service,
|
mock_get_service,
|
||||||
|
mocker,
|
||||||
):
|
):
|
||||||
|
mocker.patch('app.main.views.invites.check_token')
|
||||||
|
|
||||||
expected_service = service_one['id']
|
expected_service = service_one['id']
|
||||||
expected_email = sample_invite['email_address']
|
expected_email = sample_invite['email_address']
|
||||||
@@ -282,6 +294,7 @@ def test_signed_in_existing_user_cannot_use_anothers_invite(
|
|||||||
mock_accept_invite,
|
mock_accept_invite,
|
||||||
mock_get_service,
|
mock_get_service,
|
||||||
):
|
):
|
||||||
|
mocker.patch('app.main.views.invites.check_token')
|
||||||
invite = InvitedUser(**sample_invite)
|
invite = InvitedUser(**sample_invite)
|
||||||
mocker.patch('app.invite_api_client.check_token', return_value=invite)
|
mocker.patch('app.invite_api_client.check_token', return_value=invite)
|
||||||
mocker.patch('app.user_api_client.get_users_for_service', return_value=[api_user_active])
|
mocker.patch('app.user_api_client.get_users_for_service', return_value=[api_user_active])
|
||||||
@@ -322,7 +335,9 @@ def test_new_invited_user_verifies_and_added_to_service(
|
|||||||
mock_get_users_by_service,
|
mock_get_users_by_service,
|
||||||
mock_get_detailed_service,
|
mock_get_detailed_service,
|
||||||
mock_get_usage,
|
mock_get_usage,
|
||||||
|
mocker,
|
||||||
):
|
):
|
||||||
|
mocker.patch('app.main.views.invites.check_token')
|
||||||
|
|
||||||
# visit accept token page
|
# visit accept token page
|
||||||
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
|
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
|
||||||
@@ -354,3 +369,21 @@ def test_new_invited_user_verifies_and_added_to_service(
|
|||||||
raw_html = response.data.decode('utf-8')
|
raw_html = response.data.decode('utf-8')
|
||||||
page = BeautifulSoup(raw_html, 'html.parser')
|
page = BeautifulSoup(raw_html, 'html.parser')
|
||||||
assert page.find('h1').text == 'Dashboard'
|
assert page.find('h1').text == 'Dashboard'
|
||||||
|
|
||||||
|
|
||||||
|
def test_gives_message_if_token_has_expired(
|
||||||
|
app_,
|
||||||
|
client,
|
||||||
|
mock_check_invite_token,
|
||||||
|
mocker,
|
||||||
|
):
|
||||||
|
check_token = mocker.patch('app.main.views.invites.check_token', side_effect=SignatureExpired('this is too old'))
|
||||||
|
|
||||||
|
response = client.get(url_for('main.accept_invite', token='a really old token'))
|
||||||
|
raw_html = response.data.decode('utf-8')
|
||||||
|
page = BeautifulSoup(raw_html, 'html.parser')
|
||||||
|
|
||||||
|
check_token.assert_called_once_with(ANY, ANY, ANY, 3600 * 24 * 2)
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert 'Your invitation to GOV.UK Notify has expired' in page.find('h1').text
|
||||||
|
assert not mock_check_invite_token.called
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ def test_owasp_useful_headers_set(client, mocker):
|
|||||||
assert response.headers['Content-Security-Policy'] == (
|
assert response.headers['Content-Security-Policy'] == (
|
||||||
"default-src 'self' 'unsafe-inline';"
|
"default-src 'self' 'unsafe-inline';"
|
||||||
"script-src 'self' *.google-analytics.com 'unsafe-inline' 'unsafe-eval' data:;"
|
"script-src 'self' *.google-analytics.com 'unsafe-inline' 'unsafe-eval' data:;"
|
||||||
|
"connect-src 'self' *.google-analytics.com;"
|
||||||
"object-src 'self';"
|
"object-src 'self';"
|
||||||
"font-src 'self' data:;"
|
"font-src 'self' data:;"
|
||||||
"img-src 'self' *.google-analytics.com *.notifications.service.gov.uk static-logos.test.com data:;"
|
"img-src 'self' *.google-analytics.com *.notifications.service.gov.uk static-logos.test.com data:;"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ def test_logged_in_user_redirects_to_choose_service(
|
|||||||
|
|
||||||
@pytest.mark.parametrize('view', [
|
@pytest.mark.parametrize('view', [
|
||||||
'cookies', 'using_notify', 'pricing', 'terms', 'integration_testing', 'roadmap',
|
'cookies', 'using_notify', 'pricing', 'terms', 'integration_testing', 'roadmap',
|
||||||
'features', 'information_risk_management'
|
'features', 'information_risk_management', 'callbacks'
|
||||||
])
|
])
|
||||||
def test_static_pages(
|
def test_static_pages(
|
||||||
client,
|
client,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from tests.conftest import (
|
|||||||
SERVICE_ONE_ID,
|
SERVICE_ONE_ID,
|
||||||
active_user_with_permissions,
|
active_user_with_permissions,
|
||||||
active_user_view_permissions,
|
active_user_view_permissions,
|
||||||
|
active_user_no_mobile,
|
||||||
active_user_manage_template_permission,
|
active_user_manage_template_permission,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,6 +56,94 @@ def test_should_show_overview_page(
|
|||||||
app.user_api_client.get_users_for_service.assert_called_once_with(service_id=SERVICE_ONE_ID)
|
app.user_api_client.get_users_for_service.assert_called_once_with(service_id=SERVICE_ONE_ID)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('endpoint, extra_args, service_has_email_auth, auth_options_hidden', [
|
||||||
|
(
|
||||||
|
'main.edit_user_permissions',
|
||||||
|
{'user_id': 0},
|
||||||
|
True,
|
||||||
|
False
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'main.edit_user_permissions',
|
||||||
|
{'user_id': 0},
|
||||||
|
False,
|
||||||
|
True
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'main.invite_user',
|
||||||
|
{},
|
||||||
|
True,
|
||||||
|
False
|
||||||
|
),
|
||||||
|
(
|
||||||
|
'main.invite_user',
|
||||||
|
{},
|
||||||
|
False,
|
||||||
|
True
|
||||||
|
)
|
||||||
|
])
|
||||||
|
def test_service_with_no_email_auth_hides_auth_type_options(
|
||||||
|
client_request,
|
||||||
|
endpoint,
|
||||||
|
extra_args,
|
||||||
|
service_has_email_auth,
|
||||||
|
auth_options_hidden,
|
||||||
|
service_one
|
||||||
|
):
|
||||||
|
if service_has_email_auth:
|
||||||
|
service_one['permissions'].append('email_auth')
|
||||||
|
page = client_request.get(endpoint, service_id=service_one['id'], **extra_args)
|
||||||
|
assert (page.find('input', attrs={"name": "login_authentication"}) is None) == auth_options_hidden
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('service_has_email_auth, displays_auth_type', [
|
||||||
|
(True, True),
|
||||||
|
(False, False)
|
||||||
|
])
|
||||||
|
def test_manage_users_page_shows_member_auth_type_if_service_has_email_auth_activated(
|
||||||
|
client_request,
|
||||||
|
service_has_email_auth,
|
||||||
|
service_one,
|
||||||
|
mock_get_users_by_service,
|
||||||
|
mock_get_invites_for_service,
|
||||||
|
displays_auth_type
|
||||||
|
):
|
||||||
|
if service_has_email_auth:
|
||||||
|
service_one['permissions'].append('email_auth')
|
||||||
|
page = client_request.get('main.manage_users', service_id=service_one['id'])
|
||||||
|
assert bool(page.select_one('.tick-cross-list-hint')) == displays_auth_type
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('user, sms_option_disabled', [
|
||||||
|
(
|
||||||
|
active_user_no_mobile,
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
active_user_with_permissions,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
def test_user_with_no_mobile_number_cant_be_set_to_sms_auth(
|
||||||
|
client_request,
|
||||||
|
user,
|
||||||
|
sms_option_disabled,
|
||||||
|
service_one,
|
||||||
|
mocker
|
||||||
|
):
|
||||||
|
service_one['permissions'].append('email_auth')
|
||||||
|
test_user = mocker.patch('app.user_api_client.get_user', return_value=user(mocker))
|
||||||
|
|
||||||
|
page = client_request.get(
|
||||||
|
'main.edit_user_permissions',
|
||||||
|
service_id=service_one['id'],
|
||||||
|
user_id=test_user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
sms_auth_radio_button = page.select_one('input[value="sms_auth"]')
|
||||||
|
assert sms_auth_radio_button.has_attr("disabled") == sms_option_disabled
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('endpoint, extra_args, expected_checkboxes', [
|
@pytest.mark.parametrize('endpoint, extra_args, expected_checkboxes', [
|
||||||
(
|
(
|
||||||
'main.edit_user_permissions',
|
'main.edit_user_permissions',
|
||||||
@@ -167,6 +256,60 @@ def test_edit_some_user_permissions(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('auth_type', ['email_auth', 'sms_auth'])
|
||||||
|
def test_edit_user_permissions_including_authentication_with_email_auth_service(
|
||||||
|
logged_in_client,
|
||||||
|
active_user_with_permissions,
|
||||||
|
mocker,
|
||||||
|
mock_get_invites_for_service,
|
||||||
|
mock_set_user_permissions,
|
||||||
|
mock_update_user_attribute,
|
||||||
|
service_one,
|
||||||
|
auth_type
|
||||||
|
):
|
||||||
|
service_one['permissions'].append('email_auth')
|
||||||
|
|
||||||
|
response = logged_in_client.post(
|
||||||
|
url_for(
|
||||||
|
'main.edit_user_permissions',
|
||||||
|
service_id=service_one['id'],
|
||||||
|
user_id=active_user_with_permissions.id
|
||||||
|
),
|
||||||
|
data={
|
||||||
|
'email_address': active_user_with_permissions.email_address,
|
||||||
|
'send_messages': 'y',
|
||||||
|
'manage_templates': 'y',
|
||||||
|
'manage_service': 'y',
|
||||||
|
'manage_api_keys': 'y',
|
||||||
|
'login_authentication': auth_type
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_set_user_permissions.assert_called_with(
|
||||||
|
str(active_user_with_permissions.id),
|
||||||
|
service_one['id'],
|
||||||
|
permissions={
|
||||||
|
'send_texts',
|
||||||
|
'send_emails',
|
||||||
|
'send_letters',
|
||||||
|
'manage_users',
|
||||||
|
'manage_templates',
|
||||||
|
'manage_settings',
|
||||||
|
'manage_api_keys',
|
||||||
|
'view_activity'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
mock_update_user_attribute.assert_called_with(
|
||||||
|
str(active_user_with_permissions.id),
|
||||||
|
auth_type=auth_type
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert response.location == url_for(
|
||||||
|
'main.manage_users', service_id=service_one['id'], _external=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_should_show_page_for_inviting_user(
|
def test_should_show_page_for_inviting_user(
|
||||||
logged_in_client,
|
logged_in_client,
|
||||||
active_user_with_permissions,
|
active_user_with_permissions,
|
||||||
@@ -220,7 +363,60 @@ def test_invite_user(
|
|||||||
app.invite_api_client.create_invite.assert_called_once_with(sample_invite['from_user'],
|
app.invite_api_client.create_invite.assert_called_once_with(sample_invite['from_user'],
|
||||||
sample_invite['service'],
|
sample_invite['service'],
|
||||||
email_address,
|
email_address,
|
||||||
expected_permissions)
|
expected_permissions,
|
||||||
|
'sms_auth')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('auth_type', [
|
||||||
|
('sms_auth'),
|
||||||
|
('email_auth')
|
||||||
|
])
|
||||||
|
@pytest.mark.parametrize('email_address, gov_user', [
|
||||||
|
('test@example.gov.uk', True),
|
||||||
|
('test@nonwhitelist.com', False)
|
||||||
|
])
|
||||||
|
def test_invite_user_with_email_auth_service(
|
||||||
|
logged_in_client,
|
||||||
|
active_user_with_permissions,
|
||||||
|
sample_invite,
|
||||||
|
email_address,
|
||||||
|
gov_user,
|
||||||
|
mocker,
|
||||||
|
service_one,
|
||||||
|
auth_type
|
||||||
|
):
|
||||||
|
service_one['permissions'].append('email_auth')
|
||||||
|
sample_invite['email_address'] = 'test@example.gov.uk'
|
||||||
|
|
||||||
|
data = [InvitedUser(**sample_invite)]
|
||||||
|
assert is_gov_user(email_address) == gov_user
|
||||||
|
mocker.patch('app.invite_api_client.get_invites_for_service', return_value=data)
|
||||||
|
mocker.patch('app.user_api_client.get_users_for_service', return_value=[active_user_with_permissions])
|
||||||
|
mocker.patch('app.invite_api_client.create_invite', return_value=InvitedUser(**sample_invite))
|
||||||
|
response = logged_in_client.post(
|
||||||
|
url_for('main.invite_user', service_id=service_one['id']),
|
||||||
|
data={'email_address': email_address,
|
||||||
|
'send_messages': 'y',
|
||||||
|
'manage_templates': 'y',
|
||||||
|
'manage_service': 'y',
|
||||||
|
'manage_api_keys': 'y',
|
||||||
|
'login_authentication': auth_type},
|
||||||
|
follow_redirects=True
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||||
|
assert page.h1.string.strip() == 'Team members'
|
||||||
|
flash_banner = page.find('div', class_='banner-default-with-tick').string.strip()
|
||||||
|
assert flash_banner == 'Invite sent to test@example.gov.uk'
|
||||||
|
|
||||||
|
expected_permissions = 'manage_api_keys,manage_settings,manage_templates,manage_users,send_emails,send_letters,send_texts,view_activity' # noqa
|
||||||
|
|
||||||
|
app.invite_api_client.create_invite.assert_called_once_with(sample_invite['from_user'],
|
||||||
|
sample_invite['service'],
|
||||||
|
email_address,
|
||||||
|
expected_permissions,
|
||||||
|
auth_type)
|
||||||
|
|
||||||
|
|
||||||
def test_cancel_invited_user_cancels_user_invitations(
|
def test_cancel_invited_user_cancels_user_invitations(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from itsdangerous import SignatureExpired
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from notifications_utils.url_safe_token import generate_token
|
from notifications_utils.url_safe_token import generate_token
|
||||||
|
|
||||||
@@ -69,13 +70,13 @@ def test_should_redirect_index_if_user_has_already_changed_password(
|
|||||||
def test_should_redirect_to_forgot_password_with_flash_message_when_token_is_expired(
|
def test_should_redirect_to_forgot_password_with_flash_message_when_token_is_expired(
|
||||||
app_,
|
app_,
|
||||||
client,
|
client,
|
||||||
mock_get_user_by_email_request_password_reset,
|
|
||||||
mock_login,
|
mock_login,
|
||||||
|
mocker
|
||||||
):
|
):
|
||||||
app_.config['TOKEN_MAX_AGE_SECONDS'] = -1000
|
mocker.patch('app.main.views.new_password.check_token', side_effect=SignatureExpired('expired'))
|
||||||
user = mock_get_user_by_email_request_password_reset.return_value
|
token = generate_token('foo@bar.com', app_.config['SECRET_KEY'], app_.config['DANGEROUS_SALT'])
|
||||||
token = generate_token(user.email_address, app_.config['SECRET_KEY'], app_.config['DANGEROUS_SALT'])
|
|
||||||
response = client.post(url_for('.new_password', token=token), data={'new_password': 'a-new_password'})
|
response = client.get(url_for('.new_password', token=token))
|
||||||
|
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert response.location == url_for('.forgot_password', _external=True)
|
assert response.location == url_for('.forgot_password', _external=True)
|
||||||
app_.config['TOKEN_MAX_AGE_SECONDS'] = 3600
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ def test_should_render_platform_admin_page(
|
|||||||
response = client.get(url_for(endpoint))
|
response = client.get(url_for(endpoint))
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||||
assert len(page.select('tbody tr')) == expected_services_shown * 2 # one row for SMS, one for email
|
assert len(page.select('tbody tr')) == expected_services_shown * 3 # one row for SMS, one for email, one for letter
|
||||||
mock_get_detailed_services.assert_called_once_with({'detailed': True,
|
mock_get_detailed_services.assert_called_once_with({'detailed': True,
|
||||||
'include_from_test_key': True,
|
'include_from_test_key': True,
|
||||||
'only_active': False})
|
'only_active': False})
|
||||||
@@ -543,7 +543,7 @@ def test_should_show_correct_sent_totals_for_platform_admin(
|
|||||||
|
|
||||||
assert email_total == 60
|
assert email_total == 60
|
||||||
assert sms_total == 40
|
assert sms_total == 40
|
||||||
assert letter_total == 45
|
assert letter_total == 60
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('endpoint, restricted, research_mode', [
|
@pytest.mark.parametrize('endpoint, restricted, research_mode', [
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ from tests.conftest import (
|
|||||||
mock_get_live_service,
|
mock_get_live_service,
|
||||||
multiple_reply_to_email_addresses,
|
multiple_reply_to_email_addresses,
|
||||||
no_reply_to_email_addresses,
|
no_reply_to_email_addresses,
|
||||||
|
multiple_sms_senders,
|
||||||
|
no_sms_senders,
|
||||||
|
multiple_sms_senders_with_diff_default,
|
||||||
|
multiple_sms_senders_no_inbound
|
||||||
)
|
)
|
||||||
|
|
||||||
template_types = ['email', 'sms']
|
template_types = ['email', 'sms']
|
||||||
@@ -43,6 +47,12 @@ test_non_spreadsheet_files = glob(path.join('tests', 'non_spreadsheet_files', '*
|
|||||||
'Choose where to send replies',
|
'Choose where to send replies',
|
||||||
'Select an email address that recipients can reply to'
|
'Select an email address that recipients can reply to'
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
mock_get_service_template,
|
||||||
|
multiple_sms_senders,
|
||||||
|
'Chose text message sender',
|
||||||
|
'Select a text message sender that the recipients can reply to'
|
||||||
|
)
|
||||||
])
|
])
|
||||||
def test_show_correct_title_and_description_for_sender_type(
|
def test_show_correct_title_and_description_for_sender_type(
|
||||||
client_request,
|
client_request,
|
||||||
@@ -67,12 +77,47 @@ def test_show_correct_title_and_description_for_sender_type(
|
|||||||
assert normalize_spaces(page.select_one('legend').text) == expected_description
|
assert normalize_spaces(page.select_one('legend').text) == expected_description
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('template_mock, sender_data', [
|
||||||
|
(
|
||||||
|
mock_get_service_email_template,
|
||||||
|
multiple_reply_to_email_addresses,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
mock_get_service_template,
|
||||||
|
multiple_sms_senders_with_diff_default
|
||||||
|
),
|
||||||
|
(
|
||||||
|
mock_get_service_template,
|
||||||
|
multiple_sms_senders_no_inbound
|
||||||
|
)
|
||||||
|
])
|
||||||
def test_default_sender_is_checked_and_has_hint(
|
def test_default_sender_is_checked_and_has_hint(
|
||||||
client_request,
|
client_request,
|
||||||
service_one,
|
service_one,
|
||||||
fake_uuid,
|
fake_uuid,
|
||||||
mock_get_service_email_template,
|
template_mock,
|
||||||
multiple_reply_to_email_addresses
|
sender_data,
|
||||||
|
mocker
|
||||||
|
):
|
||||||
|
template_mock(mocker)
|
||||||
|
sender_data(mocker)
|
||||||
|
page = client_request.get(
|
||||||
|
'.set_sender',
|
||||||
|
service_id=service_one['id'],
|
||||||
|
template_id=fake_uuid
|
||||||
|
)
|
||||||
|
|
||||||
|
assert page.select('.multiple-choice input')[0].has_attr('checked')
|
||||||
|
assert normalize_spaces(page.select_one('.multiple-choice label .block-label-hint').text) == "(Default)"
|
||||||
|
assert not page.select('.multiple-choice input')[1].has_attr('checked')
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_inbound_sender_is_checked_and_has_hint_with_default_and_receives_text(
|
||||||
|
client_request,
|
||||||
|
service_one,
|
||||||
|
fake_uuid,
|
||||||
|
mock_get_service_template,
|
||||||
|
multiple_sms_senders
|
||||||
):
|
):
|
||||||
page = client_request.get(
|
page = client_request.get(
|
||||||
'.set_sender',
|
'.set_sender',
|
||||||
@@ -81,18 +126,52 @@ def test_default_sender_is_checked_and_has_hint(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert page.select('.multiple-choice input')[0].has_attr('checked')
|
assert page.select('.multiple-choice input')[0].has_attr('checked')
|
||||||
assert normalize_spaces(page.select_one('.multiple-choice label .block-label-hint').text) == "Default"
|
assert normalize_spaces(
|
||||||
|
page.select_one('.multiple-choice label .block-label-hint').text) == "(Default and receives replies)"
|
||||||
assert not page.select('.multiple-choice input')[1].has_attr('checked')
|
assert not page.select('.multiple-choice input')[1].has_attr('checked')
|
||||||
assert not page.select('.multiple-choice input')[2].has_attr('checked')
|
assert not page.select('.multiple-choice input')[2].has_attr('checked')
|
||||||
|
|
||||||
|
|
||||||
|
def test_sms_sender_has_receives_replies_hint(
|
||||||
|
client_request,
|
||||||
|
service_one,
|
||||||
|
fake_uuid,
|
||||||
|
mock_get_service_template,
|
||||||
|
multiple_sms_senders
|
||||||
|
):
|
||||||
|
page = client_request.get(
|
||||||
|
'.set_sender',
|
||||||
|
service_id=service_one['id'],
|
||||||
|
template_id=fake_uuid
|
||||||
|
)
|
||||||
|
|
||||||
|
assert page.select('.multiple-choice input')[0].has_attr('checked')
|
||||||
|
assert normalize_spaces(
|
||||||
|
page.select_one('.multiple-choice label .block-label-hint').text) == "(Default and receives replies)"
|
||||||
|
assert not page.select('.multiple-choice input')[1].has_attr('checked')
|
||||||
|
assert not page.select('.multiple-choice input')[2].has_attr('checked')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('template_mock, sender_data', [
|
||||||
|
(
|
||||||
|
mock_get_service_email_template,
|
||||||
|
multiple_reply_to_email_addresses,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
mock_get_service_template,
|
||||||
|
multiple_sms_senders
|
||||||
|
)
|
||||||
|
])
|
||||||
def test_sender_session_is_present_after_selected(
|
def test_sender_session_is_present_after_selected(
|
||||||
logged_in_client,
|
logged_in_client,
|
||||||
service_one,
|
service_one,
|
||||||
fake_uuid,
|
fake_uuid,
|
||||||
mock_get_service_email_template,
|
template_mock,
|
||||||
multiple_reply_to_email_addresses
|
sender_data,
|
||||||
|
mocker
|
||||||
):
|
):
|
||||||
|
template_mock(mocker)
|
||||||
|
sender_data(mocker)
|
||||||
logged_in_client.post(
|
logged_in_client.post(
|
||||||
url_for('.set_sender', service_id=service_one['id'], template_id=fake_uuid),
|
url_for('.set_sender', service_id=service_one['id'], template_id=fake_uuid),
|
||||||
data={'sender': '1234'}
|
data={'sender': '1234'}
|
||||||
@@ -107,6 +186,10 @@ def test_sender_session_is_present_after_selected(
|
|||||||
mock_get_service_email_template,
|
mock_get_service_email_template,
|
||||||
no_reply_to_email_addresses,
|
no_reply_to_email_addresses,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
mock_get_service_template,
|
||||||
|
no_sms_senders
|
||||||
|
)
|
||||||
])
|
])
|
||||||
def test_set_sender_redirects_if_no_sender_data(
|
def test_set_sender_redirects_if_no_sender_data(
|
||||||
logged_in_client,
|
logged_in_client,
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ from freezegun import freeze_time
|
|||||||
'Label Value Action',
|
'Label Value Action',
|
||||||
'Send text messages On Change',
|
'Send text messages On Change',
|
||||||
'Text message sender GOVUK Manage',
|
'Text message sender GOVUK Manage',
|
||||||
|
'Text messages start with service name On Change',
|
||||||
'International text messages Off Change',
|
'International text messages Off Change',
|
||||||
'Receive text messages Off Change',
|
'Receive text messages Off Change',
|
||||||
|
|
||||||
@@ -64,6 +65,7 @@ from freezegun import freeze_time
|
|||||||
'Label Value Action',
|
'Label Value Action',
|
||||||
'Send text messages On Change',
|
'Send text messages On Change',
|
||||||
'Text message sender GOVUK Manage',
|
'Text message sender GOVUK Manage',
|
||||||
|
'Text messages start with service name On Change',
|
||||||
'International text messages Off Change',
|
'International text messages Off Change',
|
||||||
'Receive text messages Off Change',
|
'Receive text messages Off Change',
|
||||||
|
|
||||||
@@ -121,9 +123,10 @@ def test_should_show_overview(
|
|||||||
'Label Value Action',
|
'Label Value Action',
|
||||||
'Send text messages On Change',
|
'Send text messages On Change',
|
||||||
'Text message sender GOVUK Manage',
|
'Text message sender GOVUK Manage',
|
||||||
|
'Text messages start with service name On Change',
|
||||||
'International text messages On Change',
|
'International text messages On Change',
|
||||||
'Receive text messages On Change',
|
'Receive text messages On Change',
|
||||||
'API endpoint for received text messages Not set Change',
|
'Callback URL for received text messages Not set Change',
|
||||||
|
|
||||||
'Label Value Action',
|
'Label Value Action',
|
||||||
'Send letters Off Change',
|
'Send letters Off Change',
|
||||||
@@ -140,6 +143,7 @@ def test_should_show_overview(
|
|||||||
'Label Value Action',
|
'Label Value Action',
|
||||||
'Send text messages On Change',
|
'Send text messages On Change',
|
||||||
'Text message sender GOVUK Manage',
|
'Text message sender GOVUK Manage',
|
||||||
|
'Text messages start with service name On Change',
|
||||||
'International text messages Off Change',
|
'International text messages Off Change',
|
||||||
'Receive text messages Off Change',
|
'Receive text messages Off Change',
|
||||||
|
|
||||||
@@ -209,7 +213,7 @@ def test_service_settings_show_elided_api_url_if_needed(
|
|||||||
|
|
||||||
non_empty_trs = [tr.find_all('td') for tr in page.find_all('tr') if tr.find_all('td')]
|
non_empty_trs = [tr.find_all('td') for tr in page.find_all('tr') if tr.find_all('td')]
|
||||||
api_url = [api_setting[1].text.strip() for api_setting in non_empty_trs
|
api_url = [api_setting[1].text.strip() for api_setting in non_empty_trs
|
||||||
if api_setting[0].text.strip() == 'API endpoint for received text messages'][0]
|
if api_setting[0].text.strip() == 'Callback URL for received text messages'][0]
|
||||||
assert api_url == elided_url
|
assert api_url == elided_url
|
||||||
assert mocked_get_fn.called is True
|
assert mocked_get_fn.called is True
|
||||||
|
|
||||||
@@ -715,7 +719,7 @@ def test_and_more_hint_appears_on_settings_with_more_than_just_a_single_sender(
|
|||||||
|
|
||||||
assert get_row(page, 2) == "Email reply to addresses test@example.com …and 2 more Manage"
|
assert get_row(page, 2) == "Email reply to addresses test@example.com …and 2 more Manage"
|
||||||
assert get_row(page, 4) == "Text message sender Example …and 2 more Manage"
|
assert get_row(page, 4) == "Text message sender Example …and 2 more Manage"
|
||||||
assert get_row(page, 8) == "Sender addresses 1 Example Street …and 2 more Manage"
|
assert get_row(page, 9) == "Sender addresses 1 Example Street …and 2 more Manage"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('sender_list_page, expected_output', [
|
@pytest.mark.parametrize('sender_list_page, expected_output', [
|
||||||
@@ -763,7 +767,7 @@ def test_api_ids_dont_show_on_option_pages_with_a_single_sender(
|
|||||||
), (
|
), (
|
||||||
'main.service_sms_senders',
|
'main.service_sms_senders',
|
||||||
multiple_sms_senders,
|
multiple_sms_senders,
|
||||||
'Example (default and recieves replies) Change 1234',
|
'Example (default and receives replies) Change 1234',
|
||||||
'Example 2 Change 5678',
|
'Example 2 Change 5678',
|
||||||
'Example 3 Change 9457'
|
'Example 3 Change 9457'
|
||||||
),
|
),
|
||||||
@@ -1274,7 +1278,7 @@ def test_does_not_show_research_mode_indicator(
|
|||||||
|
|
||||||
@pytest.mark.parametrize('url, bearer_token, expected_errors', [
|
@pytest.mark.parametrize('url, bearer_token, expected_errors', [
|
||||||
("", "", "Can’t be empty Can’t be empty"),
|
("", "", "Can’t be empty Can’t be empty"),
|
||||||
("http://not_https.com", "1234567890", "Must be a valid https url"),
|
("http://not_https.com", "1234567890", "Must be a valid https URL"),
|
||||||
("https://test.com", "123456789", "Must be at least 10 characters"),
|
("https://test.com", "123456789", "Must be at least 10 characters"),
|
||||||
])
|
])
|
||||||
def test_set_inbound_api_validation(
|
def test_set_inbound_api_validation(
|
||||||
@@ -2075,3 +2079,46 @@ def test_empty_letter_contact_block_returns_error(
|
|||||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||||
error_message = page.find('span', class_='error-message').text.strip()
|
error_message = page.find('span', class_='error-message').text.strip()
|
||||||
assert error_message == 'Can’t be empty'
|
assert error_message == 'Can’t be empty'
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_sms_prefixing_setting_page(
|
||||||
|
client_request,
|
||||||
|
mock_update_service,
|
||||||
|
):
|
||||||
|
page = client_request.get(
|
||||||
|
'main.service_set_sms_prefix', service_id=SERVICE_ONE_ID
|
||||||
|
)
|
||||||
|
assert normalize_spaces(page.select_one('legend').text) == (
|
||||||
|
'Start all text messages with ‘service one:’'
|
||||||
|
)
|
||||||
|
radios = page.select('input[type=radio]')
|
||||||
|
assert len(radios) == 2
|
||||||
|
assert radios[0]['value'] == 'on'
|
||||||
|
assert radios[0]['checked'] == ''
|
||||||
|
assert radios[1]['value'] == 'off'
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
assert radios[1]['checked']
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('post_value, expected_api_argument', [
|
||||||
|
('on', True),
|
||||||
|
('off', False),
|
||||||
|
])
|
||||||
|
def test_updates_sms_prefixing(
|
||||||
|
client_request,
|
||||||
|
mock_update_service,
|
||||||
|
post_value,
|
||||||
|
expected_api_argument,
|
||||||
|
):
|
||||||
|
client_request.post(
|
||||||
|
'main.service_set_sms_prefix', service_id=SERVICE_ONE_ID,
|
||||||
|
_data={'enabled': post_value},
|
||||||
|
_expected_redirect=url_for(
|
||||||
|
'main.service_settings', service_id=SERVICE_ONE_ID,
|
||||||
|
_external=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
mock_update_service.assert_called_once_with(
|
||||||
|
service_id=SERVICE_ONE_ID,
|
||||||
|
prefix_sms=expected_api_argument,
|
||||||
|
)
|
||||||
|
|||||||
@@ -335,13 +335,14 @@ def test_should_not_allow_creation_of_a_template_without_correct_permission(
|
|||||||
|
|
||||||
@pytest.mark.parametrize('fixture, expected_status_code', [
|
@pytest.mark.parametrize('fixture, expected_status_code', [
|
||||||
(mock_get_service_email_template, 200),
|
(mock_get_service_email_template, 200),
|
||||||
(mock_get_service_template, 302),
|
(mock_get_service_template, 200),
|
||||||
(mock_get_service_letter_template, 302),
|
(mock_get_service_letter_template, 302),
|
||||||
])
|
])
|
||||||
def test_should_redirect_to_one_off_if_template_type_is_not_email(
|
def test_should_redirect_to_one_off_if_template_type_is_letter(
|
||||||
logged_in_client,
|
logged_in_client,
|
||||||
active_user_with_permissions,
|
active_user_with_permissions,
|
||||||
multiple_reply_to_email_addresses,
|
multiple_reply_to_email_addresses,
|
||||||
|
multiple_sms_senders,
|
||||||
service_one,
|
service_one,
|
||||||
fake_uuid,
|
fake_uuid,
|
||||||
mocker,
|
mocker,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import uuid
|
import uuid
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from itsdangerous import SignatureExpired
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ def test_verify_email_redirects_to_verify_if_token_valid(
|
|||||||
mock_send_verify_code,
|
mock_send_verify_code,
|
||||||
mock_check_verify_code,
|
mock_check_verify_code,
|
||||||
):
|
):
|
||||||
token_data = {"user_id": api_user_pending.id, "secret_code": 12345}
|
token_data = {"user_id": api_user_pending.id, "secret_code": 'UNUSED'}
|
||||||
mocker.patch('app.main.views.verify.check_token', return_value=json.dumps(token_data))
|
mocker.patch('app.main.views.verify.check_token', return_value=json.dumps(token_data))
|
||||||
|
|
||||||
with client.session_transaction() as session:
|
with client.session_transaction() as session:
|
||||||
@@ -108,39 +109,20 @@ def test_verify_email_redirects_to_verify_if_token_valid(
|
|||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
assert response.location == url_for('main.verify', _external=True)
|
assert response.location == url_for('main.verify', _external=True)
|
||||||
|
|
||||||
|
assert not mock_check_verify_code.called
|
||||||
|
mock_send_verify_code.assert_called_once_with(api_user_pending.id, 'sms', api_user_pending.mobile_number)
|
||||||
|
|
||||||
|
with client.session_transaction() as session:
|
||||||
|
assert session['user_details'] == {'email': api_user_pending.email_address, 'id': api_user_pending.id}
|
||||||
|
|
||||||
|
|
||||||
def test_verify_email_redirects_to_email_sent_if_token_expired(
|
def test_verify_email_redirects_to_email_sent_if_token_expired(
|
||||||
client,
|
client,
|
||||||
mocker,
|
mocker,
|
||||||
api_user_pending,
|
api_user_pending,
|
||||||
mock_check_verify_code,
|
|
||||||
):
|
):
|
||||||
from itsdangerous import SignatureExpired
|
|
||||||
mocker.patch('app.main.views.verify.check_token', side_effect=SignatureExpired('expired'))
|
mocker.patch('app.main.views.verify.check_token', side_effect=SignatureExpired('expired'))
|
||||||
|
|
||||||
with client.session_transaction() as session:
|
|
||||||
session['user_details'] = {'email_address': api_user_pending.email_address, 'id': api_user_pending.id}
|
|
||||||
|
|
||||||
response = client.get(url_for('main.verify_email', token='notreal'))
|
|
||||||
|
|
||||||
assert response.status_code == 302
|
|
||||||
assert response.location == url_for('main.resend_email_verification', _external=True)
|
|
||||||
|
|
||||||
|
|
||||||
def test_verify_email_redirects_to_email_sent_if_token_used(
|
|
||||||
client,
|
|
||||||
mocker,
|
|
||||||
api_user_pending,
|
|
||||||
mock_get_user_pending,
|
|
||||||
mock_send_verify_code,
|
|
||||||
mock_check_verify_code_code_expired,
|
|
||||||
):
|
|
||||||
from itsdangerous import SignatureExpired
|
|
||||||
mocker.patch('app.main.views.verify.check_token', side_effect=SignatureExpired('expired'))
|
|
||||||
|
|
||||||
with client.session_transaction() as session:
|
|
||||||
session['user_details'] = {'email_address': api_user_pending.email_address, 'id': api_user_pending.id}
|
|
||||||
|
|
||||||
response = client.get(url_for('main.verify_email', token='notreal'))
|
response = client.get(url_for('main.verify_email', token='notreal'))
|
||||||
|
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
@@ -158,9 +140,6 @@ def test_verify_email_redirects_to_sign_in_if_user_active(
|
|||||||
token_data = {"user_id": api_user_active.id, "secret_code": 12345}
|
token_data = {"user_id": api_user_active.id, "secret_code": 12345}
|
||||||
mocker.patch('app.main.views.verify.check_token', return_value=json.dumps(token_data))
|
mocker.patch('app.main.views.verify.check_token', return_value=json.dumps(token_data))
|
||||||
|
|
||||||
with client.session_transaction() as session:
|
|
||||||
session['user_details'] = {'email_address': api_user_active.email_address, 'id': api_user_active.id}
|
|
||||||
|
|
||||||
response = client.get(url_for('main.verify_email', token='notreal'), follow_redirects=True)
|
response = client.get(url_for('main.verify_email', token='notreal'), follow_redirects=True)
|
||||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||||
assert page.h1.text == 'Sign in'
|
assert page.h1.text == 'Sign in'
|
||||||
|
|||||||
+103
-6
@@ -304,6 +304,66 @@ def multiple_sms_senders(mocker):
|
|||||||
return mocker.patch('app.service_api_client.get_sms_senders', side_effect=_get)
|
return mocker.patch('app.service_api_client.get_sms_senders', side_effect=_get)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def multiple_sms_senders_with_diff_default(mocker):
|
||||||
|
def _get(service_id):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'id': '1234',
|
||||||
|
'service_id': service_id,
|
||||||
|
'sms_sender': 'Example',
|
||||||
|
'is_default': True,
|
||||||
|
'created_at': datetime.utcnow(),
|
||||||
|
'inbound_number_id': None,
|
||||||
|
'updated_at': None
|
||||||
|
}, {
|
||||||
|
'id': '5678',
|
||||||
|
'service_id': service_id,
|
||||||
|
'sms_sender': 'Example 2',
|
||||||
|
'is_default': False,
|
||||||
|
'created_at': datetime.utcnow(),
|
||||||
|
'inbound_number_id': None,
|
||||||
|
'updated_at': None
|
||||||
|
}, {
|
||||||
|
'id': '9457',
|
||||||
|
'service_id': service_id,
|
||||||
|
'sms_sender': 'Example 3',
|
||||||
|
'is_default': False,
|
||||||
|
'created_at': datetime.utcnow(),
|
||||||
|
'inbound_number_id': '12354',
|
||||||
|
'updated_at': None
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return mocker.patch('app.service_api_client.get_sms_senders', side_effect=_get)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def multiple_sms_senders_no_inbound(mocker):
|
||||||
|
def _get(service_id):
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'id': '1234',
|
||||||
|
'service_id': service_id,
|
||||||
|
'sms_sender': 'Example',
|
||||||
|
'is_default': True,
|
||||||
|
'created_at': datetime.utcnow(),
|
||||||
|
'inbound_number_id': None,
|
||||||
|
'updated_at': None
|
||||||
|
}, {
|
||||||
|
'id': '5678',
|
||||||
|
'service_id': service_id,
|
||||||
|
'sms_sender': 'Example 2',
|
||||||
|
'is_default': False,
|
||||||
|
'created_at': datetime.utcnow(),
|
||||||
|
'inbound_number_id': None,
|
||||||
|
'updated_at': None
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
return mocker.patch('app.service_api_client.get_sms_senders', side_effect=_get)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def no_sms_senders(mocker):
|
def no_sms_senders(mocker):
|
||||||
def _get(service_id):
|
def _get(service_id):
|
||||||
@@ -1003,7 +1063,8 @@ def platform_admin_user(fake_uuid):
|
|||||||
'manage_settings',
|
'manage_settings',
|
||||||
'manage_api_keys',
|
'manage_api_keys',
|
||||||
'view_activity']},
|
'view_activity']},
|
||||||
'platform_admin': True
|
'platform_admin': True,
|
||||||
|
'auth_type': 'sms_auth'
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
return user
|
return user
|
||||||
@@ -1021,6 +1082,7 @@ def api_user_active(fake_uuid, email_address='test@user.gov.uk'):
|
|||||||
'failed_login_count': 0,
|
'failed_login_count': 0,
|
||||||
'permissions': {},
|
'permissions': {},
|
||||||
'platform_admin': False,
|
'platform_admin': False,
|
||||||
|
'auth_type': 'sms_auth',
|
||||||
'password_changed_at': str(datetime.utcnow())
|
'password_changed_at': str(datetime.utcnow())
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
@@ -1039,6 +1101,7 @@ def api_nongov_user_active(fake_uuid):
|
|||||||
'failed_login_count': 0,
|
'failed_login_count': 0,
|
||||||
'permissions': {},
|
'permissions': {},
|
||||||
'platform_admin': False,
|
'platform_admin': False,
|
||||||
|
'auth_type': 'sms_auth',
|
||||||
'password_changed_at': str(datetime.utcnow())
|
'password_changed_at': str(datetime.utcnow())
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
@@ -1065,7 +1128,35 @@ def active_user_with_permissions(fake_uuid):
|
|||||||
'manage_settings',
|
'manage_settings',
|
||||||
'manage_api_keys',
|
'manage_api_keys',
|
||||||
'view_activity']},
|
'view_activity']},
|
||||||
'platform_admin': False
|
'platform_admin': False,
|
||||||
|
'auth_type': 'sms_auth'
|
||||||
|
}
|
||||||
|
user = User(user_data)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def active_user_no_mobile(fake_uuid):
|
||||||
|
from app.notify_client.user_api_client import User
|
||||||
|
|
||||||
|
user_data = {'id': fake_uuid,
|
||||||
|
'name': 'Test User',
|
||||||
|
'password': 'somepassword',
|
||||||
|
'password_changed_at': str(datetime.utcnow()),
|
||||||
|
'email_address': 'test@user.gov.uk',
|
||||||
|
'mobile_number': None,
|
||||||
|
'state': 'active',
|
||||||
|
'failed_login_count': 0,
|
||||||
|
'permissions': {SERVICE_ONE_ID: ['send_texts',
|
||||||
|
'send_emails',
|
||||||
|
'send_letters',
|
||||||
|
'manage_users',
|
||||||
|
'manage_templates',
|
||||||
|
'manage_settings',
|
||||||
|
'manage_api_keys',
|
||||||
|
'view_activity']},
|
||||||
|
'platform_admin': False,
|
||||||
|
'auth_type': 'email_auth'
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
return user
|
return user
|
||||||
@@ -1084,7 +1175,8 @@ def active_user_view_permissions(fake_uuid):
|
|||||||
'state': 'active',
|
'state': 'active',
|
||||||
'failed_login_count': 0,
|
'failed_login_count': 0,
|
||||||
'permissions': {SERVICE_ONE_ID: ['view_activity']},
|
'permissions': {SERVICE_ONE_ID: ['view_activity']},
|
||||||
'platform_admin': False
|
'platform_admin': False,
|
||||||
|
'auth_type': 'sms_auth'
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
return user
|
return user
|
||||||
@@ -1107,7 +1199,8 @@ def active_user_manage_template_permission(fake_uuid):
|
|||||||
'manage_templates',
|
'manage_templates',
|
||||||
'view_activity',
|
'view_activity',
|
||||||
]},
|
]},
|
||||||
'platform_admin': False
|
'platform_admin': False,
|
||||||
|
'auth_type': 'sms_auth'
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
return user
|
return user
|
||||||
@@ -1123,7 +1216,8 @@ def api_user_locked(fake_uuid):
|
|||||||
'mobile_number': '07700 900762',
|
'mobile_number': '07700 900762',
|
||||||
'state': 'active',
|
'state': 'active',
|
||||||
'failed_login_count': 5,
|
'failed_login_count': 5,
|
||||||
'permissions': {}
|
'permissions': {},
|
||||||
|
'auth_type': 'sms_auth'
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
return user
|
return user
|
||||||
@@ -1140,7 +1234,8 @@ def api_user_request_password_reset(fake_uuid):
|
|||||||
'state': 'active',
|
'state': 'active',
|
||||||
'failed_login_count': 5,
|
'failed_login_count': 5,
|
||||||
'permissions': {},
|
'permissions': {},
|
||||||
'password_changed_at': None
|
'password_changed_at': None,
|
||||||
|
'auth_type': 'sms_auth'
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
return user
|
return user
|
||||||
@@ -1157,6 +1252,7 @@ def api_user_changed_password(fake_uuid):
|
|||||||
'state': 'active',
|
'state': 'active',
|
||||||
'failed_login_count': 5,
|
'failed_login_count': 5,
|
||||||
'permissions': {},
|
'permissions': {},
|
||||||
|
'auth_type': 'sms_auth',
|
||||||
'password_changed_at': str(datetime.utcnow() + timedelta(minutes=1))
|
'password_changed_at': str(datetime.utcnow() + timedelta(minutes=1))
|
||||||
}
|
}
|
||||||
user = User(user_data)
|
user = User(user_data)
|
||||||
@@ -1753,6 +1849,7 @@ def mock_get_users_by_service(mocker):
|
|||||||
'password_changed_at': None,
|
'password_changed_at': None,
|
||||||
'name': 'Test User',
|
'name': 'Test User',
|
||||||
'email_address': 'notify@digital.cabinet-office.gov.uk',
|
'email_address': 'notify@digital.cabinet-office.gov.uk',
|
||||||
|
'auth_type': 'sms_auth',
|
||||||
'failed_login_count': 0}]
|
'failed_login_count': 0}]
|
||||||
return [User(data[0])]
|
return [User(data[0])]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user