def test_get_respondent_by_email_fail(self): with responses.RequestsMock() as rsps: rsps.add(rsps.GET, url_get_respondent_email, status=500) with app.app_context(): with self.assertRaises(ApiError): party_controller.get_respondent_by_email( respondent_party['emailAddress'])
def test_get_respondent_by_email_not_found(self): with responses.RequestsMock() as rsps: rsps.add(rsps.GET, url_get_respondent_email, status=404) with app.app_context(): respondent = party_controller.get_respondent_by_email( respondent_party['emailAddress']) self.assertTrue(respondent is None)
def _is_existing_account(respondent_email): """ Checks if the respondent already exists against the email address provided :param respondent_email: email of the respondent :type respondent_email: str :return: returns true if account already registered :rtype: bool """ respondent = party_controller.get_respondent_by_email(respondent_email) if not respondent: return False return True
def test_get_respondent_by_email_success(self): with responses.RequestsMock() as rsps: rsps.add(rsps.GET, url_get_respondent_email, json=respondent_party, status=200) with app.app_context(): respondent = party_controller.get_respondent_by_email( respondent_party['emailAddress']) self.assertEqual(respondent['emailAddress'], respondent_party['emailAddress']) self.assertEqual(respondent['firstName'], respondent_party['firstName'])
def get_reset_password(token, form_errors=None): form = ResetPasswordForm(request.form) try: duration = app.config["EMAIL_TOKEN_EXPIRY"] email = verification.decode_email_token(token, duration) respondent = party_controller.get_respondent_by_email(email) db_token = respondent["password_verification_token"] if not token == db_token: logger.warning("Token not found for respondent", token=token, respondent_id=respondent["id"]) return render_template("passwords/password-token-not-found.html", token=token) except KeyError: logger.warning("Token not found for respondent", token=token, exc_info=True) return render_template("passwords/password-token-not-found.html", token=token) except SignatureExpired: try: party_controller.delete_verification_token(token) except NotFound: return render_template("passwords/password-token-not-found.html", token=token) logger.warning("Token expired for frontstage reset", token=token, exc_info=True) return render_template("passwords/password-expired.html", token=token) except (BadSignature, BadData): logger.warning("Invalid token sent to frontstage password reset", token=token, exc_info=True) return render_template("passwords/password-expired.html", token=token) template_data = {"error": {"type": form_errors}, "token": token} return render_template("passwords/reset-password.html", form=form, data=template_data)
def request_password_change(email): respondent = party_controller.get_respondent_by_email(email) if not respondent: logger.info("Respondent does not exist") return redirect(url_for('passwords_bp.reset_password_check_email')) party_id = str(respondent['id']) logger.info("Requesting password change", party_id=party_id) token = verification.generate_email_token(email) url_root = request.url_root # url_for comes with a leading slash, so strip off the trailing slash in url_root if there is one if url_root.endswith('/'): url_root = url_root[:-1] verification_url = url_root + url_for('passwords_bp.post_reset_password', token=token) personalisation = { 'RESET_PASSWORD_URL': verification_url, 'FIRST_NAME': respondent['firstName'] } logger.info('Reset password url', url=verification_url, party_id=party_id) try: NotifyGateway(app.config).request_to_notify(email=email, personalisation=personalisation, reference=party_id) logger.info('Password reset email successfully sent', party_id=party_id) except RasNotifyError: # Note: intentionally suppresses exception logger.error('Error sending request to Notify Gateway', respondent_id=party_id, exc_info=True) return redirect(url_for('passwords_bp.reset_password_check_email'))
def login(): # noqa: C901 form = LoginForm(request.form) form.username.data = form.username.data.strip() account_activated = request.args.get('account_activated', None) secure = app.config['WTF_CSRF_ENABLED'] if request.method == 'POST' and form.validate(): username = form.username.data password = request.form.get('password') bound_logger = logger.bind(email=obfuscate_email(username)) bound_logger.info("Attempting to find user in auth service") try: auth_controller.sign_in(username, password) except AuthError as exc: error_message = exc.auth_error party_json = party_controller.get_respondent_by_email(username) party_id = party_json.get('id') if party_json else None bound_logger = bound_logger.bind(party_id=party_id) if USER_ACCOUNT_LOCKED in error_message: # pylint: disable=no-else-return if not party_id: bound_logger.error("Respondent account locked in auth but doesn't exist in party") return render_template('sign-in/sign-in.html', form=form, data={"error": {"type": "failed"}}) bound_logger.info('User account is locked on the Auth server', status=party_json['status']) if party_json['status'] == 'ACTIVE' or party_json['status'] == 'CREATED': notify_party_and_respondent_account_locked(respondent_id=party_id, email_address=username, status='SUSPENDED') return render_template('sign-in/sign-in.account-locked.html', form=form) elif NOT_VERIFIED_ERROR in error_message: bound_logger.info('User account is not verified on the Auth server') return render_template('sign-in/sign-in.account-not-verified.html', party_id=party_id) elif BAD_AUTH_ERROR in error_message: bound_logger.info('Bad credentials provided') elif UNKNOWN_ACCOUNT_ERROR in error_message: bound_logger.info('User account does not exist in auth service') else: bound_logger.error('Unexpected error was returned from Auth service', auth_error=error_message) return render_template('sign-in/sign-in.html', form=form, data={"error": {"type": "failed"}}, next=request.args.get('next')) bound_logger.info("Successfully found user in auth service. Attempting to find user in party service") party_json = party_controller.get_respondent_by_email(username) if not party_json or 'id' not in party_json: bound_logger.error("Respondent has an account in auth but not in party") return render_template('sign-in/sign-in.html', form=form, data={"error": {"type": "failed"}}) party_id = party_json['id'] bound_logger = bound_logger.bind(party_id=party_id) if request.args.get('next'): response = make_response(redirect(request.args.get('next'))) else: response = make_response(redirect(url_for('surveys_bp.get_survey_list', tag='todo', _external=True, _scheme=getenv('SCHEME', 'http')))) bound_logger.info("Successfully found user in party service") bound_logger.info('Creating session') session = Session.from_party_id(party_id) response.set_cookie('authorization', value=session.session_key, expires=session.get_expires_in(), secure=secure, httponly=secure) count = conversation_controller.get_message_count_from_api(session) session.set_unread_message_total(count) bound_logger.info('Successfully created session', session_key=session.session_key) return response template_data = { "error": { "type": form.errors, "logged_in": "False" }, 'account_activated': account_activated } if request.args.get('next'): return render_template('sign-in/sign-in.html', form=form, data=template_data, next=request.args.get('next')) return render_template('sign-in/sign-in.html', form=form, data=template_data)
def login(): form = LoginForm(request.form) form.username.data = form.username.data.strip() account_activated = request.args.get('account_activated', None) if request.method == 'POST' and form.validate(): username = form.username.data password = request.form.get('password') party_json = party_controller.get_respondent_by_email(username) if not party_json or 'id' not in party_json: logger.info( 'Respondent not able to sign in as they don\'t have an active account in the system.' ) return render_template('sign-in/sign-in.html', form=form, data={"error": { "type": "failed" }}) party_id = party_json['id'] try: oauth2_token = oauth_controller.sign_in(username, password) except OAuth2Error as exc: error_message = exc.oauth2_error if USER_ACCOUNT_LOCKED in error_message: logger.info('User account is locked on the OAuth2 server', party_id=party_id) if party_json['status'] == 'ACTIVE' or party_json[ 'status'] == 'CREATED': notify_party_and_respondent_account_locked( respondent_id=party_id, email_address=username, status='SUSPENDED') return render_template('sign-in/sign-in.account-locked.html', form=form) elif BAD_AUTH_ERROR in error_message: return render_template('sign-in/sign-in.html', form=form, data={"error": { "type": "failed" }}) elif NOT_VERIFIED_ERROR in error_message: logger.info( 'User account is not verified on the OAuth2 server') return render_template( 'sign-in/sign-in.account-not-verified.html', party_id=party_id, email=username) else: logger.info( 'OAuth 2 server generated 401 which is not understood', oauth2_error=error_message) return render_template('sign-in/sign-in.html', form=form, data={"error": { "type": "failed" }}) # Take our raw token and add a UTC timestamp to the expires_at attribute data_dict = {**oauth2_token, 'party_id': party_id} data_dict_for_jwt_token = timestamp_token(data_dict) encoded_jwt_token = encode(data_dict_for_jwt_token) response = make_response( redirect( url_for('surveys_bp.get_survey_list', tag='todo', _external=True, _scheme=getenv('SCHEME', 'http')))) session = SessionHandler() logger.info('Creating session', party_id=party_id) session.create_session(encoded_jwt_token) response.set_cookie('authorization', value=session.session_key, expires=data_dict_for_jwt_token['expires_at']) logger.info('Successfully created session', party_id=party_id, session_key=session.session_key) return response template_data = { "error": { "type": form.errors, "logged_in": "False" }, 'account_activated': account_activated } return render_template('sign-in/sign-in.html', form=form, data=template_data)
def login(): # noqa: C901 form = LoginForm(request.form) if form.username.data is not None: form.username.data = form.username.data.strip() if request.method == "POST" and form.validate(): username = form.username.data password = request.form.get("password") bound_logger = logger.bind(email=obfuscate_email(username)) bound_logger.info("Attempting to find user in auth service") try: auth_controller.sign_in(username, password) except AuthError as exc: error_message = exc.auth_error party_json = party_controller.get_respondent_by_email(username) party_id = party_json.get("id") if party_json else None bound_logger = bound_logger.bind(party_id=party_id) if USER_ACCOUNT_LOCKED in error_message: if not party_id: bound_logger.error( "Respondent account locked in auth but doesn't exist in party" ) return render_template("sign-in/sign-in.html", form=form, data={"error": { "type": "failed" }}) bound_logger.info("User account is locked on the Auth server", status=party_json["status"]) if party_json["status"] == "ACTIVE" or party_json[ "status"] == "CREATED": notify_party_and_respondent_account_locked( respondent_id=party_id, email_address=username, status="SUSPENDED") return render_template("sign-in/sign-in.account-locked.html", form=form) elif NOT_VERIFIED_ERROR in error_message: bound_logger.info( "User account is not verified on the Auth server") return render_template( "sign-in/sign-in.account-not-verified.html", party_id=party_id) elif BAD_AUTH_ERROR in error_message: bound_logger.info("Bad credentials provided") elif UNKNOWN_ACCOUNT_ERROR in error_message: bound_logger.info( "User account does not exist in auth service") elif USER_ACCOUNT_DELETED in error_message: bound_logger.info("User account is marked for deletion") else: bound_logger.error( "Unexpected error was returned from Auth service", auth_error=error_message) logger.unbind("email") return render_template("sign-in/sign-in.html", form=form, data={"error": { "type": "failed" }}) bound_logger.info( "Successfully found user in auth service. Attempting to find user in party service" ) party_json = party_controller.get_respondent_by_email(username) if not party_json or "id" not in party_json: bound_logger.error( "Respondent has an account in auth but not in party") return render_template("sign-in/sign-in.html", form=form, data={"error": { "type": "failed" }}) party_id = party_json["id"] bound_logger = bound_logger.bind(party_id=party_id) if session.get("next"): response = make_response(redirect(session.get("next"))) session.pop("next") else: response = make_response( redirect( url_for("surveys_bp.get_survey_list", tag="todo", _external=True, _scheme=getenv("SCHEME", "http")))) bound_logger.info("Successfully found user in party service") bound_logger.info("Creating session") redis_session = Session.from_party_id(party_id) secure = app.config["WTF_CSRF_ENABLED"] response.set_cookie( "authorization", value=redis_session.session_key, expires=redis_session.get_expires_in(), secure=secure, httponly=secure, samesite="strict", ) count = conversation_controller.get_message_count_from_api( redis_session) redis_session.set_unread_message_total(count) bound_logger.info("Successfully created session", session_key=redis_session.session_key) bound_logger.unbind("email") return response account_activated = request.args.get("account_activated", None) template_data = { "error": { "type": form.errors, "logged_in": "False" }, "account_activated": account_activated } return render_template("sign-in/sign-in.html", form=form, data=template_data)
def request_password_change(email): respondent = party_controller.get_respondent_by_email(email) if not respondent: logger.info("Respondent does not exist") return redirect(url_for("passwords_bp.reset_password_trouble")) party_id = str(respondent["id"]) password_reset_counter = party_controller.get_password_reset_counter( party_id)["counter"] if password_reset_counter != 0: try: email = verification.decode_email_token( respondent["password_verification_token"], app.config["PASSWORD_RESET_ATTEMPTS_TIMEOUT"]) except SignatureExpired: try: party_controller.reset_password_reset_counter(party_id) password_reset_counter = 0 except ApiError: logger.error("Error resetting password reset counter") return redirect(url_for("passwords_bp.reset_password_trouble")) if password_reset_counter >= 5: logger.error("Password reset attempts exceeded") return redirect( url_for("passwords_bp.exceeded_number_of_reset_attempts")) logger.info("Requesting password change", party_id=party_id) token = verification.generate_email_token(email) url_root = request.url_root # url_for comes with a leading slash, so strip off the trailing slash in url_root if there is one if url_root.endswith("/"): url_root = url_root[:-1] verification_url = url_root + url_for("passwords_bp.post_reset_password", token=token) personalisation = { "RESET_PASSWORD_URL": verification_url, "FIRST_NAME": respondent["firstName"] } logger.info("Reset password url", url=verification_url, party_id=party_id) party_controller.post_verification_token(email, token) try: NotifyGateway(app.config).request_to_notify( email=email, personalisation=personalisation, reference=party_id) logger.info("Password reset email successfully sent", party_id=party_id) except RasNotifyError: # Note: intentionally suppresses exception logger.error("Error sending request to Notify Gateway", respondent_id=party_id, exc_info=True) # Get real time counter to check how many attempts are left password_reset_counter = party_controller.get_password_reset_counter( party_id)["counter"] if password_reset_counter == 4: flash(message="You have 1 try left to reset your password", category="warn") return redirect( url_for("passwords_bp.reset_password_check_email", token=token))