mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-07-20 16:13:24 -04:00
Merge pull request #1901 from alphagov/becca-invite-users
Organisation invite users
This commit is contained in:
@@ -52,6 +52,7 @@ from app.notify_client.provider_client import ProviderClient
|
||||
from app.notify_client.email_branding_client import EmailBrandingClient
|
||||
from app.notify_client.models import AnonymousUser
|
||||
from app.notify_client.organisations_api_client import OrganisationsClient
|
||||
from app.notify_client.org_invite_api_client import OrgInviteApiClient
|
||||
from app.notify_client.letter_jobs_client import LetterJobsClient
|
||||
from app.notify_client.inbound_number_client import InboundNumberClient
|
||||
from app.notify_client.billing_api_client import BillingAPIClient
|
||||
@@ -74,6 +75,7 @@ events_api_client = EventsApiClient()
|
||||
provider_client = ProviderClient()
|
||||
email_branding_client = EmailBrandingClient()
|
||||
organisations_client = OrganisationsClient()
|
||||
org_invite_api_client = OrgInviteApiClient()
|
||||
asset_fingerprinter = AssetFingerprinter()
|
||||
statsd_client = StatsdClient()
|
||||
deskpro_client = DeskproClient()
|
||||
@@ -109,6 +111,7 @@ def create_app(application):
|
||||
notification_api_client.init_app(application)
|
||||
status_api_client.init_app(application)
|
||||
invite_api_client.init_app(application)
|
||||
org_invite_api_client.init_app(application)
|
||||
template_statistics_client.init_app(application)
|
||||
events_api_client.init_app(application)
|
||||
provider_client.init_app(application)
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -34,7 +34,7 @@ class InviteApiClient(NotifyAdminAPIClient):
|
||||
return invited_users
|
||||
|
||||
def check_token(self, token):
|
||||
resp = self.get(url='/invite/{}'.format(token))
|
||||
resp = self.get(url='/invite/service/{}'.format(token))
|
||||
return InvitedUser(**resp['data'])
|
||||
|
||||
def cancel_invited_user(self, service_id, invited_user_id):
|
||||
|
||||
@@ -31,6 +31,7 @@ class User(UserMixin):
|
||||
self.max_failed_login_count = max_failed_login_count
|
||||
self.platform_admin = fields.get('platform_admin')
|
||||
self.current_session_id = fields.get('current_session_id')
|
||||
self._organisations = fields.get('organisations', [])
|
||||
|
||||
def get_id(self):
|
||||
return self.id
|
||||
@@ -158,6 +159,7 @@ class User(UserMixin):
|
||||
"state": self.state,
|
||||
"failed_login_count": self.failed_login_count,
|
||||
"permissions": [x for x in self._permissions],
|
||||
"organisations": self.organisations,
|
||||
"current_session_id": self.current_session_id
|
||||
}
|
||||
if hasattr(self, '_password'):
|
||||
@@ -167,6 +169,10 @@ class User(UserMixin):
|
||||
def set_password(self, pwd):
|
||||
self._password = pwd
|
||||
|
||||
@property
|
||||
def organisations(self):
|
||||
return self._organisations
|
||||
|
||||
|
||||
class InvitedUser(object):
|
||||
|
||||
@@ -220,6 +226,38 @@ class InvitedUser(object):
|
||||
return data
|
||||
|
||||
|
||||
class InvitedOrgUser(object):
|
||||
|
||||
def __init__(self, id, organisation, invited_by, email_address, status, created_at):
|
||||
self.id = id
|
||||
self.organisation = str(organisation)
|
||||
self.invited_by = invited_by
|
||||
self.email_address = email_address
|
||||
self.status = status
|
||||
self.created_at = created_at
|
||||
|
||||
def __eq__(self, other):
|
||||
return ((self.id,
|
||||
self.organisation,
|
||||
self.invited_by,
|
||||
self.email_address,
|
||||
self.status) == (other.id,
|
||||
other.organisation,
|
||||
other.invited_by,
|
||||
other.email_address,
|
||||
other.status))
|
||||
|
||||
def serialize(self, permissions_as_string=False):
|
||||
data = {'id': self.id,
|
||||
'organisation': self.organisation,
|
||||
'invited_by': self.invited_by,
|
||||
'email_address': self.email_address,
|
||||
'status': self.status,
|
||||
'created_at': str(self.created_at)
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
class AnonymousUser(AnonymousUserMixin):
|
||||
# set the anonymous user so that if a new browser hits us we don't error http://stackoverflow.com/a/19275188
|
||||
def logged_in_elsewhere(self):
|
||||
|
||||
52
app/notify_client/org_invite_api_client.py
Normal file
52
app/notify_client/org_invite_api_client.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from app.notify_client import _attach_current_user, NotifyAdminAPIClient
|
||||
from app.notify_client.models import InvitedOrgUser
|
||||
|
||||
|
||||
class OrgInviteApiClient(NotifyAdminAPIClient):
|
||||
def __init__(self):
|
||||
super().__init__("a" * 73, "b")
|
||||
|
||||
def init_app(self, app):
|
||||
self.base_url = app.config['API_HOST_NAME']
|
||||
self.admin_url = app.config['ADMIN_BASE_URL']
|
||||
self.service_id = app.config['ADMIN_CLIENT_USER_NAME']
|
||||
self.api_key = app.config['ADMIN_CLIENT_SECRET']
|
||||
|
||||
def create_invite(self, invite_from_id, org_id, email_address):
|
||||
data = {
|
||||
'email_address': email_address,
|
||||
'invited_by': invite_from_id,
|
||||
'invite_link_host': self.admin_url,
|
||||
}
|
||||
data = _attach_current_user(data)
|
||||
resp = self.post(url='/organisation/{}/invite'.format(org_id), data=data)
|
||||
return InvitedOrgUser(**resp['data'])
|
||||
|
||||
def get_invites_for_organisation(self, org_id):
|
||||
endpoint = '/organisation/{}/invite'.format(org_id)
|
||||
resp = self.get(endpoint)
|
||||
invites = resp['data']
|
||||
invited_users = self._get_invited_org_users(invites)
|
||||
return invited_users
|
||||
|
||||
def check_token(self, token):
|
||||
resp = self.get(url='/invite/organisation/{}'.format(token))
|
||||
return InvitedOrgUser(**resp['data'])
|
||||
|
||||
def cancel_invited_user(self, org_id, invited_user_id):
|
||||
data = {'status': 'cancelled'}
|
||||
data = _attach_current_user(data)
|
||||
self.post(url='/organisation/{0}/invite/{1}'.format(org_id, invited_user_id),
|
||||
data=data)
|
||||
|
||||
def accept_invite(self, org_id, invited_user_id):
|
||||
data = {'status': 'accepted'}
|
||||
self.post(url='/organisation/{0}/invite/{1}'.format(org_id, invited_user_id),
|
||||
data=data)
|
||||
|
||||
def _get_invited_org_users(self, invites):
|
||||
invited_users = []
|
||||
for invite in invites:
|
||||
invited_user = InvitedOrgUser(**invite)
|
||||
invited_users.append(invited_user)
|
||||
return invited_users
|
||||
@@ -1,4 +1,4 @@
|
||||
from app.notify_client import NotifyAdminAPIClient
|
||||
from app.notify_client import _attach_current_user, NotifyAdminAPIClient
|
||||
|
||||
|
||||
class OrganisationsClient(NotifyAdminAPIClient):
|
||||
@@ -43,3 +43,10 @@ class OrganisationsClient(NotifyAdminAPIClient):
|
||||
|
||||
def get_organisation_services(self, org_id):
|
||||
return self.get(url="/organisations/{}/services".format(org_id))
|
||||
|
||||
def remove_user_from_organisation(self, org_id, user_id):
|
||||
endpoint = '/organisations/{}/users/{}'.format(
|
||||
org_id=org_id,
|
||||
user_id=user_id)
|
||||
data = _attach_current_user({})
|
||||
return self.delete(endpoint, data)
|
||||
|
||||
@@ -137,12 +137,21 @@ class UserApiClient(NotifyAdminAPIClient):
|
||||
if user.has_permissions(permission, any_=True)
|
||||
])
|
||||
|
||||
def get_users_for_organisation(self, org_id):
|
||||
endpoint = '/organisations/{}/users'.format(org_id)
|
||||
resp = self.get(endpoint)
|
||||
return [User(data) for data in resp['data']]
|
||||
|
||||
def add_user_to_service(self, service_id, user_id, permissions):
|
||||
endpoint = '/service/{}/users/{}'.format(service_id, user_id)
|
||||
data = [{'permission': x} for x in permissions]
|
||||
resp = self.post(endpoint, data=data)
|
||||
return User(resp['data'], max_failed_login_count=self.max_failed_login_count)
|
||||
|
||||
def add_user_to_organisation(self, org_id, user_id):
|
||||
resp = self.post('/organisations/{}/users/{}'.format(org_id, user_id), data={})
|
||||
return User(resp['data'], max_failed_login_count=self.max_failed_login_count)
|
||||
|
||||
def set_user_permissions(self, user_id, service_id, permissions):
|
||||
data = [{'permission': x} for x in permissions]
|
||||
endpoint = '/user/{}/service/{}/permission'.format(user_id, service_id)
|
||||
@@ -153,10 +162,10 @@ class UserApiClient(NotifyAdminAPIClient):
|
||||
data = {'email': email_address}
|
||||
self.post(endpoint, data=data)
|
||||
|
||||
def is_email_unique(self, email_address):
|
||||
def is_email_already_in_use(self, email_address):
|
||||
if self.get_user_by_email_or_none(email_address):
|
||||
return False
|
||||
return True
|
||||
return True
|
||||
return False
|
||||
|
||||
def activate_user(self, user):
|
||||
if user.state == 'pending':
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<ul>
|
||||
<li><a href="{{ url_for('.organisation_dashboard', org_id=current_org.id) }}">Dashboard</a></li>
|
||||
{% if current_user.has_permissions(admin_override=True) %}
|
||||
<li><a href="{{ url_for('.organisation_users', org_id=current_org.id) }}">Team members</a></li>
|
||||
<li><a href="{{ url_for('.manage_org_users', org_id=current_org.id) }}">Team members</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
{{ from_user }} decided to cancel this invitation.
|
||||
</p>
|
||||
<p>
|
||||
If you need access to {{ service_name }}, you’ll have to ask them to invite you again.
|
||||
If you need access to {{ service_name or organisation_name }}, you’ll have to ask them to invite you again.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{% extends "org_template.html" %}
|
||||
{% from "components/table.html" import list_table, row, field, hidden_field_heading %}
|
||||
{% from "components/page-footer.html" import page_footer %}
|
||||
{% from "components/tick-cross.html" import tick_cross %}
|
||||
{% from "components/textbox.html" import textbox %}
|
||||
|
||||
{% block org_page_title %}
|
||||
Team members
|
||||
@@ -12,6 +16,52 @@
|
||||
Team members
|
||||
</h1>
|
||||
</div>
|
||||
<div class="column-one-third">
|
||||
<a href="{{ url_for('.invite_org_user', org_id=current_org.id) }}" class="button align-with-heading">Invite team member</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if show_search_box %}
|
||||
<div data-module="autofocus">
|
||||
<div class="live-search" data-module="live-search" data-targets=".user-list-item">
|
||||
{{ textbox(
|
||||
form.search,
|
||||
width='1-1'
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="user-list">
|
||||
{% for user in users %}
|
||||
<div class="user-list-item">
|
||||
<h3>
|
||||
{%- if user.name -%}
|
||||
<span class="heading-small">{{ user.name }}</span> 
|
||||
{%- endif -%}
|
||||
<span class="hint">
|
||||
{%- if user.status == 'pending' -%}
|
||||
{{ user.email_address }} (invited)
|
||||
{%- elif user.status == 'cancelled' -%}
|
||||
{{ user.email_address }} (cancelled invite)
|
||||
{%- elif user.id == current_user.id -%}
|
||||
(you)
|
||||
{% else %}
|
||||
{{ user.email_address }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</h3>
|
||||
<ul class="tick-cross-list">
|
||||
<li class="tick-cross-list-edit-link">
|
||||
{% if user.status == 'pending' %}
|
||||
<a href="{{ url_for('.cancel_invited_org_user', org_id=current_org.id, invited_user_id=user.id)}}">Cancel invitation</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{% extends "org_template.html" %}
|
||||
|
||||
{% from "components/textbox.html" import textbox %}
|
||||
{% from "components/page-footer.html" import page_footer %}
|
||||
|
||||
{% block service_page_title %}
|
||||
Invite a team member
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<h1 class="heading-large">
|
||||
Invite a team member
|
||||
</h1>
|
||||
<div class="grid-row">
|
||||
<form method="post" class="column-three-quarters" novalidate>
|
||||
{{ textbox(form.email_address, width='1-1', safe_error_message=True) }}
|
||||
|
||||
<div class="bottom-gutter">
|
||||
<p class="form-label">
|
||||
All team members can see:
|
||||
</p>
|
||||
<ul class="list list-bullet">
|
||||
<li>dashboard</li>
|
||||
<li>who the other team members are</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{{ page_footer('Send invitation email') }}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,28 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
{% from "components/textbox.html" import textbox %}
|
||||
{% from "components/page-footer.html" import page_footer %}
|
||||
|
||||
{% block service_page_title %}
|
||||
{{ user.name or user.email_localpart }}
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<h1 class="heading-large">
|
||||
{{ user.name or user.email_localpart }}
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
{{ user.email_address }}
|
||||
</p>
|
||||
<form method="post" class="column-three-quarters">
|
||||
{{ page_footer(
|
||||
'Save',
|
||||
back_link=url_for('.manage_org_users', org_id=current_org.id),
|
||||
back_link_text="Back",
|
||||
delete_link=url_for('.remove_user_from_organisation', org_id=current_org.id, user_id=user.id) if user or None,
|
||||
delete_link_text='Remove user from organisation'
|
||||
) }}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
28
app/templates/views/register-from-org-invite.html
Normal file
28
app/templates/views/register-from-org-invite.html
Normal file
@@ -0,0 +1,28 @@
|
||||
{% extends "withoutnav_template.html" %}
|
||||
{% from "components/textbox.html" import textbox %}
|
||||
{% from "components/page-footer.html" import page_footer %}
|
||||
|
||||
{% block per_page_title %}
|
||||
Create an account
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
<h1 class="heading-large">Create an account</h1>
|
||||
<p>Your account will be created with this email: {{invited_org_user.email_address}}</p>
|
||||
<form method="post" autocomplete="off">
|
||||
{{ textbox(form.name, width='3-4') }}
|
||||
<div class="extra-tracking">
|
||||
{{ textbox(form.mobile_number, width='3-4', hint='We’ll send you a security code by text message') }}
|
||||
</div>
|
||||
{{ textbox(form.password, hint="At least 8 characters", width='3-4') }}
|
||||
{{ page_footer("Continue") }}
|
||||
{{form.organisation}}
|
||||
{{form.email_address}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -5,6 +5,9 @@ from datetime import datetime, timedelta, timezone
|
||||
from flask.testing import FlaskClient
|
||||
from flask import url_for
|
||||
from flask_login import login_user
|
||||
from app.notify_client.models import (
|
||||
InvitedOrgUser,
|
||||
)
|
||||
|
||||
|
||||
class TestClient(FlaskClient):
|
||||
@@ -37,6 +40,85 @@ def created_by_json(id_, name='', email_address=''):
|
||||
return {'id': id_, 'name': name, 'email_address': email_address}
|
||||
|
||||
|
||||
def user_json(
|
||||
id_='1234',
|
||||
name='Test User',
|
||||
email_address='test@gov.uk',
|
||||
mobile_number='+447700900986',
|
||||
password_changed_at=None,
|
||||
permissions={generate_uuid(): [
|
||||
'send_texts',
|
||||
'send_emails',
|
||||
'send_letters',
|
||||
'manage_users',
|
||||
'manage_templates',
|
||||
'manage_settings',
|
||||
'manage_api_keys']
|
||||
},
|
||||
auth_type='sms_auth',
|
||||
failed_login_count=0,
|
||||
state='active',
|
||||
max_failed_login_count=3,
|
||||
platform_admin=False,
|
||||
current_session_id='1234',
|
||||
organisations=[],
|
||||
|
||||
):
|
||||
return {
|
||||
'id': id_,
|
||||
'name': name,
|
||||
'email_address': email_address,
|
||||
'mobile_number': mobile_number,
|
||||
'password_changed_at': password_changed_at,
|
||||
'permissions': permissions,
|
||||
'auth_type': auth_type,
|
||||
'failed_login_count': failed_login_count,
|
||||
'state': state,
|
||||
'max_failed_login_count': max_failed_login_count,
|
||||
'platform_admin': platform_admin,
|
||||
'current_session_id': current_session_id,
|
||||
'organisations': organisations
|
||||
}
|
||||
|
||||
|
||||
def invited_user(
|
||||
_id='1234',
|
||||
service=None,
|
||||
from_user='1234',
|
||||
email_address='testinviteduser@gov.uk',
|
||||
permissions=None,
|
||||
status='pending',
|
||||
created_at=datetime.utcnow(),
|
||||
auth_type='sms_auth',
|
||||
organisation=None
|
||||
):
|
||||
org_user = organisation is not None
|
||||
data = {
|
||||
'id': _id,
|
||||
'from_user': from_user,
|
||||
'email_address': email_address,
|
||||
'status': status,
|
||||
'created_at': created_at,
|
||||
'auth_type': auth_type,
|
||||
}
|
||||
if service:
|
||||
data['service'] = service
|
||||
if permissions:
|
||||
data['permissions'] = permissions
|
||||
if organisation:
|
||||
data['organisation'] = organisation
|
||||
|
||||
if org_user:
|
||||
return InvitedOrgUser(
|
||||
data['id'],
|
||||
data['organisation'],
|
||||
data['from_user'],
|
||||
data['email_address'],
|
||||
data['status'],
|
||||
data['created_at']
|
||||
)
|
||||
|
||||
|
||||
def service_json(
|
||||
id_='1234',
|
||||
name='Test Service',
|
||||
@@ -88,6 +170,28 @@ def service_json(
|
||||
}
|
||||
|
||||
|
||||
def organisation_json(
|
||||
id_='1234',
|
||||
name='Test Organisation',
|
||||
users=None,
|
||||
active=True,
|
||||
created_at=None,
|
||||
services=None
|
||||
):
|
||||
if users is None:
|
||||
users = []
|
||||
if services is None:
|
||||
services = []
|
||||
return {
|
||||
'id': id_,
|
||||
'name': name,
|
||||
'active': active,
|
||||
'users': users,
|
||||
'services': services,
|
||||
'created_at': created_at or str(datetime.utcnow())
|
||||
}
|
||||
|
||||
|
||||
def template_json(service_id,
|
||||
id_,
|
||||
name="sample template",
|
||||
@@ -166,6 +270,17 @@ def invite_json(id_, from_user, service_id, email_address, permissions, created_
|
||||
}
|
||||
|
||||
|
||||
def org_invite_json(id_, invited_by, org_id, email_address, created_at, status):
|
||||
return {
|
||||
'id': id_,
|
||||
'invited_by': invited_by,
|
||||
'organisation': org_id,
|
||||
'email_address': email_address,
|
||||
'status': status,
|
||||
'created_at': created_at,
|
||||
}
|
||||
|
||||
|
||||
TEST_USER_EMAIL = 'test@user.gov.uk'
|
||||
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ def test_load_service_before_request_handles_404(client_request, mocker):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('url', [
|
||||
'/invitation/MALFORMED_TOKEN',
|
||||
'/new-password/MALFORMED_TOKEN',
|
||||
'/user-profile/email/confirm/MALFORMED_TOKEN',
|
||||
'/verify-email/MALFORMED_TOKEN'
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from flask import url_for
|
||||
from bs4 import BeautifulSoup
|
||||
from unittest.mock import ANY
|
||||
from itsdangerous import SignatureExpired
|
||||
|
||||
import app
|
||||
from app.notify_client.models import InvitedUser
|
||||
@@ -22,8 +21,6 @@ def test_existing_user_accept_invite_calls_api_and_redirects_to_dashboard(
|
||||
mock_get_service,
|
||||
mocker,
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
|
||||
expected_service = service_one['id']
|
||||
expected_permissions = ['send_messages', 'manage_service', 'manage_api_keys']
|
||||
|
||||
@@ -50,8 +47,6 @@ def test_existing_user_with_no_permissions_accept_invite(
|
||||
mock_add_user_to_service,
|
||||
mock_get_service,
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
|
||||
expected_service = service_one['id']
|
||||
sample_invite['permissions'] = ''
|
||||
expected_permissions = []
|
||||
@@ -69,8 +64,6 @@ def test_if_existing_user_accepts_twice_they_redirect_to_sign_in(
|
||||
sample_invite,
|
||||
mock_get_service,
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
|
||||
sample_invite['status'] = 'accepted'
|
||||
invite = InvitedUser(**sample_invite)
|
||||
mocker.patch('app.invite_api_client.check_token', return_value=invite)
|
||||
@@ -96,7 +89,6 @@ def test_existing_user_of_service_get_redirected_to_signin(
|
||||
mock_get_user_by_email,
|
||||
mock_accept_invite,
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
sample_invite['email_address'] = api_user_active.email_address
|
||||
invite = InvitedUser(**sample_invite)
|
||||
mocker.patch('app.invite_api_client.check_token', return_value=invite)
|
||||
@@ -128,8 +120,6 @@ def test_existing_signed_out_user_accept_invite_redirects_to_sign_in(
|
||||
mock_get_service,
|
||||
mocker,
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
|
||||
expected_service = service_one['id']
|
||||
expected_permissions = ['send_messages', 'manage_service', 'manage_api_keys']
|
||||
|
||||
@@ -161,8 +151,6 @@ def test_new_user_accept_invite_calls_api_and_redirects_to_registration(
|
||||
mock_get_service,
|
||||
mocker,
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
|
||||
expected_redirect_location = 'http://localhost/register-from-invite'
|
||||
|
||||
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
|
||||
@@ -184,8 +172,6 @@ def test_new_user_accept_invite_calls_api_and_views_registration_page(
|
||||
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)
|
||||
|
||||
mock_check_invite_token.assert_called_with('thisisnotarealtoken')
|
||||
@@ -219,7 +205,6 @@ def test_cancelled_invited_user_accepts_invited_redirect_to_cancelled_invitation
|
||||
mock_get_user,
|
||||
mock_get_service,
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
cancelled_invitation = create_sample_invite(mocker, service_one, status='cancelled')
|
||||
mock_check_token_invite(mocker, cancelled_invitation)
|
||||
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
|
||||
@@ -237,7 +222,7 @@ def test_new_user_accept_invite_completes_new_registration_redirects_to_verify(
|
||||
api_user_active,
|
||||
mock_check_invite_token,
|
||||
mock_dont_get_user_by_email,
|
||||
mock_is_email_unique,
|
||||
mock_email_is_not_already_in_use,
|
||||
mock_register_user,
|
||||
mock_send_verify_code,
|
||||
mock_accept_invite,
|
||||
@@ -246,8 +231,6 @@ def test_new_user_accept_invite_completes_new_registration_redirects_to_verify(
|
||||
mock_get_service,
|
||||
mocker,
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
|
||||
expected_service = service_one['id']
|
||||
expected_email = sample_invite['email_address']
|
||||
expected_from_user = service_one['users'][0]
|
||||
@@ -297,7 +280,6 @@ def test_signed_in_existing_user_cannot_use_anothers_invite(
|
||||
mock_accept_invite,
|
||||
mock_get_service,
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
invite = InvitedUser(**sample_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])
|
||||
@@ -324,8 +306,6 @@ def test_accept_invite_does_not_treat_email_addresses_as_case_sensitive(
|
||||
mock_accept_invite,
|
||||
mock_get_user_by_email
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
|
||||
# the email address of api_user_active is 'test@user.gov.uk'
|
||||
sample_invite['email_address'] = 'TEST@user.gov.uk'
|
||||
invite = InvitedUser(**sample_invite)
|
||||
@@ -345,7 +325,7 @@ def test_new_invited_user_verifies_and_added_to_service(
|
||||
api_user_active,
|
||||
mock_check_invite_token,
|
||||
mock_dont_get_user_by_email,
|
||||
mock_is_email_unique,
|
||||
mock_email_is_not_already_in_use,
|
||||
mock_register_user,
|
||||
mock_send_verify_code,
|
||||
mock_check_verify_code,
|
||||
@@ -363,8 +343,6 @@ def test_new_invited_user_verifies_and_added_to_service(
|
||||
mock_get_usage,
|
||||
mocker,
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
|
||||
# visit accept token page
|
||||
response = client.get(url_for('main.accept_invite', token='thisisnotarealtoken'))
|
||||
assert response.status_code == 302
|
||||
@@ -404,24 +382,6 @@ def test_new_invited_user_verifies_and_added_to_service(
|
||||
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
|
||||
|
||||
|
||||
def test_existing_user_accepts_and_sets_email_auth(
|
||||
client_request,
|
||||
api_user_active,
|
||||
@@ -434,7 +394,6 @@ def test_existing_user_accepts_and_sets_email_auth(
|
||||
mock_add_user_to_service,
|
||||
mocker
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
sample_invite['email_address'] = api_user_active.email_address
|
||||
|
||||
service_one['permissions'].append('email_auth')
|
||||
@@ -465,7 +424,6 @@ def test_existing_user_doesnt_get_auth_changed_by_service_without_permission(
|
||||
mock_add_user_to_service,
|
||||
mocker
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
sample_invite['email_address'] = api_user_active.email_address
|
||||
|
||||
assert 'email_auth' not in service_one['permissions']
|
||||
@@ -495,7 +453,6 @@ def test_existing_email_auth_user_without_phone_cannot_set_sms_auth(
|
||||
mock_add_user_to_service,
|
||||
mocker
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
sample_invite['email_address'] = api_user_active.email_address
|
||||
|
||||
service_one['permissions'].append('email_auth')
|
||||
@@ -529,7 +486,6 @@ def test_existing_email_auth_user_with_phone_can_set_sms_auth(
|
||||
mock_add_user_to_service,
|
||||
mocker
|
||||
):
|
||||
mocker.patch('app.main.views.invites.check_token')
|
||||
sample_invite['email_address'] = api_user_active.email_address
|
||||
|
||||
service_one['permissions'].append('email_auth')
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import pytest
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from flask import url_for
|
||||
|
||||
from datetime import (
|
||||
datetime,
|
||||
timedelta
|
||||
)
|
||||
|
||||
from tests.conftest import (
|
||||
normalize_spaces
|
||||
)
|
||||
from unittest.mock import ANY
|
||||
from app.notify_client.models import InvitedOrgUser
|
||||
|
||||
|
||||
def test_organisation_page_shows_all_organisations(
|
||||
@@ -151,3 +160,364 @@ def test_organisation_services_show(
|
||||
assert normalize_spaces(
|
||||
page.select('.browse-list-item a')[i]['href']
|
||||
) == '/services/{}'.format(service_id)
|
||||
|
||||
|
||||
def test_view_team_members(
|
||||
logged_in_platform_admin_client,
|
||||
mocker,
|
||||
mock_get_organisation,
|
||||
mock_get_users_for_organisation,
|
||||
mock_get_invited_users_for_organisation,
|
||||
fake_uuid
|
||||
):
|
||||
response = logged_in_platform_admin_client.get(
|
||||
url_for('.manage_org_users', org_id=fake_uuid),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||
|
||||
for i in range(0, 2):
|
||||
assert normalize_spaces(
|
||||
page.select('.user-list-item .heading-small')[i].text
|
||||
) == 'Test User {}'.format(i + 1)
|
||||
|
||||
assert normalize_spaces(
|
||||
page.select('.tick-cross-list-edit-link')[1].text
|
||||
) == 'Cancel invitation'
|
||||
|
||||
|
||||
def test_invite_org_user(
|
||||
logged_in_platform_admin_client,
|
||||
mocker,
|
||||
mock_get_organisation,
|
||||
sample_org_invite,
|
||||
fake_uuid
|
||||
):
|
||||
|
||||
mock_invite_org_user = mocker.patch(
|
||||
'app.org_invite_api_client.create_invite',
|
||||
return_value=InvitedOrgUser(**sample_org_invite)
|
||||
)
|
||||
|
||||
logged_in_platform_admin_client.post(
|
||||
url_for('.invite_org_user', org_id=mock_get_organisation['id']),
|
||||
data={'email_address': 'test@example.gov.uk'}
|
||||
)
|
||||
|
||||
mock_invite_org_user.assert_called_once_with(
|
||||
sample_org_invite['invited_by'],
|
||||
'{}'.format(mock_get_organisation['id']),
|
||||
'test@example.gov.uk',
|
||||
)
|
||||
|
||||
|
||||
def test_invite_org_user_errors_when_same_email_as_inviter(
|
||||
logged_in_platform_admin_client,
|
||||
mocker,
|
||||
mock_get_organisation,
|
||||
sample_org_invite,
|
||||
fake_uuid
|
||||
):
|
||||
new_org_user_data = {
|
||||
'email_address': 'platform@admin.gov.uk',
|
||||
}
|
||||
|
||||
mock_invite_org_user = mocker.patch(
|
||||
'app.org_invite_api_client.create_invite',
|
||||
return_value=InvitedOrgUser(**sample_org_invite)
|
||||
)
|
||||
|
||||
response = logged_in_platform_admin_client.post(
|
||||
url_for('.invite_org_user', org_id=mock_get_organisation['id']),
|
||||
data=new_org_user_data
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||
|
||||
assert mock_invite_org_user.called is False
|
||||
assert normalize_spaces(page.select_one('.error-message').text) == 'You can’t send an invitation to yourself'
|
||||
|
||||
# Broken
|
||||
|
||||
|
||||
def test_accepted_invite_when_user_already_logged_in(
|
||||
logged_in_client,
|
||||
mock_check_org_invite_token
|
||||
):
|
||||
response = logged_in_client.get(
|
||||
url_for('main.accept_org_invite', token='thisisnotarealtoken'),
|
||||
follow_redirects=True
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||
|
||||
assert 'This invite is for another email address.' in normalize_spaces(page.select_one('.banner-dangerous').text)
|
||||
|
||||
|
||||
def test_cancelled_invite_opened_by_user(
|
||||
client,
|
||||
mock_check_org_cancelled_invite_token,
|
||||
mock_get_organisation,
|
||||
mock_get_user,
|
||||
fake_uuid
|
||||
):
|
||||
response = client.get(url_for('main.accept_org_invite', token='thisisnotarealtoken'), follow_redirects=True)
|
||||
|
||||
assert response.status_code == 200
|
||||
page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser')
|
||||
|
||||
assert normalize_spaces(
|
||||
page.select_one('h1').text
|
||||
) == 'The invitation you were sent has been cancelled'
|
||||
assert normalize_spaces(
|
||||
page.select('main p')[0].text
|
||||
) == 'Test User decided to cancel this invitation.'
|
||||
assert normalize_spaces(
|
||||
page.select('main p')[1].text
|
||||
) == 'If you need access to Org 1, you’ll have to ask them to invite you again.'
|
||||
|
||||
mock_get_user.assert_called_once_with(fake_uuid)
|
||||
mock_get_organisation.assert_called_once_with('596364a0-858e-42c8-9062-a8fe822260af')
|
||||
|
||||
|
||||
def test_user_invite_already_accepted(
|
||||
client,
|
||||
mock_check_org_accepted_invite_token
|
||||
):
|
||||
response = client.get(url_for('main.accept_org_invite', token='thisisnotarealtoken'))
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for(
|
||||
'main.organisation_dashboard',
|
||||
org_id='596364a0-858e-42c8-9062-a8fe822260af',
|
||||
_external=True
|
||||
)
|
||||
|
||||
|
||||
def test_existing_user_invite_already_is_member_of_organisation(
|
||||
client,
|
||||
mock_check_org_invite_token,
|
||||
mock_get_user_by_email,
|
||||
mock_get_users_for_organisation,
|
||||
mock_accept_org_invite,
|
||||
mock_add_user_to_organisation,
|
||||
fake_uuid
|
||||
):
|
||||
response = client.get(url_for('main.accept_org_invite', token='thisisnotarealtoken'))
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for(
|
||||
'main.organisation_dashboard',
|
||||
org_id='596364a0-858e-42c8-9062-a8fe822260af',
|
||||
_external=True
|
||||
)
|
||||
|
||||
mock_accept_org_invite.assert_called_once_with('596364a0-858e-42c8-9062-a8fe822260af', ANY)
|
||||
mock_get_user_by_email.assert_called_once_with('invited_user@test.gov.uk')
|
||||
mock_get_users_for_organisation.assert_called_once_with('596364a0-858e-42c8-9062-a8fe822260af')
|
||||
|
||||
|
||||
def test_existing_user_invite_not_a_member_of_organisation(
|
||||
client,
|
||||
mock_check_org_invite_token,
|
||||
mock_get_user_by_email,
|
||||
mock_get_users_for_organisation,
|
||||
mock_accept_org_invite,
|
||||
mock_add_user_to_organisation,
|
||||
fake_uuid
|
||||
):
|
||||
response = client.get(url_for('main.accept_org_invite', token='thisisnotarealtoken'))
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for(
|
||||
'main.organisation_dashboard',
|
||||
org_id='596364a0-858e-42c8-9062-a8fe822260af',
|
||||
_external=True
|
||||
)
|
||||
|
||||
mock_accept_org_invite.assert_called_once_with('596364a0-858e-42c8-9062-a8fe822260af', ANY)
|
||||
mock_get_user_by_email.assert_called_once_with('invited_user@test.gov.uk')
|
||||
mock_get_users_for_organisation.assert_called_once_with('596364a0-858e-42c8-9062-a8fe822260af')
|
||||
mock_add_user_to_organisation.assert_called_once_with(
|
||||
'596364a0-858e-42c8-9062-a8fe822260af',
|
||||
'6ce466d0-fd6a-11e5-82f5-e0accb9d11a6'
|
||||
)
|
||||
|
||||
|
||||
def test_user_accepts_invite(
|
||||
client,
|
||||
mock_check_org_invite_token,
|
||||
mock_dont_get_user_by_email,
|
||||
mock_get_users_for_organisation,
|
||||
):
|
||||
response = client.get(url_for('main.accept_org_invite', token='thisisnotarealtoken'))
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for('main.register_from_org_invite', _external=True)
|
||||
|
||||
mock_check_org_invite_token.assert_called_once_with('thisisnotarealtoken')
|
||||
mock_dont_get_user_by_email.assert_called_once_with('invited_user@test.gov.uk')
|
||||
mock_get_users_for_organisation.assert_called_once_with('596364a0-858e-42c8-9062-a8fe822260af')
|
||||
|
||||
|
||||
def test_registration_from_org_invite_404s_if_user_not_in_session(
|
||||
client,
|
||||
):
|
||||
response = client.get(url_for('main.register_from_org_invite'))
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
@pytest.mark.parametrize('data, error', [
|
||||
[{
|
||||
'name': 'Bad Mobile',
|
||||
'mobile_number': 'not good',
|
||||
'password': 'validPassword!'
|
||||
}, 'Must not contain letters or symbols'],
|
||||
[{
|
||||
'name': 'Bad Password',
|
||||
'mobile_number': '+44123412345',
|
||||
'password': 'password'
|
||||
}, 'Choose a password that’s harder to guess'],
|
||||
])
|
||||
def test_registration_from_org_invite_has_bad_data(
|
||||
client,
|
||||
sample_org_invite,
|
||||
data,
|
||||
error
|
||||
):
|
||||
invited_org_user = InvitedOrgUser(**sample_org_invite)
|
||||
with client.session_transaction() as session:
|
||||
session['invited_org_user'] = invited_org_user.serialize()
|
||||
|
||||
response = client.post(url_for('main.register_from_org_invite'), data=data)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert error in response.get_data(as_text=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('diff_data', [
|
||||
['email_address'],
|
||||
['organisation'],
|
||||
['email_address', 'organisation']
|
||||
])
|
||||
def test_registration_from_org_invite_has_different_email_or_organisation(
|
||||
client,
|
||||
sample_org_invite,
|
||||
diff_data
|
||||
):
|
||||
invited_org_user = InvitedOrgUser(**sample_org_invite)
|
||||
with client.session_transaction() as session:
|
||||
session['invited_org_user'] = invited_org_user.serialize()
|
||||
|
||||
for data in diff_data:
|
||||
session['invited_org_user'][data] = 'different'
|
||||
|
||||
response = client.post(url_for('main.register_from_org_invite'), data={
|
||||
'name': 'Test User',
|
||||
'mobile_number': '+4407700900460',
|
||||
'password': 'validPassword!',
|
||||
'email_address': session['invited_org_user']['email_address'],
|
||||
'organisation': session['invited_org_user']['organisation']
|
||||
})
|
||||
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
def test_org_user_registers_with_email_already_in_use(
|
||||
client,
|
||||
sample_org_invite,
|
||||
mock_email_is_already_in_use,
|
||||
mock_get_user_by_email,
|
||||
mock_accept_org_invite,
|
||||
mock_add_user_to_organisation,
|
||||
mock_send_already_registered_email,
|
||||
mock_register_user
|
||||
):
|
||||
invited_org_user = InvitedOrgUser(**sample_org_invite)
|
||||
with client.session_transaction() as session:
|
||||
session['invited_org_user'] = invited_org_user.serialize()
|
||||
|
||||
response = client.post(url_for('main.register_from_org_invite'), data={
|
||||
'name': 'Test User',
|
||||
'mobile_number': '+4407700900460',
|
||||
'password': 'validPassword!',
|
||||
'email_address': session['invited_org_user']['email_address'],
|
||||
'organisation': session['invited_org_user']['organisation']
|
||||
})
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for('main.verify', _external=True)
|
||||
|
||||
mock_get_user_by_email.assert_called_once_with(
|
||||
session['invited_org_user']['email_address']
|
||||
)
|
||||
assert mock_register_user.called is False
|
||||
assert mock_send_already_registered_email.called is False
|
||||
|
||||
|
||||
def test_org_user_registration(
|
||||
client,
|
||||
sample_org_invite,
|
||||
mock_email_is_not_already_in_use,
|
||||
mock_register_user,
|
||||
mock_send_verify_code,
|
||||
mock_get_user_by_email,
|
||||
mock_send_verify_email,
|
||||
mock_accept_org_invite,
|
||||
mock_add_user_to_organisation,
|
||||
):
|
||||
invited_org_user = InvitedOrgUser(**sample_org_invite)
|
||||
with client.session_transaction() as session:
|
||||
session['invited_org_user'] = invited_org_user.serialize()
|
||||
|
||||
response = client.post(url_for('main.register_from_org_invite'), data={
|
||||
'name': 'Test User',
|
||||
'email_address': session['invited_org_user']['email_address'],
|
||||
'mobile_number': '+4407700900460',
|
||||
'password': 'validPassword!',
|
||||
'organisation': session['invited_org_user']['organisation']
|
||||
})
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for('main.verify', _external=True)
|
||||
|
||||
mock_get_user_by_email.called is False
|
||||
mock_register_user.assert_called_once_with(
|
||||
'Test User',
|
||||
session['invited_org_user']['email_address'],
|
||||
'+4407700900460',
|
||||
'validPassword!',
|
||||
'sms_auth'
|
||||
)
|
||||
mock_send_verify_code.assert_called_once_with(
|
||||
'6ce466d0-fd6a-11e5-82f5-e0accb9d11a6',
|
||||
'sms',
|
||||
'+4407700900460',
|
||||
)
|
||||
|
||||
|
||||
def test_verified_org_user_redirects_to_dashboard(
|
||||
client,
|
||||
sample_org_invite,
|
||||
mock_check_verify_code,
|
||||
mock_get_user,
|
||||
mock_activate_user,
|
||||
mock_login,
|
||||
):
|
||||
invited_org_user = InvitedOrgUser(**sample_org_invite).serialize()
|
||||
with client.session_transaction() as session:
|
||||
session['expiry_date'] = str(datetime.utcnow() + timedelta(hours=1))
|
||||
session['user_details'] = {"email": invited_org_user['email_address'], "id": invited_org_user['id']}
|
||||
session['organisation_id'] = invited_org_user['organisation']
|
||||
|
||||
response = client.post(url_for('main.verify'), data={'sms_code': '12345'})
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for(
|
||||
'main.organisation_dashboard',
|
||||
org_id=invited_org_user['organisation'],
|
||||
_external=True
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ def test_register_creates_new_user_and_redirects_to_continue_page(
|
||||
mock_send_verify_code,
|
||||
mock_register_user,
|
||||
mock_get_user_by_email_not_found,
|
||||
mock_is_email_unique,
|
||||
mock_email_is_not_already_in_use,
|
||||
mock_send_verify_email,
|
||||
mock_login,
|
||||
phone_number_to_register_with,
|
||||
@@ -122,7 +122,7 @@ def test_should_add_user_details_to_session(
|
||||
mock_register_user,
|
||||
mock_get_user,
|
||||
mock_get_user_by_email_not_found,
|
||||
mock_is_email_unique,
|
||||
mock_email_is_not_already_in_use,
|
||||
mock_send_verify_email,
|
||||
mock_login,
|
||||
):
|
||||
@@ -176,7 +176,7 @@ def test_register_with_existing_email_sends_emails(
|
||||
def test_register_from_invite(
|
||||
client,
|
||||
fake_uuid,
|
||||
mock_is_email_unique,
|
||||
mock_email_is_not_already_in_use,
|
||||
mock_register_user,
|
||||
mock_send_verify_code,
|
||||
mock_accept_invite,
|
||||
@@ -207,7 +207,6 @@ def test_register_from_invite(
|
||||
def test_register_from_invite_when_user_registers_in_another_browser(
|
||||
client,
|
||||
api_user_active,
|
||||
mock_is_email_not_unique,
|
||||
mock_get_user_by_email,
|
||||
mock_accept_invite,
|
||||
):
|
||||
@@ -237,7 +236,7 @@ def test_register_from_invite_when_user_registers_in_another_browser(
|
||||
def test_register_from_email_auth_invite(
|
||||
client,
|
||||
sample_invite,
|
||||
mock_is_email_unique,
|
||||
mock_email_is_not_already_in_use,
|
||||
mock_register_user,
|
||||
mock_get_user,
|
||||
mock_send_verify_email,
|
||||
@@ -285,7 +284,7 @@ def test_register_from_email_auth_invite(
|
||||
def test_can_register_email_auth_without_phone_number(
|
||||
client,
|
||||
sample_invite,
|
||||
mock_is_email_unique,
|
||||
mock_email_is_not_already_in_use,
|
||||
mock_register_user,
|
||||
mock_get_user,
|
||||
mock_send_verify_email,
|
||||
|
||||
@@ -30,6 +30,7 @@ def test_should_redirect_after_name_change(
|
||||
logged_in_client,
|
||||
api_user_active,
|
||||
mock_update_user_attribute,
|
||||
mock_email_is_not_already_in_use
|
||||
):
|
||||
new_name = 'New Name'
|
||||
data = {'new_name': new_name}
|
||||
@@ -54,7 +55,7 @@ def test_should_show_email_page(
|
||||
def test_should_redirect_after_email_change(
|
||||
logged_in_client,
|
||||
mock_login,
|
||||
mock_is_email_unique,
|
||||
mock_email_is_not_already_in_use,
|
||||
):
|
||||
data = {'email_address': 'new_notify@notify.gov.uk'}
|
||||
response = logged_in_client.post(
|
||||
|
||||
@@ -11,11 +11,15 @@ from bs4 import BeautifulSoup
|
||||
from app import create_app
|
||||
from app.notify_client.models import (
|
||||
User,
|
||||
InvitedUser
|
||||
InvitedUser,
|
||||
InvitedOrgUser
|
||||
)
|
||||
|
||||
from . import (
|
||||
service_json,
|
||||
organisation_json,
|
||||
user_json,
|
||||
invited_user,
|
||||
TestClient,
|
||||
template_json,
|
||||
template_version_json,
|
||||
@@ -25,7 +29,8 @@ from . import (
|
||||
invite_json,
|
||||
sample_uuid,
|
||||
generate_uuid,
|
||||
single_notification_json
|
||||
single_notification_json,
|
||||
org_invite_json
|
||||
)
|
||||
|
||||
from notifications_utils.url_safe_token import generate_token
|
||||
@@ -1050,7 +1055,8 @@ def api_user_pending(fake_uuid):
|
||||
'mobile_number': '07700 900762',
|
||||
'state': 'pending',
|
||||
'failed_login_count': 0,
|
||||
'permissions': {}
|
||||
'permissions': {},
|
||||
'organisations': []
|
||||
}
|
||||
user = User(user_data)
|
||||
return user
|
||||
@@ -1075,7 +1081,8 @@ def platform_admin_user(fake_uuid):
|
||||
'manage_api_keys',
|
||||
'view_activity']},
|
||||
'platform_admin': True,
|
||||
'auth_type': 'sms_auth'
|
||||
'auth_type': 'sms_auth',
|
||||
'organisations': []
|
||||
}
|
||||
user = User(user_data)
|
||||
return user
|
||||
@@ -1094,7 +1101,8 @@ def api_user_active(fake_uuid, email_address='test@user.gov.uk'):
|
||||
'permissions': {},
|
||||
'platform_admin': False,
|
||||
'auth_type': 'sms_auth',
|
||||
'password_changed_at': str(datetime.utcnow())
|
||||
'password_changed_at': str(datetime.utcnow()),
|
||||
'organisations': []
|
||||
}
|
||||
user = User(user_data)
|
||||
return user
|
||||
@@ -1113,7 +1121,8 @@ def api_user_active_email_auth(fake_uuid, email_address='test@user.gov.uk'):
|
||||
'permissions': {},
|
||||
'platform_admin': False,
|
||||
'auth_type': 'email_auth',
|
||||
'password_changed_at': str(datetime.utcnow())
|
||||
'password_changed_at': str(datetime.utcnow()),
|
||||
'organisations': []
|
||||
}
|
||||
user = User(user_data)
|
||||
return user
|
||||
@@ -1132,7 +1141,8 @@ def api_nongov_user_active(fake_uuid):
|
||||
'permissions': {},
|
||||
'platform_admin': False,
|
||||
'auth_type': 'sms_auth',
|
||||
'password_changed_at': str(datetime.utcnow())
|
||||
'password_changed_at': str(datetime.utcnow()),
|
||||
'organisations': []
|
||||
}
|
||||
user = User(user_data)
|
||||
return user
|
||||
@@ -1159,7 +1169,8 @@ def active_user_with_permissions(fake_uuid):
|
||||
'manage_api_keys',
|
||||
'view_activity']},
|
||||
'platform_admin': False,
|
||||
'auth_type': 'sms_auth'
|
||||
'auth_type': 'sms_auth',
|
||||
'organisations': []
|
||||
}
|
||||
user = User(user_data)
|
||||
return user
|
||||
@@ -1186,7 +1197,8 @@ def active_user_no_mobile(fake_uuid):
|
||||
'manage_api_keys',
|
||||
'view_activity']},
|
||||
'platform_admin': False,
|
||||
'auth_type': 'email_auth'
|
||||
'auth_type': 'email_auth',
|
||||
'organisations': []
|
||||
}
|
||||
user = User(user_data)
|
||||
return user
|
||||
@@ -1206,7 +1218,8 @@ def active_user_view_permissions(fake_uuid):
|
||||
'failed_login_count': 0,
|
||||
'permissions': {SERVICE_ONE_ID: ['view_activity']},
|
||||
'platform_admin': False,
|
||||
'auth_type': 'sms_auth'
|
||||
'auth_type': 'sms_auth',
|
||||
'organisations': []
|
||||
}
|
||||
user = User(user_data)
|
||||
return user
|
||||
@@ -1230,7 +1243,8 @@ def active_user_manage_template_permission(fake_uuid):
|
||||
'view_activity',
|
||||
]},
|
||||
'platform_admin': False,
|
||||
'auth_type': 'sms_auth'
|
||||
'auth_type': 'sms_auth',
|
||||
'organisations': []
|
||||
}
|
||||
user = User(user_data)
|
||||
return user
|
||||
@@ -1255,7 +1269,8 @@ def active_user_no_api_key_permission(fake_uuid):
|
||||
'view_activity',
|
||||
]},
|
||||
'platform_admin': False,
|
||||
'auth_type': 'sms_auth'
|
||||
'auth_type': 'sms_auth',
|
||||
'organisations': []
|
||||
}
|
||||
user = User(user_data)
|
||||
return user
|
||||
@@ -1297,7 +1312,8 @@ def api_user_locked(fake_uuid):
|
||||
'state': 'active',
|
||||
'failed_login_count': 5,
|
||||
'permissions': {},
|
||||
'auth_type': 'sms_auth'
|
||||
'auth_type': 'sms_auth',
|
||||
'organisations': []
|
||||
}
|
||||
user = User(user_data)
|
||||
return user
|
||||
@@ -1315,7 +1331,8 @@ def api_user_request_password_reset(fake_uuid):
|
||||
'failed_login_count': 5,
|
||||
'permissions': {},
|
||||
'password_changed_at': None,
|
||||
'auth_type': 'sms_auth'
|
||||
'auth_type': 'sms_auth',
|
||||
'organisations': []
|
||||
}
|
||||
user = User(user_data)
|
||||
return user
|
||||
@@ -1333,7 +1350,8 @@ def api_user_changed_password(fake_uuid):
|
||||
'failed_login_count': 5,
|
||||
'permissions': {},
|
||||
'auth_type': 'sms_auth',
|
||||
'password_changed_at': str(datetime.utcnow() + timedelta(minutes=1))
|
||||
'password_changed_at': str(datetime.utcnow() + timedelta(minutes=1)),
|
||||
'organisations': []
|
||||
}
|
||||
user = User(user_data)
|
||||
return user
|
||||
@@ -1383,6 +1401,19 @@ def mock_get_user(mocker, user=None):
|
||||
'app.user_api_client.get_user', side_effect=_get_user)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_get_organisation_user(mocker, user=None):
|
||||
if user is None:
|
||||
user = api_user_active(fake_uuid())
|
||||
|
||||
def _get_user(id_):
|
||||
user.id = id_
|
||||
return user
|
||||
|
||||
return mocker.patch(
|
||||
'app.user_api_client.get_user', side_effect=_get_user)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_get_locked_user(mocker, api_user_locked):
|
||||
return mock_get_user(mocker, user=api_user_locked)
|
||||
@@ -1520,13 +1551,13 @@ def mock_activate_user(mocker):
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_is_email_unique(mocker):
|
||||
return mocker.patch('app.user_api_client.is_email_unique', return_value=True)
|
||||
def mock_email_is_already_in_use(mocker):
|
||||
return mocker.patch('app.user_api_client.is_email_already_in_use', return_value=True)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_is_email_not_unique(mocker):
|
||||
return mocker.patch('app.user_api_client.is_email_unique', return_value=False)
|
||||
def mock_email_is_not_already_in_use(mocker):
|
||||
return mocker.patch('app.user_api_client.is_email_already_in_use', return_value=False)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
@@ -1925,14 +1956,14 @@ def mock_get_users_by_service(mocker):
|
||||
'manage_users',
|
||||
'manage_templates',
|
||||
'manage_settings',
|
||||
'manage_api_keys',
|
||||
'access_developer_docs']},
|
||||
'manage_api_keys']},
|
||||
'state': 'active',
|
||||
'password_changed_at': None,
|
||||
'name': 'Test User',
|
||||
'email_address': 'notify@digital.cabinet-office.gov.uk',
|
||||
'auth_type': 'sms_auth',
|
||||
'failed_login_count': 0}]
|
||||
'failed_login_count': 0,
|
||||
'organisations': []}]
|
||||
return [User(data[0])]
|
||||
|
||||
return mocker.patch('app.user_api_client.get_users_for_service', side_effect=_get_users_for_service, autospec=True)
|
||||
@@ -2658,6 +2689,11 @@ def mock_update_service_callback_api(mocker):
|
||||
return mocker.patch('app.service_api_client.update_service_callback_api', side_effect=_update_service_callback_api)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def organisation_one(api_user_active):
|
||||
return organisation_json('596364a0-858e-42c8-9062-a8fe822260af', 'organisation one', [api_user_active.id])
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_get_organisations(mocker):
|
||||
def _get_organisations():
|
||||
@@ -2684,10 +2720,10 @@ def mock_get_organisations(mocker):
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_get_organisation(mocker):
|
||||
def _get_organisation(organisation_id):
|
||||
def _get_organisation(org_id):
|
||||
return {
|
||||
'name': 'Org 1',
|
||||
'id': organisation_id,
|
||||
'id': org_id,
|
||||
'active': True
|
||||
}
|
||||
|
||||
@@ -2730,3 +2766,84 @@ def mock_get_organisation_services(mocker):
|
||||
'app.organisations_client.get_organisation_services',
|
||||
side_effect=_get_organisation_services
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_get_users_for_organisation(mocker):
|
||||
def _get_users_for_organisation(org_id):
|
||||
return [
|
||||
User(user_json(id_='1234', name='Test User 1')),
|
||||
User(user_json(id_='5678', name='Test User 2', email_address='testt@gov.uk'))
|
||||
]
|
||||
|
||||
return mocker.patch(
|
||||
'app.user_api_client.get_users_for_organisation',
|
||||
side_effect=_get_users_for_organisation
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_get_invited_users_for_organisation(mocker):
|
||||
def _get_invited_invited_users_for_organisation(org_id):
|
||||
return [
|
||||
invited_user(organisation='1234')
|
||||
]
|
||||
|
||||
return mocker.patch(
|
||||
'app.org_invite_api_client.get_invites_for_organisation',
|
||||
side_effect=_get_invited_invited_users_for_organisation
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def sample_org_invite(mocker, organisation_one, status='pending'):
|
||||
id_ = str(generate_uuid())
|
||||
invited_by = organisation_one['users'][0]
|
||||
email_address = 'invited_user@test.gov.uk'
|
||||
organisation = organisation_one['id']
|
||||
created_at = str(datetime.utcnow())
|
||||
|
||||
return org_invite_json(id_, invited_by, organisation, email_address, created_at, status)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_check_org_invite_token(mocker, sample_org_invite):
|
||||
def _check_org_token(token):
|
||||
return InvitedOrgUser(**sample_org_invite)
|
||||
|
||||
return mocker.patch('app.org_invite_api_client.check_token', side_effect=_check_org_token)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_check_org_cancelled_invite_token(mocker, sample_org_invite):
|
||||
def _check_org_token(token):
|
||||
sample_org_invite['status'] = 'cancelled'
|
||||
return InvitedOrgUser(**sample_org_invite)
|
||||
|
||||
return mocker.patch('app.org_invite_api_client.check_token', side_effect=_check_org_token)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_check_org_accepted_invite_token(mocker, sample_org_invite):
|
||||
sample_org_invite['status'] = 'accepted'
|
||||
|
||||
def _check_org_token(token):
|
||||
return InvitedOrgUser(**sample_org_invite)
|
||||
|
||||
return mocker.patch('app.org_invite_api_client.check_token', side_effect=_check_org_token)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_accept_org_invite(mocker, sample_org_invite):
|
||||
def _accept(organisation_id, invite_id):
|
||||
return InvitedOrgUser(**sample_org_invite)
|
||||
|
||||
return mocker.patch('app.org_invite_api_client.accept_invite', side_effect=_accept)
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_add_user_to_organisation(mocker, organisation_one, api_user_active):
|
||||
def _add_user(organisation_id, user_id):
|
||||
return api_user_active
|
||||
|
||||
return mocker.patch('app.user_api_client.add_user_to_organisation', side_effect=_add_user)
|
||||
|
||||
Reference in New Issue
Block a user