Merge pull request #2991 from alphagov/user-api-client-return-json

Make user API client return JSON, not a model
This commit is contained in:
Chris Hill-Scott
2019-06-06 09:51:52 +01:00
committed by GitHub
57 changed files with 1138 additions and 991 deletions

View File

@@ -48,7 +48,7 @@ from app.extensions import (
)
from app.models.organisation import Organisation
from app.models.service import Service
from app.models.user import AnonymousUser
from app.models.user import AnonymousUser, User
from app.navigation import (
CaseworkNavigation,
HeaderNavigation,
@@ -463,7 +463,7 @@ def nl2br(value):
@login_manager.user_loader
def load_user(user_id):
return user_api_client.get_user(user_id)
return User.from_id(user_id)
def load_service_before_request():

View File

@@ -44,7 +44,7 @@ from app.main.validators import (
ValidEmail,
ValidGovEmail,
)
from app.models.user import permissions, roles
from app.models.roles_and_permissions import permissions, roles
from app.utils import guess_name_from_email_address
@@ -373,11 +373,11 @@ class RegisterUserForm(StripWhitespaceForm):
class RegisterUserFromInviteForm(RegisterUserForm):
def __init__(self, invited_user):
super().__init__(
service=invited_user['service'],
email_address=invited_user['email_address'],
auth_type=invited_user['auth_type'],
service=invited_user.service,
email_address=invited_user.email_address,
auth_type=invited_user.auth_type,
name=guess_name_from_email_address(
invited_user['email_address']
invited_user.email_address
),
)
@@ -394,8 +394,8 @@ class RegisterUserFromInviteForm(RegisterUserForm):
class RegisterUserFromOrgInviteForm(StripWhitespaceForm):
def __init__(self, invited_org_user):
super().__init__(
organisation=invited_org_user['organisation'],
email_address=invited_org_user['email_address'],
organisation=invited_org_user.organisation,
email_address=invited_org_user.email_address,
)
name = StringField(

View File

@@ -3,21 +3,22 @@ from flask import redirect, render_template, session, url_for
from app import user_api_client
from app.main import main
from app.main.forms import TextNotReceivedForm
from app.models.user import User
from app.utils import redirect_to_sign_in
@main.route('/resend-email-verification')
@redirect_to_sign_in
def resend_email_verification():
user = user_api_client.get_user_by_email(session['user_details']['email'])
user_api_client.send_verify_email(user.id, user.email_address)
user = User.from_email_address(session['user_details']['email'])
user.send_verify_email()
return render_template('views/resend-email-verification.html', email=user.email_address)
@main.route('/text-not-received', methods=['GET', 'POST'])
@redirect_to_sign_in
def check_and_resend_text_code():
user = user_api_client.get_user_by_email(session['user_details']['email'])
user = User.from_email_address(session['user_details']['email'])
if user.state == 'active':
# this is a verified user and therefore redirect to page to request resend without edit mobile
@@ -25,8 +26,8 @@ def check_and_resend_text_code():
form = TextNotReceivedForm(mobile_number=user.mobile_number)
if form.validate_on_submit():
user_api_client.send_verify_code(user.id, 'sms', to=form.mobile_number.data)
user = user_api_client.update_user_attribute(user.id, mobile_number=form.mobile_number.data)
user.send_verify_code(to=form.mobile_number.data)
user.update(mobile_number=form.mobile_number.data)
return redirect(url_for('.verify'))
return render_template('views/text-not-received.html', form=form)
@@ -35,8 +36,8 @@ def check_and_resend_text_code():
@main.route('/send-new-code', methods=['GET'])
@redirect_to_sign_in
def check_and_resend_verification_code():
user = user_api_client.get_user_by_email(session['user_details']['email'])
user_api_client.send_verify_code(user.id, 'sms', user.mobile_number)
user = User.from_email_address(session['user_details']['email'])
user.send_verify_code()
if user.state == 'pending':
return redirect(url_for('main.verify'))
else:

View File

@@ -4,6 +4,7 @@ from flask_login import login_required
from app import user_api_client
from app.main import main
from app.main.forms import SearchUsersByEmailForm
from app.models.user import User
from app.utils import user_is_platform_admin
@@ -29,7 +30,7 @@ def find_users_by_email():
@login_required
@user_is_platform_admin
def user_information(user_id):
user = user_api_client.get_user(user_id)
user = User.from_id(user_id)
services = user_api_client.get_services_for_user(user)
return render_template(
'views/find-users/user-information.html',

View File

@@ -1,28 +1,27 @@
from flask import abort, flash, redirect, render_template, session, url_for
from flask_login import current_user
from markupsafe import Markup
from notifications_python_client.errors import HTTPError
from app import (
invite_api_client,
org_invite_api_client,
organisations_client,
service_api_client,
user_api_client,
)
from app.main import main
from app.models.organisation import Organisation
from app.models.service import Service
from app.models.user import (
InvitedOrgUser,
InvitedUser,
OrganisationUsers,
User,
Users,
)
from app.notify_client import InviteTokenError
@main.route("/invitation/<token>")
def accept_invite(token):
try:
invited_user = invite_api_client.check_token(token)
except HTTPError as e:
if e.status_code == 400 and 'invitation' in e.message:
flash(e.message['invitation'])
return redirect(url_for('main.sign_in'))
else:
raise e
invited_user = InvitedUser.from_token(token)
except InviteTokenError as exception:
flash(str(exception))
return redirect(url_for('main.sign_in'))
if not current_user.is_anonymous and current_user.email_address.lower() != invited_user.email_address.lower():
message = Markup("""
@@ -38,11 +37,10 @@ def accept_invite(token):
abort(403)
if invited_user.status == 'cancelled':
from_user = user_api_client.get_user(invited_user.from_user)
service = service_api_client.get_service(invited_user.service)['data']
service = Service.from_id(invited_user.service)
return render_template('views/cancelled-invitation.html',
from_user=from_user.name,
service_name=service['name'])
from_user=invited_user.from_user.name,
service_name=service.name)
if invited_user.status == 'accepted':
session.pop('invited_user', None)
@@ -50,36 +48,32 @@ def accept_invite(token):
session['invited_user'] = invited_user.serialize()
existing_user = user_api_client.get_user_by_email_or_none(invited_user.email_address)
service_users = user_api_client.get_users_for_service(invited_user.service)
existing_user = User.from_email_address_or_none(invited_user.email_address)
if existing_user:
invite_api_client.accept_invite(invited_user.service, invited_user.id)
if existing_user in service_users:
invited_user.accept_invite()
if existing_user in Users(invited_user.service):
return redirect(url_for('main.service_dashboard', service_id=invited_user.service))
else:
service = service_api_client.get_service(invited_user.service)['data']
service = Service.from_id(invited_user.service)
# if the service you're being added to can modify auth type, then check if this is relevant
if 'email_auth' in service['permissions'] and (
# they have a phone number, we want them to start using it. if they dont have a mobile we just
# ignore that option of the invite
(existing_user.mobile_number and invited_user.auth_type == 'sms_auth') or
# we want them to start sending emails. it's always valid, so lets always update
invited_user.auth_type == 'email_auth'
if service.has_permission('email_auth') and (
# they have a phone number, we want them to start using it. if they dont have a mobile we just
# ignore that option of the invite
(existing_user.mobile_number and invited_user.auth_type == 'sms_auth') or
# we want them to start sending emails. it's always valid, so lets always update
invited_user.auth_type == 'email_auth'
):
user_api_client.update_user_attribute(existing_user.id, auth_type=invited_user.auth_type)
user_api_client.add_user_to_service(invited_user.service,
existing_user.id,
invited_user.permissions,
invited_user.folder_permissions)
return redirect(url_for('main.service_dashboard', service_id=invited_user.service))
existing_user.update(auth_type=invited_user.auth_type)
invited_user.add_to_service()
return redirect(url_for('main.service_dashboard', service_id=service.id))
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)
invited_org_user = InvitedOrgUser.from_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 {}.
@@ -94,11 +88,10 @@ def accept_org_invite(token):
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)
organisation = Organisation.from_id(invited_org_user.organisation)
return render_template('views/cancelled-invitation.html',
from_user=invited_by.name,
organisation_name=organisation['name'])
from_user=invited_org_user.invited_by.name,
organisation_name=organisation.name)
if invited_org_user.status == 'accepted':
session.pop('invited_org_user', None)
@@ -106,13 +99,13 @@ def accept_org_invite(token):
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)
existing_user = User.from_email_address_or_none(invited_org_user.email_address)
organisation_users = OrganisationUsers(invited_org_user.organisation)
if existing_user:
org_invite_api_client.accept_invite(invited_org_user.organisation, invited_org_user.id)
invited_org_user.accept_invite()
if existing_user not in organisation_users:
user_api_client.add_user_to_organisation(invited_org_user.organisation, existing_user.id)
invited_org_user.add_to_organisation()
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

@@ -10,12 +10,7 @@ from flask import (
from flask_login import current_user, login_required
from notifications_python_client.errors import HTTPError
from app import (
current_service,
invite_api_client,
service_api_client,
user_api_client,
)
from app import current_service, service_api_client
from app.event_handlers import (
create_email_change_event,
create_mobile_number_change_event,
@@ -29,7 +24,8 @@ from app.main.forms import (
PermissionsForm,
SearchUsersForm,
)
from app.models.user import permissions
from app.models.roles_and_permissions import permissions
from app.models.user import InvitedUser, User
from app.utils import is_gov_user, redact_mobile_number, user_has_permissions
@@ -64,7 +60,7 @@ def invite_user(service_id):
if form.validate_on_submit():
email_address = form.email_address.data
invited_user = invite_api_client.create_invite(
invited_user = InvitedUser.create(
current_user.id,
service_id,
email_address,
@@ -106,13 +102,13 @@ def edit_user_permissions(service_id, user_id):
)
if form.validate_on_submit():
user_api_client.set_user_permissions(
user_id, service_id,
user.set_permissions(
service_id,
permissions=form.permissions,
folder_permissions=form.folder_permissions.data,
)
if service_has_email_auth:
user_api_client.update_user_attribute(user_id, auth_type=form.login_authentication.data)
user.update(auth_type=form.login_authentication.data)
return redirect(url_for('.manage_users', service_id=service_id))
return render_template(
@@ -154,13 +150,10 @@ def edit_user_email(service_id, user_id):
user = current_service.get_team_member(user_id)
user_email = user.email_address
def _is_email_already_in_use(email):
return user_api_client.is_email_already_in_use(email)
if is_gov_user(user_email):
form = ChangeEmailForm(_is_email_already_in_use, email_address=user_email)
form = ChangeEmailForm(User.already_registered, email_address=user_email)
else:
form = ChangeNonGovEmailForm(_is_email_already_in_use, email_address=user_email)
form = ChangeNonGovEmailForm(User.already_registered, email_address=user_email)
if request.form.get('email_address', '').strip() == user_email:
return redirect(url_for('.manage_users', service_id=current_service.id))
@@ -193,7 +186,7 @@ def confirm_edit_user_email(service_id, user_id):
))
if request.method == 'POST':
try:
user_api_client.update_user_attribute(str(user_id), email_address=new_email, updated_by=current_user.id)
user.update(email_address=new_email, updated_by=current_user.id)
except HTTPError as e:
abort(500, e)
else:
@@ -253,7 +246,7 @@ def confirm_edit_user_mobile_number(service_id, user_id):
))
if request.method == 'POST':
try:
user_api_client.update_user_attribute(str(user_id), mobile_number=new_number, updated_by=current_user.id)
user.update(mobile_number=new_number, updated_by=current_user.id)
except HTTPError as e:
abort(500, e)
else:

View File

@@ -12,10 +12,10 @@ from flask import (
from itsdangerous import SignatureExpired
from notifications_utils.url_safe_token import check_token
from app import user_api_client
from app.main import main
from app.main.forms import NewPasswordForm
from app.main.views.two_factor import log_in_user
from app.models.user import User
@main.route('/new-password/<path:token>', methods=['GET', 'POST'])
@@ -28,7 +28,7 @@ def new_password(token):
return redirect(url_for('.forgot_password'))
email_address = json.loads(token_data)['email']
user = user_api_client.get_user_by_email(email_address)
user = User.from_email_address(email_address)
if user.password_changed_at and datetime.strptime(user.password_changed_at, '%Y-%m-%d %H:%M:%S.%f') > \
datetime.strptime(json.loads(token_data)['created_at'], '%Y-%m-%d %H:%M:%S.%f'):
flash('The link in the email has already been used')
@@ -37,7 +37,7 @@ def new_password(token):
form = NewPasswordForm()
if form.validate_on_submit():
user_api_client.reset_failed_login_count(user.id)
user.reset_failed_login_count()
session['user_details'] = {
'id': user.id,
'email': user.email_address,
@@ -47,7 +47,7 @@ def new_password(token):
return log_in_user(user.id)
else:
# send user a 2fa sms code
user_api_client.send_verify_code(user.id, 'sms', user.mobile_number)
user.send_verify_code()
return redirect(url_for('main.two_factor'))
else:
return render_template('views/new-password.html', token=token, form=form, user=user)

View File

@@ -31,6 +31,7 @@ from app.main.forms import (
SetLetterBranding,
)
from app.main.views.service_settings import get_branding_as_value_and_label
from app.models.user import InvitedOrgUser, User
from app.utils import user_has_permissions, user_is_platform_admin
@@ -112,7 +113,7 @@ def invite_org_user(org_id):
)
if form.validate_on_submit():
email_address = form.email_address.data
invited_org_user = org_invite_api_client.create_invite(
invited_org_user = InvitedOrgUser.create(
current_user.id,
org_id,
email_address
@@ -131,11 +132,9 @@ def invite_org_user(org_id):
@login_required
@user_has_permissions()
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
user=User.from_id(user_id)
)
@@ -143,7 +142,7 @@ def edit_user_org_permissions(org_id, user_id):
@login_required
@user_has_permissions()
def remove_user_from_organisation(org_id, user_id):
user = user_api_client.get_user(user_id)
user = User.from_id(user_id)
if request.method == 'POST':
try:
organisations_client.remove_user_from_organisation(org_id, user_id)

View File

@@ -3,7 +3,6 @@ from datetime import datetime, timedelta
from flask import abort, redirect, render_template, session, url_for
from flask_login import current_user
from app import invite_api_client, org_invite_api_client, user_api_client
from app.main import main
from app.main.forms import (
RegisterUserForm,
@@ -11,6 +10,7 @@ from app.main.forms import (
RegisterUserFromOrgInviteForm,
)
from app.main.views.verify import activate_user
from app.models.user import InvitedOrgUser, InvitedUser, User
@main.route('/register', methods=['GET', 'POST'])
@@ -28,20 +28,18 @@ def register():
@main.route('/register-from-invite', methods=['GET', 'POST'])
def register_from_invite():
invited_user = session.get('invited_user')
invited_user = InvitedUser.from_session()
if not invited_user:
abort(404)
is_sms_auth = invited_user['auth_type'] == 'sms_auth'
form = RegisterUserFromInviteForm(invited_user)
if form.validate_on_submit():
if form.service.data != invited_user['service'] or form.email_address.data != invited_user['email_address']:
if form.service.data != invited_user.service or form.email_address.data != invited_user.email_address:
abort(400)
_do_registration(form, send_email=False, send_sms=is_sms_auth)
invite_api_client.accept_invite(invited_user['service'], invited_user['id'])
if is_sms_auth:
_do_registration(form, send_email=False, send_sms=invited_user.sms_auth)
invited_user.accept_invite()
if invited_user.sms_auth:
return redirect(url_for('main.verify'))
else:
# we've already proven this user has email because they clicked the invite link,
@@ -53,7 +51,7 @@ def register_from_invite():
@main.route('/register-from-org-invite', methods=['GET', 'POST'])
def register_from_org_invite():
invited_org_user = session.get('invited_org_user')
invited_org_user = InvitedOrgUser.from_session()
if not invited_org_user:
abort(404)
@@ -63,34 +61,37 @@ def register_from_org_invite():
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']):
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'])
_do_registration(form, send_email=False, send_sms=True, organisation_id=invited_org_user.organisation)
invited_org_user.accept_invite()
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)
user = User.from_email_address_or_none(form.email_address.data)
if user:
if send_email:
user_api_client.send_already_registered_email(user.id, user.email_address)
user.send_already_registered_email()
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)
user = User.register(
name=form.name.data,
email_address=form.email_address.data,
mobile_number=form.mobile_number.data,
password=form.password.data,
auth_type=form.auth_type.data,
)
if send_email:
user_api_client.send_verify_email(user.id, user.email_address)
user.send_verify_email()
if send_sms:
user_api_client.send_verify_code(user.id, 'sms', user.mobile_number)
user.send_verify_code()
session['expiry_date'] = str(datetime.utcnow() + timedelta(hours=1))
session['user_details'] = {"email": user.email_address, "id": user.id}
if organisation_id:

View File

@@ -32,7 +32,6 @@ from app import (
job_api_client,
notification_api_client,
service_api_client,
user_api_client,
)
from app.main import main
from app.main.forms import (
@@ -41,6 +40,7 @@ from app.main.forms import (
SetSenderForm,
get_placeholder_form_instance,
)
from app.models.user import Users
from app.s3_client.s3_csv_client import (
s3download,
s3upload,
@@ -520,8 +520,6 @@ def _check_messages(service_id, template_id, upload_id, preview_row, letters_as_
if e.status_code != 404:
raise
users = user_api_client.get_users_for_service(service_id=service_id)
statistics = service_api_client.get_service_statistics(service_id, today_only=True)
remaining_messages = (current_service.message_limit - sum(stat['requested'] for stat in statistics.values()))
@@ -559,7 +557,7 @@ def _check_messages(service_id, template_id, upload_id, preview_row, letters_as_
max_initial_rows_shown=50,
max_errors_shown=50,
whitelist=itertools.chain.from_iterable(
[user.name, user.mobile_number, user.email_address] for user in users
[user.name, user.mobile_number, user.email_address] for user in Users(service_id)
) if current_service.trial_mode else None,
remaining_messages=remaining_messages,
international_sms=current_service.has_permission('international_sms'),

View File

@@ -10,9 +10,10 @@ from flask import (
)
from flask_login import current_user
from app import invite_api_client, login_manager, user_api_client
from app import login_manager
from app.main import main
from app.main.forms import LoginForm
from app.models.user import InvitedUser, User
@main.route('/sign-in', methods=(['GET', 'POST']))
@@ -24,26 +25,26 @@ def sign_in():
if form.validate_on_submit():
user = user_api_client.get_user_by_email_or_none(form.email_address.data)
user = _get_and_verify_user(user, form.password.data)
user = User.from_email_address_and_password_or_none(
form.email_address.data, form.password.data
)
if user and user.state == 'pending':
return redirect(url_for('main.resend_email_verification'))
if user and session.get('invited_user'):
invited_user = session.get('invited_user')
if user.email_address.lower() != invited_user['email_address'].lower():
invited_user = InvitedUser.from_session()
if user.email_address.lower() != invited_user.email_address.lower():
flash("You can't accept an invite for another person.")
session.pop('invited_user', None)
abort(403)
else:
invite_api_client.accept_invite(invited_user['service'], invited_user['id'])
if user:
session['user_details'] = {"email": user.email_address, "id": user.id}
if user.is_active:
if user.auth_type == "email_auth":
return sign_in_email(user.id, user.email_address)
else:
return sign_in_sms(user.id, user.mobile_number)
invited_user.accept_invite()
if user and user.sign_in():
if user.sms_auth:
return redirect(url_for('.two_factor', next=request.args.get('next')))
if user.email_auth:
return redirect(url_for('.two_factor_email_sent'))
# Vague error message for login in case of user not known, locked, inactive or password not verified
flash(Markup(
@@ -62,35 +63,8 @@ def sign_in():
)
def sign_in_email(user_id, to):
if request.args.get('next'):
user_api_client.send_verify_code(user_id, 'email', None, request.args.get('next'))
else:
user_api_client.send_verify_code(user_id, 'email', None)
return redirect(url_for('.two_factor_email_sent'))
def sign_in_sms(user_id, to):
user_api_client.send_verify_code(user_id, 'sms', to)
if request.args.get('next'):
return redirect(url_for('.two_factor', next=request.args.get('next')))
else:
return redirect(url_for('.two_factor'))
@login_manager.unauthorized_handler
def sign_in_again():
return redirect(
url_for('main.sign_in', next=request.path)
)
def _get_and_verify_user(user, password):
if not user:
return None
elif user.is_locked():
return None
elif not user_api_client.verify_password(user.id, password):
return None
else:
return user

View File

@@ -8,13 +8,14 @@ from flask import (
session,
url_for,
)
from flask_login import current_user, login_user
from flask_login import current_user
from itsdangerous import SignatureExpired
from notifications_utils.url_safe_token import check_token
from app import user_api_client
from app.main import main
from app.main.forms import TwoFactorForm
from app.models.user import User
from app.utils import redirect_to_sign_in
@@ -30,7 +31,7 @@ def two_factor_email_sent():
@main.route('/email-auth/<token>', methods=['GET'])
def two_factor_email(token):
if current_user.is_authenticated:
return redirect_when_logged_in(current_user.id)
return redirect_when_logged_in(platform_admin=current_user.platform_admin)
# checks url is valid, and hasn't timed out
try:
@@ -79,27 +80,27 @@ def _is_safe_redirect_url(target):
def log_in_user(user_id):
try:
user = user_api_client.get_user(user_id)
user = User.from_id(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
# Check if coming from new password page
if 'password' in session.get('user_details', {}):
user = user_api_client.update_password(user.id, password=session['user_details']['password'])
activated_user = user_api_client.activate_user(user)
login_user(activated_user)
user.update_password(session['user_details']['password'])
user.activate()
user.login()
finally:
# get rid of anything in the session that we don't expect to have been set during register/sign in flow
session.pop("user_details", None)
session.pop("file_uploads", None)
return redirect_when_logged_in(user_id)
return redirect_when_logged_in(platform_admin=user.platform_admin)
def redirect_when_logged_in(user_id):
def redirect_when_logged_in(platform_admin):
next_url = request.args.get('next')
if next_url and _is_safe_redirect_url(next_url):
return redirect(next_url)
if current_user.platform_admin:
if platform_admin:
return redirect(url_for('main.platform_admin'))
return redirect(url_for('main.show_accounts_or_dashboard'))

View File

@@ -14,6 +14,7 @@ from app.main.forms import (
ConfirmPasswordForm,
TwoFactorForm,
)
from app.models.user import User
from app.utils import user_is_gov_user
NEW_EMAIL = 'new-email'
@@ -37,7 +38,7 @@ def user_profile_name():
form = ChangeNameForm(new_name=current_user.name)
if form.validate_on_submit():
user_api_client.update_user_attribute(current_user.id, name=form.new_name.data)
current_user.update(name=form.new_name.data)
return redirect(url_for('.user_profile'))
return render_template(
@@ -52,9 +53,7 @@ def user_profile_name():
@user_is_gov_user
def user_profile_email():
def _is_email_already_in_use(email):
return user_api_client.is_email_already_in_use(email)
form = ChangeEmailForm(_is_email_already_in_use,
form = ChangeEmailForm(User.already_registered,
email_address=current_user.email_address)
if form.validate_on_submit():
@@ -99,9 +98,8 @@ def user_profile_email_confirm(token):
current_app.config['DANGEROUS_SALT'],
current_app.config['EMAIL_EXPIRY_SECONDS'])
token_data = json.loads(token_data)
user_id = token_data['user_id']
new_email = token_data['email']
user_api_client.update_user_attribute(user_id, email_address=new_email)
user = User.from_id(token_data['user_id'])
user.update(email_address=token_data['email'])
session.pop(NEW_EMAIL, None)
return redirect(url_for('.user_profile'))
@@ -138,7 +136,7 @@ def user_profile_mobile_number_authenticate():
if form.validate_on_submit():
session[NEW_MOBILE_PASSWORD_CONFIRMED] = True
user_api_client.send_verify_code(current_user.id, 'sms', session[NEW_MOBILE])
current_user.send_verify_code(to=session[NEW_MOBILE])
return redirect(url_for('.user_profile_mobile_number_confirm'))
return render_template(
@@ -163,13 +161,11 @@ def user_profile_mobile_number_confirm():
form = TwoFactorForm(_check_code)
if form.validate_on_submit():
user = user_api_client.get_user(current_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
current_user.refresh_session_id()
mobile_number = session[NEW_MOBILE]
del session[NEW_MOBILE]
del session[NEW_MOBILE_PASSWORD_CONFIRMED]
user_api_client.update_user_attribute(current_user.id, mobile_number=mobile_number)
current_user.update(mobile_number=mobile_number)
return redirect(url_for('.user_profile'))
return render_template(

View File

@@ -9,14 +9,13 @@ from flask import (
session,
url_for,
)
from flask_login import login_user
from itsdangerous import SignatureExpired
from notifications_utils.url_safe_token import check_token
from app import user_api_client
from app.main import main
from app.main.forms import TwoFactorForm
from app.models.user import InvitedUser
from app.models.user import InvitedUser, User
from app.utils import redirect_to_sign_in
@@ -54,7 +53,7 @@ def verify_email(token):
# token contains json blob of format: {'user_id': '...', 'secret_code': '...'} (secret_code is unused)
token_data = json.loads(token_data)
user = user_api_client.get_user(token_data['user_id'])
user = User.from_id(token_data['user_id'])
if not user:
abort(404)
@@ -63,17 +62,17 @@ def verify_email(token):
return redirect(url_for('main.sign_in'))
session['user_details'] = {"email": user.email_address, "id": user.id}
user_api_client.send_verify_code(user.id, 'sms', user.mobile_number)
user.send_verify_code()
return redirect(url_for('main.verify'))
def activate_user(user_id):
user = user_api_client.get_user(user_id)
user = User.from_id(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')
activated_user = user_api_client.activate_user(user)
login_user(activated_user)
activated_user = user.activate()
activated_user.login()
invited_user = session.get('invited_user')
if invited_user:
@@ -91,8 +90,8 @@ def activate_user(user_id):
def _add_invited_user_to_service(invited_user):
invitation = InvitedUser(**invited_user)
user = user_api_client.get_user(session['user_id'])
invitation = InvitedUser(invited_user)
user = User.from_id(session['user_id'])
service_id = invited_user['service']
user_api_client.add_user_to_service(service_id, user.id, invitation.permissions, invitation.folder_permissions)
return service_id

View File

@@ -15,6 +15,7 @@ class JSONModel():
def __getattr__(self, attr):
if attr in self.ALLOWED_PROPERTIES:
return self._dict[attr]
return
raise AttributeError('`{}` is not a {} attribute'.format(
attr,
self.__class__.__name__.lower(),
@@ -25,3 +26,7 @@ class JSONModel():
return next(thing for thing in things if thing['id'] == str(id))
except StopIteration:
abort(404)
class InviteTokenError(Exception):
pass

View File

@@ -131,20 +131,18 @@ class Organisation(JSONModel):
return [s for s in self.services if not s['active'] or s['restricted']]
@cached_property
def active_users(self):
# need to put this here to prevent cyclical import
from app.notify_client.user_api_client import user_api_client
return user_api_client.get_users_for_organisation(org_id=self.id)
def invited_users(self):
from app.models.user import OrganisationInvitedUsers
return OrganisationInvitedUsers(self.id)
@cached_property
def invited_users(self):
# need to put this here to prevent cyclical import
from app.notify_client.org_invite_api_client import org_invite_api_client
return org_invite_api_client.get_invites_for_organisation(org_id=self.id)
def active_users(self):
from app.models.user import OrganisationUsers
return OrganisationUsers(self.id)
@cached_property
def team_members(self):
return sorted(
self.active_users + [i for i in self.invited_users if i.status != 'accepted'],
key=lambda user: user.email_address,
self.invited_users + self.active_users,
key=lambda user: user.email_address.lower(),
)

View File

@@ -0,0 +1,44 @@
from itertools import chain
roles = {
'send_messages': ['send_texts', 'send_emails', 'send_letters'],
'manage_templates': ['manage_templates'],
'manage_service': ['manage_users', 'manage_settings'],
'manage_api_keys': ['manage_api_keys'],
'view_activity': ['view_activity'],
}
# same dict as above, but flipped round
roles_by_permission = {
permission: next(
role for role, permissions in roles.items() if permission in permissions
) for permission in chain(*list(roles.values()))
}
all_permissions = set(roles_by_permission.values())
permissions = (
('view_activity', 'See dashboard'),
('send_messages', 'Send messages'),
('manage_templates', 'Add and edit templates'),
('manage_service', 'Manage settings, team and usage'),
('manage_api_keys', 'Manage API integration'),
)
def translate_permissions_from_db_to_admin_roles(permissions):
"""
Given a list of database permissions, return a set of roles
look them up in roles_by_permission, falling back to just passing through from the api if they aren't in the dict
"""
return {roles_by_permission.get(permission, permission) for permission in permissions}
def translate_permissions_from_admin_roles_to_db(permissions):
"""
Given a list of admin roles (ie: checkboxes on a permissions edit page for example), return a set of db permissions
Looks them up in the roles dict, falling back to just passing through if they're not recognised.
"""
return set(chain.from_iterable(roles.get(permission, [permission]) for permission in permissions))

View File

@@ -4,6 +4,7 @@ from werkzeug.utils import cached_property
from app.models import JSONModel
from app.models.organisation import Organisation
from app.models.user import InvitedUsers, User, Users
from app.notify_client.api_key_api_client import api_key_api_client
from app.notify_client.billing_api_client import billing_api_client
from app.notify_client.email_branding_client import email_branding_client
@@ -16,7 +17,6 @@ from app.notify_client.service_api_client import service_api_client
from app.notify_client.template_folder_api_client import (
template_folder_api_client,
)
from app.notify_client.user_api_client import user_api_client
from app.utils import get_default_sms_sender
@@ -60,6 +60,10 @@ class Service(JSONModel):
if 'permissions' not in self._dict:
self.permissions = {'email', 'sms', 'letter'}
@classmethod
def from_id(cls, service_id):
return cls(service_api_client.get_service(service_id)['data'])
def update(self, **kwargs):
return service_api_client.update_service(self.id, **kwargs)
@@ -99,11 +103,11 @@ class Service(JSONModel):
@cached_property
def invited_users(self):
return invite_api_client.get_invites_for_service(service_id=self.id)
return InvitedUsers(self.id)
@cached_property
def active_users(self):
return user_api_client.get_users_for_service(service_id=self.id)
return Users(self.id)
@cached_property
def team_members(self):
@@ -114,16 +118,12 @@ class Service(JSONModel):
@cached_property
def has_team_members(self):
return (
user_api_client.get_count_of_users_with_permission(
self.id, 'manage_service'
) + invite_api_client.get_count_of_invites_with_permission(
self.id, 'manage_service'
)
) > 1
return len([
user for user in self.team_members
if user.has_permission_for_service(self.id, 'manage_service')
]) > 1
def cancel_invite(self, invited_user_id):
if str(invited_user_id) not in {user.id for user in self.invited_users}:
abort(404)
@@ -137,7 +137,7 @@ class Service(JSONModel):
if str(user_id) not in {user.id for user in self.active_users}:
abort(404)
return user_api_client.get_user(user_id)
return User.from_id(user_id)
@cached_property
def all_templates(self):

View File

@@ -1,37 +1,22 @@
from itertools import chain
from collections.abc import Sequence
from flask import abort, request, session
from flask_login import AnonymousUserMixin, UserMixin
from flask import abort, current_app, request, session
from flask_login import AnonymousUserMixin, UserMixin, login_user
from notifications_python_client.errors import HTTPError
from werkzeug.utils import cached_property
from app.models import JSONModel
from app.models.organisation import Organisation
from app.notify_client.organisations_api_client import organisations_client
from app.utils import is_gov_user
roles = {
'send_messages': ['send_texts', 'send_emails', 'send_letters'],
'manage_templates': ['manage_templates'],
'manage_service': ['manage_users', 'manage_settings'],
'manage_api_keys': ['manage_api_keys'],
'view_activity': ['view_activity'],
}
# same dict as above, but flipped round
roles_by_permission = {
permission: next(
role for role, permissions in roles.items() if permission in permissions
) for permission in chain(*list(roles.values()))
}
all_permissions = set(roles_by_permission.values())
permissions = (
('view_activity', 'See dashboard'),
('send_messages', 'Send messages'),
('manage_templates', 'Add and edit templates'),
('manage_service', 'Manage settings, team and usage'),
('manage_api_keys', 'Manage API integration'),
from app.models.roles_and_permissions import (
all_permissions,
translate_permissions_from_db_to_admin_roles,
)
from app.notify_client import InviteTokenError
from app.notify_client.invite_api_client import invite_api_client
from app.notify_client.org_invite_api_client import org_invite_api_client
from app.notify_client.organisations_api_client import organisations_client
from app.notify_client.user_api_client import user_api_client
from app.utils import is_gov_user
def _get_service_id_from_view_args():
@@ -42,43 +27,66 @@ def _get_org_id_from_view_args():
return str(request.view_args.get('org_id', '')) or None
def translate_permissions_from_db_to_admin_roles(permissions):
"""
Given a list of database permissions, return a set of roles
class User(JSONModel, UserMixin):
look them up in roles_by_permission, falling back to just passing through from the api if they aren't in the dict
"""
return {roles_by_permission.get(permission, permission) for permission in permissions}
ALLOWED_PROPERTIES = {
'id',
'name',
'email_address',
'auth_type',
'current_session_id',
'failed_login_count',
'logged_in_at',
'mobile_number',
'organisations',
'password_changed_at',
'permissions',
'platform_admin',
'services',
'state',
}
def __init__(self, _dict):
super().__init__(_dict)
self.permissions = _dict.get('permissions', {})
self.max_failed_login_count = current_app.config['MAX_FAILED_LOGIN_COUNT']
def translate_permissions_from_admin_roles_to_db(permissions):
"""
Given a list of admin roles (ie: checkboxes on a permissions edit page for example), return a set of db permissions
@classmethod
def from_id(cls, user_id):
return cls(user_api_client.get_user(user_id))
Looks them up in the roles dict, falling back to just passing through if they're not recognised.
"""
return set(chain.from_iterable(roles.get(permission, [permission]) for permission in permissions))
@classmethod
def from_email_address(cls, email_address):
return cls(user_api_client.get_user_by_email(email_address))
@classmethod
def from_email_address_or_none(cls, email_address):
response = user_api_client.get_user_by_email_or_none(email_address)
if response:
return cls(response)
return None
class User(UserMixin):
def __init__(self, fields, max_failed_login_count=3):
self.id = fields.get('id')
self.name = fields.get('name')
self.email_address = fields.get('email_address')
self.mobile_number = fields.get('mobile_number')
self.password_changed_at = fields.get('password_changed_at')
self._set_permissions(fields.get('permissions', {}))
self.auth_type = fields.get('auth_type')
self.failed_login_count = fields.get('failed_login_count')
self.state = fields.get('state')
self.max_failed_login_count = max_failed_login_count
self.logged_in_at = fields.get('logged_in_at')
self.platform_admin = fields.get('platform_admin')
self.current_session_id = fields.get('current_session_id')
self.services = fields.get('services', [])
self.organisations = fields.get('organisations', [])
@staticmethod
def already_registered(email_address):
return bool(User.from_email_address_or_none(email_address))
def _set_permissions(self, permissions_by_service):
@classmethod
def from_email_address_and_password_or_none(cls, email_address, password):
user = cls.from_email_address_or_none(email_address)
if not user:
return None
if user.locked:
return None
if not user_api_client.verify_password(user.id, password):
return None
return user
@property
def permissions(self):
return self._permissions
@permissions.setter
def permissions(self, permissions_by_service):
"""
Permissions is a dict {'service_id': ['permission a', 'permission b', 'permission c']}
@@ -96,13 +104,61 @@ class User(UserMixin):
in permissions_by_service.items()
}
def get_id(self):
return self.id
def update(self, **kwargs):
response = user_api_client.update_user_attribute(self.id, **kwargs)
self.__init__(response)
def update_password(self, password):
response = user_api_client.update_password(self.id, password)
self.__init__(response)
def set_permissions(self, service_id, permissions, folder_permissions):
user_api_client.set_user_permissions(
self.id,
service_id,
permissions=permissions,
folder_permissions=folder_permissions,
)
def logged_in_elsewhere(self):
# if the current user (ie: db object) has no session, they've never logged in before
return self.current_session_id is not None and session.get('current_session_id') != self.current_session_id
def activate(self):
if self.state == 'pending':
user_data = user_api_client.activate_user(self.id)
return self.__class__(user_data['data'])
else:
return self
def login(self):
login_user(self)
def sign_in(self):
session['user_details'] = {"email": self.email_address, "id": self.id}
if not self.is_active:
return False
if self.email_auth:
user_api_client.send_verify_code(self.id, 'email', None, request.args.get('next'))
if self.sms_auth:
user_api_client.send_verify_code(self.id, 'sms', self.mobile_number)
return True
@property
def sms_auth(self):
return self.auth_type == 'sms_auth'
@property
def email_auth(self):
return self.auth_type == 'email_auth'
def reset_failed_login_count(self):
user_api_client.reset_failed_login_count(self.id)
@property
def is_active(self):
return self.state == 'active'
@@ -118,14 +174,6 @@ class User(UserMixin):
super(User, self).is_authenticated
)
@property
def permissions(self):
return self._permissions
@permissions.setter
def permissions(self, permissions):
raise AttributeError("Read only property")
def has_permissions(self, *permissions, restrict_admin_usage=False):
unknown_permissions = set(permissions) - all_permissions
if unknown_permissions:
@@ -181,7 +229,8 @@ class User(UserMixin):
if not self.belongs_to_service(service_id):
abort(403)
def is_locked(self):
@property
def locked(self):
return self.failed_login_count >= self.max_failed_login_count
@property
@@ -225,38 +274,126 @@ class User(UserMixin):
dct['password'] = self._password
return dct
@classmethod
def register(
cls,
name,
email_address,
mobile_number,
password,
auth_type,
):
return cls(user_api_client.register_user(
name,
email_address,
mobile_number or None,
password,
auth_type,
))
def set_password(self, pwd):
self._password = pwd
def send_verify_email(self):
user_api_client.send_verify_email(self.id, self.email_address)
class InvitedUser(object):
def send_verify_code(self, to=None):
user_api_client.send_verify_code(self.id, 'sms', to or self.mobile_number)
def __init__(self,
id,
service,
from_user,
email_address,
permissions,
status,
created_at,
auth_type,
folder_permissions):
self.id = id
self.service = str(service)
self.from_user = from_user
self.email_address = email_address
def send_already_registered_email(self):
user_api_client.send_already_registered_email(self.id, self.email_address)
def refresh_session_id(self):
self.current_session_id = user_api_client.get_user(self.id).get('current_session_id')
session['current_session_id'] = self.current_session_id
class InvitedUser(JSONModel):
ALLOWED_PROPERTIES = {
'id',
'service',
'email_address',
'permissions',
'status',
'created_at',
'auth_type',
'folder_permissions',
}
def __init__(self, _dict):
super().__init__(_dict)
self.permissions = _dict.get('permissions') or []
self._from_user = _dict['from_user']
@classmethod
def create(
cls,
invite_from_id,
service_id,
email_address,
permissions,
auth_type,
folder_permissions,
):
return cls(invite_api_client.create_invite(
invite_from_id,
service_id,
email_address,
permissions,
auth_type,
folder_permissions,
))
def accept_invite(self):
invite_api_client.accept_invite(self.service, self.id)
def add_to_service(self):
user_api_client.add_user_to_service(
self.service,
self.id,
self.permissions,
self.folder_permissions,
)
@property
def permissions(self):
return self._permissions
@permissions.setter
def permissions(self, permissions):
if isinstance(permissions, list):
self.permissions = permissions
self._permissions = permissions
else:
if permissions:
self.permissions = permissions.split(',')
self._permissions = permissions.split(',')
self._permissions = translate_permissions_from_db_to_admin_roles(self.permissions)
@property
def from_user(self):
return User.from_id(self._from_user)
@property
def sms_auth(self):
return self.auth_type == 'sms_auth'
@property
def email_auth(self):
return self.auth_type == 'email_auth'
@classmethod
def from_token(cls, token):
try:
return cls(invite_api_client.check_token(token))
except HTTPError as exception:
if exception.status_code == 400 and 'invitation' in exception.message:
raise InviteTokenError(exception.message['invitation'])
else:
self.permissions = []
self.status = status
self.created_at = created_at
self.auth_type = auth_type
self.permissions = translate_permissions_from_db_to_admin_roles(self.permissions)
self.folder_permissions = folder_permissions
raise exception
@classmethod
def from_session(cls):
invited_user = session.get('invited_user')
return cls(invited_user) if invited_user else None
def has_permissions(self, *permissions):
if self.status == 'cancelled':
@@ -271,12 +408,12 @@ class InvitedUser(object):
def __eq__(self, other):
return ((self.id,
self.service,
self.from_user,
self._from_user,
self.email_address,
self.auth_type,
self.status) == (other.id,
other.service,
other.from_user,
other._from_user,
other.email_address,
other.auth_type,
other.status))
@@ -284,7 +421,7 @@ class InvitedUser(object):
def serialize(self, permissions_as_string=False):
data = {'id': self.id,
'service': self.service,
'from_user': self.from_user,
'from_user': self._from_user,
'email_address': self.email_address,
'status': self.status,
'created_at': str(self.created_at),
@@ -302,43 +439,118 @@ class InvitedUser(object):
return [{'id': x} for x in self.folder_permissions]
class InvitedOrgUser(object):
class InvitedOrgUser(JSONModel):
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
ALLOWED_PROPERTIES = {
'id',
'organisation',
'email_address',
'status',
'created_at',
}
def __init__(self, _dict):
super().__init__(_dict)
self._invited_by = _dict['invited_by']
def __eq__(self, other):
return ((self.id,
self.organisation,
self.invited_by,
self._invited_by,
self.email_address,
self.status) == (other.id,
other.organisation,
other.invited_by,
other._invited_by,
other.email_address,
other.status))
@classmethod
def create(cls, invite_from_id, org_id, email_address):
return cls(org_invite_api_client.create_invite(
invite_from_id, org_id, email_address
))
@classmethod
def from_session(cls):
invited_org_user = session.get('invited_org_user')
return cls(invited_org_user) if invited_org_user else None
def serialize(self, permissions_as_string=False):
data = {'id': self.id,
'organisation': self.organisation,
'invited_by': self.invited_by,
'invited_by': self._invited_by,
'email_address': self.email_address,
'status': self.status,
'created_at': str(self.created_at)
}
return data
@property
def invited_by(self):
return User.from_id(self._invited_by)
@classmethod
def from_token(cls, token):
try:
return cls(org_invite_api_client.check_token(token))
except HTTPError as exception:
if exception.status_code == 400 and 'invitation' in exception.message:
raise InviteTokenError(exception.message['invitation'])
else:
raise exception
def accept_invite(self):
org_invite_api_client.accept_invite(self.organisation, self.id)
def add_to_organisation(self):
user_api_client.add_user_to_organisation(self.organisation, self.id)
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):
return False
@property
def default_organisation(self):
return Organisation(None)
class Users(Sequence):
client = user_api_client.get_users_for_service
model = User
def __init__(self, service_id):
self.users = self.client(service_id)
def __getitem__(self, index):
return self.model(self.users[index])
def __len__(self):
return len(self.users)
def __add__(self, other):
return list(self) + list(other)
class OrganisationUsers(Users):
client = user_api_client.get_users_for_organisation
class InvitedUsers(Users):
client = invite_api_client.get_invites_for_service
model = InvitedUser
def __init__(self, service_id):
self.users = [
user for user in self.client(service_id)
if user['status'] != 'accepted'
]
class OrganisationInvitedUsers(InvitedUsers):
client = org_invite_api_client.get_invites_for_organisation
model = InvitedOrgUser

View File

@@ -60,3 +60,7 @@ class NotifyAdminAPIClient(BaseAPIClient):
def delete(self, *args, **kwargs):
self.check_inactive_service()
return super().delete(*args, **kwargs)
class InviteTokenError(Exception):
pass

View File

@@ -1,5 +1,4 @@
from app.models.user import (
InvitedUser,
from app.models.roles_and_permissions import (
roles,
translate_permissions_from_admin_roles_to_db,
)
@@ -31,16 +30,9 @@ class InviteApiClient(NotifyAdminAPIClient):
}
data = _attach_current_user(data)
resp = self.post(url='/service/{}/invite'.format(service_id), data=data)
return InvitedUser(**resp['data'])
return resp['data']
def get_invites_for_service(self, service_id):
return [
InvitedUser(**invite)
for invite in self._get_invites_for_service(service_id)
if invite['status'] != 'accepted'
]
def _get_invites_for_service(self, service_id):
return self.get(
'/service/{}/invite'.format(service_id)
)['data']
@@ -54,8 +46,7 @@ class InviteApiClient(NotifyAdminAPIClient):
])
def check_token(self, token):
resp = self.get(url='/invite/service/{}'.format(token))
return InvitedUser(**resp['data'])
return self.get(url='/invite/service/{}'.format(token))['data']
def cancel_invited_user(self, service_id, invited_user_id):
data = {'status': 'cancelled'}

View File

@@ -1,4 +1,3 @@
from app.models.user import InvitedOrgUser
from app.notify_client import NotifyAdminAPIClient, _attach_current_user
@@ -17,18 +16,16 @@ class OrgInviteApiClient(NotifyAdminAPIClient):
}
data = _attach_current_user(data)
resp = self.post(url='/organisation/{}/invite'.format(org_id), data=data)
return InvitedOrgUser(**resp['data'])
return 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
return resp['data']
def check_token(self, token):
resp = self.get(url='/invite/organisation/{}'.format(token))
return InvitedOrgUser(**resp['data'])
return resp['data']
def cancel_invited_user(self, org_id, invited_user_id):
data = {'status': 'cancelled'}
@@ -41,12 +38,5 @@ class OrgInviteApiClient(NotifyAdminAPIClient):
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
org_invite_api_client = OrgInviteApiClient()

View File

@@ -2,9 +2,7 @@ from itertools import chain
from notifications_python_client.errors import HTTPError
from app.models.user import (
User,
roles,
from app.models.roles_and_permissions import (
translate_permissions_from_admin_roles_to_db,
)
from app.notify_client import NotifyAdminAPIClient, cache
@@ -22,8 +20,6 @@ class UserApiClient(NotifyAdminAPIClient):
def init_app(self, app):
super().init_app(app)
self.max_failed_login_count = app.config["MAX_FAILED_LOGIN_COUNT"]
self.admin_url = app.config['ADMIN_BASE_URL']
def register_user(self, name, email_address, mobile_number, password, auth_type):
@@ -35,10 +31,10 @@ class UserApiClient(NotifyAdminAPIClient):
"auth_type": auth_type
}
user_data = self.post("/user", data)
return User(user_data['data'], max_failed_login_count=self.max_failed_login_count)
return user_data['data']
def get_user(self, user_id):
return User(self._get_user(user_id)['data'], max_failed_login_count=self.max_failed_login_count)
return self._get_user(user_id)['data']
@cache.set('user-{user_id}')
def _get_user(self, user_id):
@@ -46,7 +42,7 @@ class UserApiClient(NotifyAdminAPIClient):
def get_user_by_email(self, email_address):
user_data = self.get('/user/email', params={'email': email_address})
return User(user_data['data'], max_failed_login_count=self.max_failed_login_count)
return user_data['data']
def get_user_by_email_or_none(self, email_address):
try:
@@ -54,13 +50,7 @@ class UserApiClient(NotifyAdminAPIClient):
except HTTPError as e:
if e.status_code == 404:
return None
def get_users(self):
users_data = self.get("/user")['data']
users = []
for user in users_data:
users.append(User(user, max_failed_login_count=self.max_failed_login_count))
return users
raise e
@cache.delete('user-{user_id}')
def update_user_attribute(self, user_id, **kwargs):
@@ -73,20 +63,20 @@ class UserApiClient(NotifyAdminAPIClient):
url = "/user/{}".format(user_id)
user_data = self.post(url, data=data)
return User(user_data['data'], max_failed_login_count=self.max_failed_login_count)
return user_data['data']
@cache.delete('user-{user_id}')
def reset_failed_login_count(self, user_id):
url = "/user/{}/reset-failed-login-count".format(user_id)
user_data = self.post(url, data={})
return User(user_data['data'], max_failed_login_count=self.max_failed_login_count)
return user_data['data']
@cache.delete('user-{user_id}')
def update_password(self, user_id, password):
data = {"_password": password}
url = "/user/{}/update-password".format(user_id)
user_data = self.post(url, data=data)
return User(user_data['data'], max_failed_login_count=self.max_failed_login_count)
return user_data['data']
@cache.delete('user-{user_id}')
def verify_password(self, user_id, password):
@@ -132,21 +122,11 @@ class UserApiClient(NotifyAdminAPIClient):
def get_users_for_service(self, service_id):
endpoint = '/service/{}/users'.format(service_id)
resp = self.get(endpoint)
return [User(data) for data in resp['data']]
def get_count_of_users_with_permission(self, service_id, permission):
if permission not in roles.keys():
raise TypeError('{} is not a valid permission'.format(permission))
return len([
user for user in self.get_users_for_service(service_id)
if user.has_permission_for_service(service_id, permission)
])
return self.get(endpoint)['data']
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']]
return self.get(endpoint)['data']
@cache.delete('service-{service_id}')
@cache.delete('service-{service_id}-template-folders')
@@ -164,7 +144,7 @@ class UserApiClient(NotifyAdminAPIClient):
@cache.delete('user-{user_id}')
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)
return resp['data']
@cache.delete('service-{service_id}-template-folders')
@cache.delete('user-{user_id}')
@@ -191,20 +171,8 @@ class UserApiClient(NotifyAdminAPIClient):
users = self.post(endpoint, data=data)
return users
def is_email_already_in_use(self, email_address):
if self.get_user_by_email_or_none(email_address):
return True
return False
def activate_user(self, user):
if user.state == 'pending':
user_data = self._activate_user(user.id)
return User(user_data['data'], max_failed_login_count=self.max_failed_login_count)
else:
return user
@cache.delete('user-{user_id}')
def _activate_user(self, user_id):
def activate_user(self, user_id):
return self.post("/user/{}/activate".format(user_id), data=None)
def send_change_email_verification(self, user_id, new_email):