invite-team-members

This commit is contained in:
chrisw
2018-02-19 16:53:29 +00:00
parent b7b25c7d16
commit 22bbc0d6d8
26 changed files with 1132 additions and 132 deletions

View File

@@ -237,6 +237,25 @@ class RegisterUserFromInviteForm(StripWhitespaceForm):
raise ValidationError('Cant 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='Cant be empty')]
)
mobile_number = InternationalPhoneNumber('Mobile number', validators=[DataRequired(message='Cant 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 cant 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 cant 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")

View File

@@ -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("""
Youre 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'))

View File

@@ -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))

View File

@@ -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')

View File

@@ -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():

View File

@@ -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'))