def test_unread_message_count(self): session = Session.from_party_id("party") session.save() self.assertFalse(session.message_count_expired()) session.set_unread_message_total(5) key = session.session_key session_to_assert = Session.from_session_key(key) self.assertEqual(session_to_assert.get_unread_message_count(), 5)
def test_from_session_key(self): session = Session.from_party_id("party") session.save() session_key = session.session_key session_from_redis = Session.from_session_key(session_key) self.assertTrue(session_from_redis is not None) decoded_jwt = session_from_redis.get_decoded_jwt() self.assertEqual(decoded_jwt["party_id"], "party") self.assertEqual(session_from_redis.get_party_id(), "party")
def extract_session_wrapper(*args, **kwargs): session_key = request.cookies.get("authorization") redis_session = Session.from_session_key(session_key) encoded_jwt = redis_session.get_encoded_jwt() if encoded_jwt: logger.debug("Attempting to authorize token") try: jwt = decode(encoded_jwt, jwt_secret, algorithms="HS256") logger.debug("Token decoded successfully") except JWTError: logger.warning("Unable to decode token") raise JWTValidationError else: logger.warning("No authorization token provided") flash("To help protect your information we have signed you out.", "info") session["next"] = request.url return redirect(url_for("sign_in_bp.login")) if app.config["VALIDATE_JWT"]: if validate(jwt): redis_session.refresh_session() return original_function(redis_session, *args, **kwargs) else: logger.warning("Token is not valid for this request") raise JWTValidationError
def test_message_count_expired(self, test_patch): expired_time = datetime.now() - timedelta(seconds=301) test_patch.return_value = expired_time.timestamp() session = Session.from_party_id("party") session.save() self.assertTrue(session.message_count_expired())
def extract_session_wrapper(*args, **kwargs): session_key = request.cookies.get('authorization') session = Session.from_session_key(session_key) encoded_jwt = session.get_encoded_jwt() if encoded_jwt: logger.debug('Attempting to authorize token') try: jwt = decode(encoded_jwt, jwt_secret, algorithms='HS256') logger.debug('Token decoded successfully') except JWTError: logger.warning('Unable to decode token') raise JWTValidationError else: logger.warning('No authorization token provided') flash( 'To help protect your information we have signed you out.', 'info') return redirect(url_for('sign_in_bp.login', next=request.url)) if app.config['VALIDATE_JWT']: if validate(jwt): session.refresh_session() return original_function(session, *args, **kwargs) else: logger.warning('Token is not valid for this request') raise JWTValidationError
def test_get_message_count_from_session(self): session = Session.from_party_id("id") session.set_unread_message_total(3) with app.app_context(): count = conversation_controller.try_message_count_from_session( session) self.assertEqual(3, count)
def test_get_message_count_unauthorized(self, headers): headers.return_value = {"Authorization": "token"} session = Session.from_party_id("id") with responses.RequestsMock() as rsps: rsps.add(rsps.GET, url_get_conversation_count, status=403) with app.app_context(): with self.assertRaises(IncorrectAccountAccessError): conversation_controller.get_message_count_from_api(session)
def _create_get_conversation_headers(encoded_jwt=None): try: if encoded_jwt is None: encoded_jwt = Session.from_session_key( request.cookies['authorization']).get_encoded_jwt() except KeyError: logger.error('Authorization token missing in cookie') raise AuthorizationTokenMissing return {'Authorization': encoded_jwt}
def test_get_message_count_other_error_returns_0(self, headers): headers.return_value = {"Authorization": "token"} session = Session.from_party_id("id") with responses.RequestsMock() as rsps: rsps.add(rsps.GET, url_get_conversation_count, status=400) with app.app_context(): count = conversation_controller.get_message_count_from_api( session) self.assertEqual(0, count)
def test_delete_session(self): # Create session and get session key session = Session.from_party_id("party") session.save() session_key = session.session_key session.delete_session() session = redis.get(session_key) self.assertEqual(session, None)
def confirm_account_email_change(token): logger.info("Attempting to confirm account email change", token=token) try: party_controller.verify_email(token) except ApiError as exc: # Handle api errors if exc.status_code == 409: logger.info( "Expired account email change verification token", token=token, api_url=exc.url, api_status_code=exc.status_code, ) session_key = request.cookies.get("authorization") session = Session.from_session_key(session_key) session.delete_session() return render_template( "account/account-email-change-confirm-link-expired.html", token=token) elif exc.status_code == 404: logger.warning( "Unrecognised account email change verification token", token=token, api_url=exc.url, api_status_code=exc.status_code, ) abort(404) else: logger.info( "Failed to verify account email change verification email", token=token, api_url=exc.url, api_status_code=exc.status_code, ) raise exc # Successful account activation therefore redirect back to the login screen session_key = request.cookies.get("authorization") session = Session.from_session_key(session_key) session.delete_session() logger.info("Successfully verified email change on your account", token=token) return render_template("account/account-email-change-confirm.html")
def test_create_session(self): # Create session and get session key session = Session.from_party_id("party") self.assertFalse(session.is_persisted()) session.save() self.assertTrue(session.is_persisted()) # Retrieve encoded_jwt from session test_jwt = session.get_decoded_jwt() self.assertEqual(test_jwt['party_id'], "party") self.assertEqual(test_jwt['unread_message_count']['value'], 0)
def test_get_message_count_from_session_if_persisted_by_api(self): session = Session.from_party_id("id") session.set_unread_message_total(1) session_key = session.session_key session_under_test = Session.from_session_key(session_key) with responses.RequestsMock() as rsps: rsps.add(rsps.GET, url_get_conversation_count, json=message_count, status=200, headers={'Authorisation': 'token'}, content_type='application/json') with app.app_context(): count = conversation_controller.get_message_count_from_api( session_under_test) self.assertEqual(3, session_under_test.get_unread_message_count())
def confirm_account_email_change(token): if not app.config['ACCOUNT_EMAIL_CHANGE_ENABLED']: abort(404) logger.info('Attempting to confirm account email change', token=token) try: party_controller.verify_email(token) except ApiError as exc: # Handle api errors if exc.status_code == 409: logger.info('Expired account email change verification token', token=token, api_url=exc.url, api_status_code=exc.status_code) session_key = request.cookies.get('authorization') session = Session.from_session_key(session_key) session.delete_session() return render_template( 'account/account-email-change-confirm-link-expired.html', token=token) elif exc.status_code == 404: logger.warning( 'Unrecognised account email change verification token', token=token, api_url=exc.url, api_status_code=exc.status_code) abort(404) else: logger.info( 'Failed to verify account email change verification email', token=token, api_url=exc.url, api_status_code=exc.status_code) raise exc # Successful account activation therefore redirect back to the login screen session_key = request.cookies.get('authorization') session = Session.from_session_key(session_key) session.delete_session() logger.info('Successfully verified email change on your account', token=token) return render_template('account/account-email-change-confirm.html')
def _create_send_message_headers() -> dict: try: encoded_jwt = Session.from_session_key( request.cookies["authorization"]).get_encoded_jwt() except KeyError: logger.error("Authorization token missing in cookie") raise AuthorizationTokenMissing return { "Authorization": encoded_jwt, "Content-Type": "application/json", "Accept": "application/json" }
def _create_send_message_headers(): try: encoded_jwt = Session.from_session_key( request.cookies['authorization']).get_encoded_jwt() except KeyError: logger.error('Authorization token missing in cookie') raise AuthorizationTokenMissing return { 'Authorization': encoded_jwt, 'Content-Type': 'application/json', 'Accept': 'application/json' }
def test_refresh_session(self): # Create session and get session key session = Session.from_party_id("party") session.save() session_key = session.session_key # Wait 3 seconds and update the session time.sleep(1) session.refresh_session() # Check that the session expiry time has been reset expires_in = redis.ttl(session_key) self.assertEqual(expires_in, 3600)
def handle_csrf_error(error): logger.warning("CSRF token has expired", error_message=error.description, status_code=error.code) session_key = request.cookies.get("authorization") session_handler = Session.from_session_key(session_key) encoded_jwt = session_handler.get_encoded_jwt() if not encoded_jwt: return render_template("errors/400-error.html"), 400 else: session["next"] = request.url return redirect(url_for("sign_in_bp.logout", csrf_error=True))
def logout(): # Delete user session in redis session_key = request.cookies.get('authorization') session = Session.from_session_key(session_key) session.delete_session() if request.args.get('csrf_error'): flash('To help protect your information we have signed you out.', 'info') # Delete session cookie response = make_response( redirect(url_for('sign_in_bp.login', next=request.args.get('next')))) response.set_cookie('authorization', value='', expires=0) return response
def handle_csrf_error(error): logger.warning('CSRF token has expired', error_message=error.description, status_code=error.code) session_key = request.cookies.get('authorization') session_handler = Session.from_session_key(session_key) encoded_jwt = session_handler.get_encoded_jwt() if not encoded_jwt: return render_template('errors/400-error.html'), 400 else: return redirect( url_for('sign_in_bp.logout', csrf_error=True, next=request.url))
def test_get_message_count_from_api(self, headers): headers.return_value = {'Authorization': "token"} session = Session.from_party_id("id") with responses.RequestsMock() as rsps: rsps.add(rsps.GET, url_get_conversation_count, json=message_count, status=200, headers={'Authorisation': 'token'}, content_type='application/json') with app.app_context(): count = conversation_controller.get_message_count_from_api( session) self.assertEqual(3, count)
def logout(): flashed_messages = get_flashed_messages(with_categories=True) # Delete user session in redis session_key = request.cookies.get("authorization") session = Session.from_session_key(session_key) session.delete_session() if len(flashed_messages) > 0: for category, message in flashed_messages: flash(message=message, category=category) if request.args.get("csrf_error"): flash("To help protect your information we have signed you out.", "info") # Delete session cookie response = make_response( redirect(url_for("sign_in_bp.login", next=request.args.get("next")))) response.delete_cookie("authorization") return response
def test_get_message_count_from_api_when_expired(self, headers): headers.return_value = {'Authorization': "token"} session = Session.from_party_id("id") decoded = session.get_decoded_jwt() decoded['unread_message_count']['refresh_in'] = ( datetime.fromtimestamp( decoded['unread_message_count']['refresh_in']) - timedelta(seconds=301)).timestamp() encoded = jwt.encode(decoded) session.encoded_jwt_token = encoded with responses.RequestsMock() as rsps: rsps.add(rsps.GET, url_get_conversation_count, json=message_count, status=200, headers={'Authorisation': 'token'}, content_type='application/json') with app.app_context(): count = conversation_controller.try_message_count_from_session( session) self.assertEqual(3, count)
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(): # 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 setUp(self): self.app = app.test_client() self.app.testing = True self.session = Session.from_party_id("test")