mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-06 06:18:24 -04:00
Merge branch 'master' of https://github.com/alphagov/notifications-admin into vb-free-sms-history
This commit is contained in:
@@ -186,6 +186,14 @@ class PermissionsForm(Form):
|
||||
manage_templates = BooleanField("Add and edit templates")
|
||||
manage_service = BooleanField("Modify this service and its team")
|
||||
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):
|
||||
@@ -693,10 +701,10 @@ class ServiceInboundNumberForm(Form):
|
||||
|
||||
|
||||
class ServiceInboundApiForm(Form):
|
||||
url = StringField("Inbound sms url",
|
||||
url = StringField("Callback URL",
|
||||
validators=[DataRequired(message='Can’t be empty'),
|
||||
Regexp(regex="^https.*",
|
||||
message='Must be a valid https url')]
|
||||
message='Must be a valid https URL')]
|
||||
)
|
||||
bearer_token = PasswordFieldShowHasContent("Bearer token",
|
||||
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(
|
||||
placeholder_name,
|
||||
dict_to_populate_from,
|
||||
|
||||
@@ -135,3 +135,8 @@ def using_notify():
|
||||
@main.route('/information-risk-management')
|
||||
def information_risk_management():
|
||||
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,
|
||||
flash,
|
||||
render_template,
|
||||
abort
|
||||
abort,
|
||||
current_app
|
||||
)
|
||||
from itsdangerous import SignatureExpired
|
||||
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 import (
|
||||
invite_api_client,
|
||||
user_api_client,
|
||||
service_api_client
|
||||
)
|
||||
|
||||
from flask_login import current_user
|
||||
|
||||
|
||||
@main.route("/invitation/<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)
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ from app.main.forms import (
|
||||
InviteUserForm,
|
||||
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
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ def manage_users(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)
|
||||
if invite.status != 'accepted']
|
||||
|
||||
return render_template(
|
||||
'views/manage-users.html',
|
||||
users=users,
|
||||
@@ -51,7 +52,13 @@ def manage_users(service_id):
|
||||
@user_has_permissions('manage_users', admin_override=True)
|
||||
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():
|
||||
email_address = form.email_address.data
|
||||
@@ -60,7 +67,8 @@ def invite_user(service_id):
|
||||
current_user.id,
|
||||
service_id,
|
||||
email_address,
|
||||
permissions
|
||||
permissions,
|
||||
form.login_authentication.data
|
||||
)
|
||||
|
||||
flash('Invite sent to {}'.format(invited_user.email_address), 'default_with_tick')
|
||||
@@ -68,7 +76,8 @@ def invite_user(service_id):
|
||||
|
||||
return render_template(
|
||||
'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
|
||||
@user_has_permissions('manage_users', admin_override=True)
|
||||
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
|
||||
# call as well. eg. /user/<user_id>?&service=service_id
|
||||
user = user_api_client.get_user(user_id)
|
||||
# Need to make the email address read only, or a disabled field?
|
||||
# Do it through the template or the form class?
|
||||
form = PermissionsForm(**{
|
||||
role: user.has_permissions(permissions=permissions) for role, permissions in roles.items()
|
||||
})
|
||||
user_has_no_mobile_number = user.mobile_number is None
|
||||
|
||||
form = PermissionsForm(
|
||||
**{role: user.has_permissions(permissions=permissions) for role, permissions in roles.items()},
|
||||
login_authentication=user.auth_type
|
||||
)
|
||||
|
||||
if form.validate_on_submit():
|
||||
user_api_client.set_user_permissions(
|
||||
user_id, service_id,
|
||||
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 render_template(
|
||||
'views/edit-user-permissions.html',
|
||||
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
|
||||
|
||||
from flask import (render_template, url_for, redirect, flash, session, current_app)
|
||||
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.forms import NewPasswordForm
|
||||
from datetime import datetime
|
||||
from app import user_api_client
|
||||
|
||||
|
||||
@main.route('/new-password/<path:token>', methods=['GET', 'POST'])
|
||||
def new_password(token):
|
||||
from notifications_utils.url_safe_token import check_token
|
||||
try:
|
||||
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:
|
||||
flash('The link in the email we sent you has expired. Enter your email address to resend.')
|
||||
return redirect(url_for('.forgot_password'))
|
||||
|
||||
@@ -113,7 +113,6 @@ def create_global_stats(services):
|
||||
'requested': 0
|
||||
}
|
||||
}
|
||||
|
||||
for service in services:
|
||||
for msg_type, status in itertools.product(('sms', 'email', 'letter'), ('delivered', 'failed', 'requested')):
|
||||
stats[msg_type][status] += service['statistics'][msg_type][status]
|
||||
|
||||
@@ -178,7 +178,7 @@ def set_sender(service_id, template_id):
|
||||
|
||||
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
|
||||
|
||||
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_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():
|
||||
session['sender_id'] = form.sender.data
|
||||
@@ -220,12 +225,24 @@ def get_sender_context(sender_details, template_type):
|
||||
'title': 'Choose sender address',
|
||||
'description': 'Select an address that recipients can reply to',
|
||||
'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]
|
||||
|
||||
sender_format = context['field_name']
|
||||
|
||||
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]
|
||||
return context
|
||||
|
||||
@@ -233,7 +250,8 @@ def get_sender_context(sender_details, template_type):
|
||||
def get_sender_details(service_id, template_type):
|
||||
api_call = {
|
||||
'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]
|
||||
return api_call(service_id)
|
||||
|
||||
@@ -248,7 +266,7 @@ def send_test(service_id, template_id):
|
||||
session['send_test_letter_page_count'] = None
|
||||
|
||||
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
|
||||
|
||||
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'):
|
||||
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(
|
||||
db_template,
|
||||
current_service,
|
||||
@@ -324,7 +347,8 @@ def send_test_step(service_id, template_id, step_index):
|
||||
filetype='png',
|
||||
),
|
||||
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(
|
||||
@@ -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()))
|
||||
|
||||
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(
|
||||
service_api_client.get_service_template(
|
||||
service_id,
|
||||
@@ -463,7 +493,8 @@ def _check_messages(service_id, template_type, upload_id, letters_as_pdf=False):
|
||||
upload_id=upload_id,
|
||||
filetype='png',
|
||||
) 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(
|
||||
contents,
|
||||
@@ -723,12 +754,18 @@ def check_notification(service_id, template_id):
|
||||
|
||||
def _check_notification(service_id, template_id, exception=None):
|
||||
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(
|
||||
db_template,
|
||||
current_service,
|
||||
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
|
||||
@@ -814,3 +851,10 @@ def get_email_reply_to_address_from_session(service_id):
|
||||
return service_api_client.get_reply_to_email_address(
|
||||
service_id, session['sender_id']
|
||||
)['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,
|
||||
FreeSMSAllowance,
|
||||
ServiceEditInboundNumberForm,
|
||||
SMSPrefixForm,
|
||||
)
|
||||
from app import user_api_client, current_service, organisations_client, inbound_number_client, billing_api_client
|
||||
from notifications_utils.formatters import formatted_list
|
||||
@@ -108,7 +109,9 @@ def service_settings(service_id):
|
||||
letter_contact_details_count=letter_contact_details_count,
|
||||
default_sms_sender=default_sms_sender,
|
||||
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'])
|
||||
@login_required
|
||||
@user_has_permissions('manage_settings', admin_override=True)
|
||||
@@ -595,7 +622,7 @@ def service_sms_senders(service_id):
|
||||
if sender['is_default']:
|
||||
hints += ["default"]
|
||||
if sender['inbound_number_id']:
|
||||
hints += ["recieves replies"]
|
||||
hints += ["receives replies"]
|
||||
if hints:
|
||||
sender['hint'] = "(" + " and ".join(hints) + ")"
|
||||
|
||||
|
||||
@@ -50,34 +50,26 @@ def verify():
|
||||
@main.route('/verify-email/<token>')
|
||||
def verify_email(token):
|
||||
try:
|
||||
token_data = check_token(token,
|
||||
current_app.config['SECRET_KEY'],
|
||||
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'))
|
||||
|
||||
token_data = check_token(
|
||||
token,
|
||||
current_app.config['SECRET_KEY'],
|
||||
current_app.config['DANGEROUS_SALT'],
|
||||
current_app.config['EMAIL_EXPIRY_SECONDS']
|
||||
)
|
||||
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'))
|
||||
|
||||
# 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')
|
||||
|
||||
Reference in New Issue
Block a user