mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-08-23 15:57:23 -04:00
invite-team-members
This commit is contained in:
@@ -237,6 +237,25 @@ class RegisterUserFromInviteForm(StripWhitespaceForm):
|
||||
raise ValidationError('Can’t be empty')
|
||||
|
||||
|
||||
class RegisterUserFromOrgInviteForm(StripWhitespaceForm):
|
||||
def __init__(self, invited_org_user):
|
||||
super().__init__(
|
||||
organisation=invited_org_user['organisation'],
|
||||
email_address=invited_org_user['email_address'],
|
||||
)
|
||||
|
||||
name = StringField(
|
||||
'Full name',
|
||||
validators=[DataRequired(message='Can’t be empty')]
|
||||
)
|
||||
|
||||
mobile_number = InternationalPhoneNumber('Mobile number', validators=[DataRequired(message='Can’t be empty')])
|
||||
password = password()
|
||||
organisation = HiddenField('organisation')
|
||||
email_address = HiddenField('email_address')
|
||||
auth_type = HiddenField('auth_type', validators=[DataRequired()])
|
||||
|
||||
|
||||
class PermissionsForm(StripWhitespaceForm):
|
||||
send_messages = BooleanField("Send messages from existing templates")
|
||||
manage_templates = BooleanField("Add and edit templates")
|
||||
@@ -264,6 +283,18 @@ class InviteUserForm(PermissionsForm):
|
||||
raise ValidationError("You can’t send an invitation to yourself")
|
||||
|
||||
|
||||
class InviteOrgUserForm(StripWhitespaceForm):
|
||||
email_address = email_address(gov_user=False)
|
||||
|
||||
def __init__(self, invalid_email_address, *args, **kwargs):
|
||||
super(InviteOrgUserForm, self).__init__(*args, **kwargs)
|
||||
self.invalid_email_address = invalid_email_address.lower()
|
||||
|
||||
def validate_email_address(self, field):
|
||||
if field.data.lower() == self.invalid_email_address:
|
||||
raise ValidationError("You can’t send an invitation to yourself")
|
||||
|
||||
|
||||
class TwoFactorForm(StripWhitespaceForm):
|
||||
def __init__(self, validate_code_func, *args, **kwargs):
|
||||
'''
|
||||
@@ -419,7 +450,7 @@ class ChangeEmailForm(StripWhitespaceForm):
|
||||
|
||||
def validate_email_address(self, field):
|
||||
is_valid = self.validate_email_func(field.data)
|
||||
if not is_valid:
|
||||
if is_valid:
|
||||
raise ValidationError("The email address is already in use")
|
||||
|
||||
|
||||
|
||||
@@ -4,38 +4,23 @@ from flask import (
|
||||
session,
|
||||
flash,
|
||||
render_template,
|
||||
abort,
|
||||
current_app
|
||||
abort
|
||||
)
|
||||
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,
|
||||
org_invite_api_client,
|
||||
user_api_client,
|
||||
organisations_client,
|
||||
service_api_client
|
||||
)
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
if not current_user.is_anonymous and current_user.email_address.lower() != invited_user.email_address.lower():
|
||||
@@ -88,3 +73,44 @@ def accept_invite(token):
|
||||
return redirect(url_for('main.service_dashboard', service_id=invited_user.service))
|
||||
else:
|
||||
return redirect(url_for('main.register_from_invite'))
|
||||
|
||||
|
||||
@main.route("/organisation-invitation/<token>")
|
||||
def accept_org_invite(token):
|
||||
invited_org_user = org_invite_api_client.check_token(token)
|
||||
if not current_user.is_anonymous and current_user.email_address.lower() != invited_org_user.email_address.lower():
|
||||
message = Markup("""
|
||||
You’re signed in as {}.
|
||||
This invite is for another email address.
|
||||
<a href={}>Sign out</a> and click the link again to accept this invite.
|
||||
""".format(
|
||||
current_user.email_address,
|
||||
url_for("main.sign_out", _external=True)))
|
||||
|
||||
flash(message=message)
|
||||
|
||||
abort(403)
|
||||
|
||||
if invited_org_user.status == 'cancelled':
|
||||
invited_by = user_api_client.get_user(invited_org_user.invited_by)
|
||||
organisation = organisations_client.get_organisation(invited_org_user.organisation)
|
||||
return render_template('views/cancelled-invitation.html',
|
||||
from_user=invited_by.name,
|
||||
organisation_name=organisation['name'])
|
||||
|
||||
if invited_org_user.status == 'accepted':
|
||||
session.pop('invited_org_user', None)
|
||||
return redirect(url_for('main.organisation_dashboard', org_id=invited_org_user.organisation))
|
||||
|
||||
session['invited_org_user'] = invited_org_user.serialize()
|
||||
|
||||
existing_user = user_api_client.get_user_by_email_or_none(invited_org_user.email_address)
|
||||
organisation_users = user_api_client.get_users_for_organisation(invited_org_user.organisation)
|
||||
|
||||
if existing_user:
|
||||
org_invite_api_client.accept_invite(invited_org_user.organisation, invited_org_user.id)
|
||||
if existing_user not in organisation_users:
|
||||
user_api_client.add_user_to_organisation(invited_org_user.organisation, existing_user.id)
|
||||
return redirect(url_for('main.organisation_dashboard', org_id=invited_org_user.organisation))
|
||||
else:
|
||||
return redirect(url_for('main.register_from_org_invite'))
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
from flask import redirect, render_template, url_for
|
||||
from flask_login import login_required
|
||||
from flask import (
|
||||
redirect,
|
||||
render_template,
|
||||
url_for,
|
||||
flash,
|
||||
request
|
||||
)
|
||||
from flask_login import (
|
||||
login_required,
|
||||
current_user
|
||||
)
|
||||
|
||||
from app import organisations_client
|
||||
from app import (
|
||||
organisations_client,
|
||||
org_invite_api_client,
|
||||
user_api_client,
|
||||
)
|
||||
from app.main.forms import (
|
||||
SearchUsersForm,
|
||||
InviteOrgUserForm,
|
||||
)
|
||||
from app.main import main
|
||||
from app.main.forms import CreateOrUpdateOrganisation
|
||||
from app.utils import user_has_permissions
|
||||
|
||||
from notifications_python_client.errors import HTTPError
|
||||
from werkzeug.exceptions import abort
|
||||
|
||||
|
||||
@main.route("/organisations", methods=['GET'])
|
||||
@login_required
|
||||
@@ -38,7 +58,7 @@ def add_organisation():
|
||||
)
|
||||
|
||||
|
||||
@main.route("/organisation/<org_id>", methods=['GET'])
|
||||
@main.route("/organisations/<org_id>", methods=['GET'])
|
||||
@login_required
|
||||
@user_has_permissions(admin_override=True)
|
||||
def organisation_dashboard(org_id):
|
||||
@@ -50,7 +70,7 @@ def organisation_dashboard(org_id):
|
||||
)
|
||||
|
||||
|
||||
@main.route("/organisation/<org_id>/edit", methods=['GET', 'POST'])
|
||||
@main.route("/organisations/<org_id>/edit", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@user_has_permissions(admin_override=True)
|
||||
def update_organisation(org_id):
|
||||
@@ -75,11 +95,96 @@ def update_organisation(org_id):
|
||||
)
|
||||
|
||||
|
||||
@main.route("/organisation/<org_id>/users", methods=['GET'])
|
||||
@main.route("/organisations/<org_id>/users", methods=['GET'])
|
||||
@login_required
|
||||
@user_has_permissions(admin_override=True)
|
||||
def organisation_users(org_id):
|
||||
def manage_org_users(org_id):
|
||||
users = sorted(
|
||||
user_api_client.get_users_for_organisation(org_id=org_id) + [
|
||||
invite for invite in org_invite_api_client.get_invites_for_organisation(org_id=org_id)
|
||||
if invite.status != 'accepted'
|
||||
],
|
||||
key=lambda user: user.email_address,
|
||||
)
|
||||
|
||||
return render_template(
|
||||
'views/organisations/organisation/users/index.html',
|
||||
users=users,
|
||||
show_search_box=(len(users) > 7),
|
||||
form=SearchUsersForm(),
|
||||
)
|
||||
|
||||
|
||||
@main.route("/organisations/<org_id>/users/invite", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@user_has_permissions(admin_override=True)
|
||||
def invite_org_user(org_id):
|
||||
form = InviteOrgUserForm(
|
||||
invalid_email_address=current_user.email_address
|
||||
)
|
||||
if form.validate_on_submit():
|
||||
email_address = form.email_address.data
|
||||
invited_org_user = org_invite_api_client.create_invite(
|
||||
current_user.id,
|
||||
org_id,
|
||||
email_address
|
||||
)
|
||||
|
||||
flash('Invite sent to {}'.format(invited_org_user.email_address), 'default_with_tick')
|
||||
return redirect(url_for('.manage_org_users', org_id=org_id))
|
||||
|
||||
return render_template(
|
||||
'views/organisations/organisation/users/invite-org-user.html',
|
||||
form=form
|
||||
)
|
||||
|
||||
|
||||
@main.route("/organisations/<org_id>/users/<user_id>", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@user_has_permissions(admin_override=True)
|
||||
def edit_user_org_permissions(org_id, user_id):
|
||||
user = user_api_client.get_user(user_id)
|
||||
|
||||
return render_template(
|
||||
'views/organisations/organisation/users/user/index.html',
|
||||
user=user
|
||||
)
|
||||
|
||||
|
||||
@main.route("/organisations/<org_id>/users/<user_id>/delete", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
@user_has_permissions(admin_override=True)
|
||||
def remove_user_from_organisation(org_id, user_id):
|
||||
user = user_api_client.get_user(user_id)
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
organisations_client.remove_user_from_organisation(org_id, user_id)
|
||||
except HTTPError as e:
|
||||
msg = "You cannot remove the only user for a service"
|
||||
if e.status_code == 400 and msg in e.message:
|
||||
flash(msg, 'info')
|
||||
return redirect(url_for(
|
||||
'.manage_org_users',
|
||||
org_id=org_id))
|
||||
else:
|
||||
abort(500, e)
|
||||
|
||||
return redirect(url_for(
|
||||
'.manage_org_users',
|
||||
org_id=org_id
|
||||
))
|
||||
|
||||
flash('Are you sure you want to remove {}?'.format(user.name), 'remove')
|
||||
return render_template(
|
||||
'views/organisations/organisation/users/user/index.html',
|
||||
user=user,
|
||||
)
|
||||
|
||||
|
||||
@main.route("/organisations/<org_id>/cancel-invited-user/<invited_user_id>", methods=['GET'])
|
||||
@login_required
|
||||
@user_has_permissions(admin_override=True)
|
||||
def cancel_invited_org_user(org_id, invited_user_id):
|
||||
org_invite_api_client.cancel_invited_user(org_id=org_id, invited_user_id=invited_user_id)
|
||||
|
||||
return redirect(url_for('main.manage_org_users', org_id=org_id))
|
||||
|
||||
@@ -17,13 +17,15 @@ from app.main import main
|
||||
|
||||
from app.main.forms import (
|
||||
RegisterUserForm,
|
||||
RegisterUserFromInviteForm
|
||||
RegisterUserFromInviteForm,
|
||||
RegisterUserFromOrgInviteForm
|
||||
)
|
||||
from app.main.views.verify import activate_user
|
||||
|
||||
from app import (
|
||||
user_api_client,
|
||||
invite_api_client
|
||||
invite_api_client,
|
||||
org_invite_api_client
|
||||
)
|
||||
|
||||
|
||||
@@ -65,20 +67,42 @@ def register_from_invite():
|
||||
return render_template('views/register-from-invite.html', invited_user=invited_user, form=form)
|
||||
|
||||
|
||||
def _do_registration(form, send_sms=True, send_email=True):
|
||||
if user_api_client.is_email_unique(form.email_address.data):
|
||||
@main.route('/register-from-org-invite', methods=['GET', 'POST'])
|
||||
def register_from_org_invite():
|
||||
invited_org_user = session.get('invited_org_user')
|
||||
if not invited_org_user:
|
||||
abort(404)
|
||||
|
||||
form = RegisterUserFromOrgInviteForm(
|
||||
invited_org_user,
|
||||
)
|
||||
form.auth_type.data = 'sms_auth'
|
||||
|
||||
if form.validate_on_submit():
|
||||
if (form.organisation.data != invited_org_user['organisation'] or
|
||||
form.email_address.data != invited_org_user['email_address']):
|
||||
abort(400)
|
||||
_do_registration(form, send_email=False, send_sms=True, organisation_id=invited_org_user['organisation'])
|
||||
org_invite_api_client.accept_invite(invited_org_user['organisation'], invited_org_user['id'])
|
||||
user_api_client.add_user_to_organisation(invited_org_user['organisation'], session['user_details']['id'])
|
||||
|
||||
return redirect(url_for('main.verify'))
|
||||
return render_template('views/register-from-org-invite.html', invited_org_user=invited_org_user, form=form)
|
||||
|
||||
|
||||
def _do_registration(form, send_sms=True, send_email=True, organisation_id=None):
|
||||
if user_api_client.is_email_already_in_use(form.email_address.data):
|
||||
user = user_api_client.get_user_by_email(form.email_address.data)
|
||||
if send_email:
|
||||
user_api_client.send_already_registered_email(user.id, user.email_address)
|
||||
session['expiry_date'] = str(datetime.utcnow() + timedelta(hours=1))
|
||||
session['user_details'] = {"email": user.email_address, "id": user.id}
|
||||
else:
|
||||
user = user_api_client.register_user(form.name.data,
|
||||
form.email_address.data,
|
||||
form.mobile_number.data or None,
|
||||
form.password.data,
|
||||
form.auth_type.data)
|
||||
|
||||
# TODO possibly there should be some exception handling
|
||||
# for sending sms and email codes.
|
||||
# How do we report to the user there is a problem with
|
||||
# sending codes apart from service unavailable?
|
||||
# at the moment i believe http 500 is fine.
|
||||
|
||||
if send_email:
|
||||
user_api_client.send_verify_email(user.id, user.email_address)
|
||||
|
||||
@@ -86,12 +110,8 @@ def _do_registration(form, send_sms=True, send_email=True):
|
||||
user_api_client.send_verify_code(user.id, 'sms', user.mobile_number)
|
||||
session['expiry_date'] = str(datetime.utcnow() + timedelta(hours=1))
|
||||
session['user_details'] = {"email": user.email_address, "id": user.id}
|
||||
else:
|
||||
user = user_api_client.get_user_by_email(form.email_address.data)
|
||||
if send_email:
|
||||
user_api_client.send_already_registered_email(user.id, user.email_address)
|
||||
session['expiry_date'] = str(datetime.utcnow() + timedelta(hours=1))
|
||||
session['user_details'] = {"email": user.email_address, "id": user.id}
|
||||
if organisation_id:
|
||||
session['organisation_id'] = organisation_id
|
||||
|
||||
|
||||
@main.route('/registration-continue')
|
||||
|
||||
@@ -64,9 +64,9 @@ def user_profile_email():
|
||||
if not is_gov_user(current_user.email_address):
|
||||
abort(403)
|
||||
|
||||
def _is_email_unique(email):
|
||||
return user_api_client.is_email_unique(email)
|
||||
form = ChangeEmailForm(_is_email_unique,
|
||||
def _is_email_already_in_use(email):
|
||||
return user_api_client.is_email_already_in_use(email)
|
||||
form = ChangeEmailForm(_is_email_already_in_use,
|
||||
email_address=current_user.email_address)
|
||||
|
||||
if form.validate_on_submit():
|
||||
|
||||
@@ -74,6 +74,10 @@ def activate_user(user_id):
|
||||
user = user_api_client.get_user(user_id)
|
||||
# the user will have a new current_session_id set by the API - store it in the cookie for future requests
|
||||
session['current_session_id'] = user.current_session_id
|
||||
organisation_id = session.get('organisation_id', None)
|
||||
activated_user = user_api_client.activate_user(user)
|
||||
login_user(activated_user)
|
||||
return redirect(url_for('main.add_service', first='first'))
|
||||
if organisation_id:
|
||||
return redirect(url_for('main.organisation_dashboard', org_id=organisation_id))
|
||||
else:
|
||||
return redirect(url_for('main.add_service', first='first'))
|
||||
|
||||
Reference in New Issue
Block a user