def test_get_answer(client, logged_in_headers, admin_logged_in_headers): """Test the answers/<id> route.""" answer = Answer(answer=Answer.hash_answer('strawberry'), user_id=1, question_id=1) db.session.add(answer) db.session.commit() rv = client.get('/api/v1/answers/1', headers=logged_in_headers) assert json.loads(rv.data.decode('utf-8')) == { 'id': 1, 'question': { 'enabled': True, 'id': 1, 'question': 'What is your favorite flavor of ice cream?', 'url': 'http://localhost/api/v1/questions/1' }, 'user_id': 1 } rv = client.get('/api/v1/answers/1', headers=admin_logged_in_headers) assert json.loads(rv.data.decode('utf-8')) == { 'message': 'Administrators are not authorized to proceed with this action', 'status': 403 }
def test_get_answers(client, logged_in_headers, admin_logged_in_headers): """Test the answers route.""" answer = Answer(answer=Answer.hash_answer('strawberry'), user_id=1, question_id=1) answer2 = Answer(answer=Answer.hash_answer('green'), user_id=1, question_id=2) answer3 = Answer(answer=Answer.hash_answer('Buzz Lightyear'), user_id=1, question_id=3) answer4 = Answer(answer=Answer.hash_answer('Hamm'), user_id=2, question_id=3) db.session.add(answer) db.session.add(answer2) db.session.add(answer3) db.session.add(answer4) db.session.commit() rv = client.get('/api/v1/answers', headers=logged_in_headers) items = [ { 'id': 1, 'question': { 'enabled': True, 'id': 1, 'question': 'What is your favorite flavor of ice cream?', 'url': 'http://localhost/api/v1/questions/1', }, 'url': 'http://localhost/api/v1/answers/1', 'user_id': 1 }, { 'id': 2, 'question': { 'enabled': True, 'id': 2, 'question': 'What is your favorite color?', 'url': 'http://localhost/api/v1/questions/2', }, 'url': 'http://localhost/api/v1/answers/2', 'user_id': 1 }, { 'id': 3, 'question': { 'enabled': True, 'id': 3, 'question': 'What is your favorite toy?', 'url': 'http://localhost/api/v1/questions/3', }, 'url': 'http://localhost/api/v1/answers/3', 'user_id': 1 } ] assert json.loads(rv.data.decode('utf-8'))['items'] == items rv = client.get('/api/v1/answers', headers=admin_logged_in_headers) assert json.loads(rv.data.decode('utf-8')) == { 'message': 'Administrators are not authorized to proceed with this action', 'status': 403 }
def _configure_user(): """Configure testuser2 in the database.""" user = User(ad_guid='10385a23-6def-4990-84a8-32444e36e496') db.session.add(user) answer = Answer(answer=Answer.hash_answer('strawberry'), user_id=1, question_id=1) answer2 = Answer(answer=Answer.hash_answer('green'), user_id=1, question_id=2) answer3 = Answer(answer=Answer.hash_answer('buzz lightyear'), user_id=1, question_id=3) db.session.add(answer) db.session.add(answer2) db.session.add(answer3) db.session.commit()
def test_get_answer_different_user(client, logged_in_headers): """Test accessing the answer of a different user in the answers/<id> route.""" user = User(ad_guid='5609c5ec-c0df-4480-a94b-b6eb0fc4c066') db.session.add(user) db.session.commit() answer = Answer(answer=Answer.hash_answer('strawberry'), user_id=user.id, question_id=1) db.session.add(answer) db.session.commit() rv = client.get('/api/v1/answers/1', headers=logged_in_headers) assert json.loads(rv.data.decode('utf-8')) == { 'message': 'This answer is not associated with your account', 'status': 401, }
def test_add_answers_case_sensitive(app, client, logged_in_headers): """Test the answers POST route when case sensitive answers are enabled.""" data = json.dumps( [ {'question_id': 2, 'answer': 'Bright Green'}, {'question_id': 3, 'answer': 'Buzz Lightyear'}, {'question_id': 1, 'answer': 'strawberry'}, ] ) with mock.patch.dict(app.config, {'CASE_SENSITIVE_ANSWERS': True}): client.post('/api/v1/answers', headers=logged_in_headers, data=data) assert Answer.verify_answer('bright green', Answer.query.get(1).answer) is False assert Answer.verify_answer('Bright Green', Answer.query.get(1).answer) is True
def test_get_answers(client, logged_in_headers, admin_logged_in_headers): """Test the answers route.""" answer = Answer(answer=Answer.hash_answer('strawberry'), user_id=1, question_id=1) answer2 = Answer(answer=Answer.hash_answer('green'), user_id=1, question_id=2) answer3 = Answer(answer=Answer.hash_answer('Buzz Lightyear'), user_id=1, question_id=3) answer4 = Answer(answer=Answer.hash_answer('Hamm'), user_id=2, question_id=3) db.session.add(answer) db.session.add(answer2) db.session.add(answer3) db.session.add(answer4) db.session.commit() rv = client.get('/api/v1/answers', headers=logged_in_headers) items = [{ 'id': 1, 'question': { 'enabled': True, 'id': 1, 'question': 'What is your favorite flavor of ice cream?', 'url': 'http://localhost/api/v1/questions/1', }, 'url': 'http://localhost/api/v1/answers/1', 'user_id': 1 }, { 'id': 2, 'question': { 'enabled': True, 'id': 2, 'question': 'What is your favorite color?', 'url': 'http://localhost/api/v1/questions/2', }, 'url': 'http://localhost/api/v1/answers/2', 'user_id': 1 }, { 'id': 3, 'question': { 'enabled': True, 'id': 3, 'question': 'What is your favorite toy?', 'url': 'http://localhost/api/v1/questions/3', }, 'url': 'http://localhost/api/v1/answers/3', 'user_id': 1 }] assert json.loads(rv.data.decode('utf-8'))['items'] == items rv = client.get('/api/v1/answers', headers=admin_logged_in_headers) assert json.loads(rv.data.decode('utf-8')) == { 'message': 'Administrators are not authorized to proceed with this action', 'status': 403 }
def test_get_answers_unauthenticated(client, logged_in_headers, mock_ad): """Test the unauthenticated answers route.""" answer = Answer(answer=Answer.hash_answer('strawberry'), user_id=1, question_id=1) answer2 = Answer(answer=Answer.hash_answer('green'), user_id=1, question_id=2) answer3 = Answer(answer=Answer.hash_answer('Buzz Lightyear'), user_id=1, question_id=3) answer4 = Answer(answer=Answer.hash_answer('Hamm'), user_id=2, question_id=3) db.session.add(answer) db.session.add(answer2) db.session.add(answer3) db.session.add(answer4) db.session.commit() rv = client.get('/api/v1/answers/testuser2', headers={'Content-Type': 'application/json'}) items = [ { 'id': 1, 'question': { 'enabled': True, 'id': 1, 'question': 'What is your favorite flavor of ice cream?', 'url': 'http://localhost/api/v1/questions/1', }, 'url': 'http://localhost/api/v1/answers/1', 'user_id': 1 }, { 'id': 2, 'question': { 'enabled': True, 'id': 2, 'question': 'What is your favorite color?', 'url': 'http://localhost/api/v1/questions/2', }, 'url': 'http://localhost/api/v1/answers/2', 'user_id': 1 }, { 'id': 3, 'question': { 'enabled': True, 'id': 3, 'question': 'What is your favorite toy?', 'url': 'http://localhost/api/v1/questions/3', }, 'url': 'http://localhost/api/v1/answers/3', 'user_id': 1 } ] assert json.loads(rv.data.decode('utf-8'))['items'] == items
def test_get_answers_unauthenticated(client, logged_in_headers, mock_ad): """Test the unauthenticated answers route.""" answer = Answer(answer=Answer.hash_answer('strawberry'), user_id=1, question_id=1) answer2 = Answer(answer=Answer.hash_answer('green'), user_id=1, question_id=2) answer3 = Answer(answer=Answer.hash_answer('Buzz Lightyear'), user_id=1, question_id=3) answer4 = Answer(answer=Answer.hash_answer('Hamm'), user_id=2, question_id=3) db.session.add(answer) db.session.add(answer2) db.session.add(answer3) db.session.add(answer4) db.session.commit() rv = client.get('/api/v1/answers/testuser2', headers={'Content-Type': 'application/json'}) items = [{ 'id': 1, 'question': { 'enabled': True, 'id': 1, 'question': 'What is your favorite flavor of ice cream?', 'url': 'http://localhost/api/v1/questions/1', }, 'url': 'http://localhost/api/v1/answers/1', 'user_id': 1 }, { 'id': 2, 'question': { 'enabled': True, 'id': 2, 'question': 'What is your favorite color?', 'url': 'http://localhost/api/v1/questions/2', }, 'url': 'http://localhost/api/v1/answers/2', 'user_id': 1 }, { 'id': 3, 'question': { 'enabled': True, 'id': 3, 'question': 'What is your favorite toy?', 'url': 'http://localhost/api/v1/questions/3', }, 'url': 'http://localhost/api/v1/answers/3', 'user_id': 1 }] assert json.loads(rv.data.decode('utf-8'))['items'] == items
def test_delete_answers(client, logged_in_headers, admin_logged_in_headers): """Test the answers route using the DELETE method to reset the user's configured answers.""" answer = Answer(answer=Answer.hash_answer('strawberry'), user_id=1, question_id=1) answer2 = Answer(answer=Answer.hash_answer('green'), user_id=1, question_id=2) answer3 = Answer(answer=Answer.hash_answer('Buzz Lightyear'), user_id=1, question_id=3) db.session.add(answer) db.session.add(answer2) db.session.add(answer3) db.session.commit() rv = client.delete('/api/v1/answers', headers=logged_in_headers) assert rv.status_code == 204 assert rv.data.decode('utf-8') == '' assert len(Answer.query.filter_by(user_id=1).all()) == 0 rv = client.delete('/api/v1/answers', headers=admin_logged_in_headers) assert json.loads(rv.data.decode('utf-8')) == { 'message': 'Administrators are not authorized to proceed with this action', 'status': 403 }
def test_add_answers_case_sensitive(app, client, logged_in_headers): """Test the answers POST route when case sensitive answers are enabled.""" data = json.dumps([ { 'question_id': 2, 'answer': 'Bright Green' }, { 'question_id': 3, 'answer': 'Buzz Lightyear' }, { 'question_id': 1, 'answer': 'strawberry' }, ]) with mock.patch.dict(app.config, {'CASE_SENSITIVE_ANSWERS': True}): client.post('/api/v1/answers', headers=logged_in_headers, data=data) assert Answer.verify_answer('bright green', Answer.query.get(1).answer) is False assert Answer.verify_answer('Bright Green', Answer.query.get(1).answer) is True
def test_get_answer_different_user(client, logged_in_headers): """Test accessing the answer of a different user in the answers/<id> route.""" user = User(ad_guid='5609c5ec-c0df-4480-a94b-b6eb0fc4c066') db.session.add(user) db.session.commit() answer = Answer(answer=Answer.hash_answer('strawberry'), user_id=user.id, question_id=1) db.session.add(answer) db.session.commit() rv = client.get('/api/v1/answers/1', headers=logged_in_headers) assert json.loads(rv.data.decode('utf-8')) == { 'message': 'This answer is not associated with your account', 'status': 401 }
def add_answers(): """ Add a user's secret answers tied to administrator approved questions. :rtype: flask.Response """ user_ad_guid = get_jwt_identity()['guid'] user_id = db.session.query( User.id).filter_by(ad_guid=user_ad_guid).scalar() username = get_jwt_identity()['username'] # Make sure the user hasn't already set the required amount of secret answers num_answers_in_db = \ (db.session.query(func.count(Answer.answer))).filter_by(user_id=user_id).scalar() if num_answers_in_db != 0: log.debug({ 'message': 'The user attempted to set their secret answers but had them already set', 'user': username, }) raise ValidationError( 'You\'ve previously set your secret answers. Please reset them to set them again.' ) req_json = copy.deepcopy(request.get_json(force=True)) if not isinstance(req_json, list): log.debug({ 'message': 'The user did not supply an array', 'user': username }) raise ValidationError('The input must be an array') num_answers = len(req_json) # Verify that the user supplied the required amount of answers if num_answers != current_app.config['REQUIRED_ANSWERS']: log.info({ 'message': 'The user supplied an invalid amount of answers', 'user': username }) if num_answers == 1: error_prefix = '1 answer was' else: error_prefix = '{0} answers were'.format(num_answers) raise ValidationError('{0} supplied but {1} are required'.format( error_prefix, current_app.config['REQUIRED_ANSWERS'])) question_ids = set() answer_strings = set() for answer in req_json: _validate_api_input(answer, 'answer', string_types) _validate_api_input(answer, 'question_id', int) # Verify the answers meet the length requirements if len(answer['answer'] ) < current_app.config['ANSWERS_MINIMUM_LENGTH']: log.info({ 'message': 'The user supplied an answer of length {0}, but {1} is required' .format(len(answer['answer']), current_app.config['ANSWERS_MINIMUM_LENGTH']), 'user': username, }) raise ValidationError( 'The answer must be at least {0} characters long'.format( current_app.config['ANSWERS_MINIMUM_LENGTH'])) # If answers aren't stored as case-sensitive, then convert it to lowercase if current_app.config['CASE_SENSITIVE_ANSWERS'] is False: log.debug({ 'message': 'Setting the answer to lowercase', 'user': username }) answer['answer'] = answer['answer'].lower() # Make sure the supplied question_id maps to a real and enabled question in the database question = Question.query.get(answer['question_id']) if not question: log.info({ 'message': 'The user supplied an invalid question', 'user': username }) raise ValidationError('The "question_id" is invalid') elif question.enabled is False: log.info({ 'message': 'The user tried to use a disabled question', 'user': username }) raise ValidationError( 'The "question_id" of {0} is to a disabled question'.format( question.id)) # Store these in sets to check duplicates question_ids.add(answer['question_id']) answer_strings.add(answer['answer']) # Make sure the user doesn't try to reuse the same question if len(question_ids) != num_answers: log.info({ 'message': 'The user supplied duplicate questions', 'user': username }) raise ValidationError( 'One or more questions were the same. Please provide unique questions.' ) # If duplicate answers aren't allowed, then verify the answers are unique allow_dup_answers = current_app.config['ALLOW_DUPLICATE_ANSWERS'] if allow_dup_answers is False and num_answers != len(answer_strings): log.info({ 'message': 'The user supplied duplicate answers', 'user': username }) raise ValidationError( 'One or more answers were the same. Please provide unique answers.' ) # Now that the input is validated, add the entries to the database answer_objects = [] for answer in req_json: hashed_answer = Answer.hash_answer(answer['answer']) answer_obj = Answer(answer=hashed_answer, question_id=answer['question_id'], user_id=user_id) db.session.add(answer_obj) answer_objects.append(answer_obj) db.session.commit() # This must be run after the session is committed because the ID needs to be set answers_json = [answer.to_json() for answer in answer_objects] log.info({ 'message': 'The user successfully set their secret answers', 'user': username }) return jsonify(answers_json), 201
def add_answers(): """ Add a user's secret answers tied to administrator approved questions. :rtype: flask.Response """ user_ad_guid = get_jwt_identity()['guid'] user_id = db.session.query(User.id).filter_by(ad_guid=user_ad_guid).scalar() username = get_jwt_identity()['username'] # Make sure the user hasn't already set the required amount of secret answers num_answers_in_db = \ (db.session.query(func.count(Answer.answer))).filter_by(user_id=user_id).scalar() if num_answers_in_db != 0: log.debug({ 'message': 'The user attempted to set their secret answers but had them already set', 'user': username, }) raise ValidationError( 'You\'ve previously set your secret answers. Please reset them to set them again.') req_json = copy.deepcopy(request.get_json(force=True)) if not isinstance(req_json, list): log.debug({'message': 'The user did not supply an array', 'user': username}) raise ValidationError('The input must be an array') num_answers = len(req_json) # Verify that the user supplied the required amount of answers if num_answers != current_app.config['REQUIRED_ANSWERS']: log.info({'message': 'The user supplied an invalid amount of answers', 'user': username}) if num_answers == 1: error_prefix = '1 answer was' else: error_prefix = '{0} answers were'.format(num_answers) raise ValidationError('{0} supplied but {1} are required'.format( error_prefix, current_app.config['REQUIRED_ANSWERS'] )) question_ids = set() answer_strings = set() for answer in req_json: _validate_api_input(answer, 'answer', string_types) _validate_api_input(answer, 'question_id', int) # Verify the answers meet the length requirements if len(answer['answer']) < current_app.config['ANSWERS_MINIMUM_LENGTH']: log.info({ 'message': 'The user supplied an answer of length {0}, but {1} is required'.format( len(answer['answer']), current_app.config['ANSWERS_MINIMUM_LENGTH'] ), 'user': username, }) raise ValidationError('The answer must be at least {0} characters long'.format( current_app.config['ANSWERS_MINIMUM_LENGTH'])) # If answers aren't stored as case-sensitive, then convert it to lowercase if current_app.config['CASE_SENSITIVE_ANSWERS'] is False: log.debug({'message': 'Setting the answer to lowercase', 'user': username}) answer['answer'] = answer['answer'].lower() # Make sure the supplied question_id maps to a real and enabled question in the database question = Question.query.get(answer['question_id']) if not question: log.info({'message': 'The user supplied an invalid question', 'user': username}) raise ValidationError('The "question_id" is invalid') elif question.enabled is False: log.info({'message': 'The user tried to use a disabled question', 'user': username}) raise ValidationError( 'The "question_id" of {0} is to a disabled question'.format(question.id)) # Store these in sets to check duplicates question_ids.add(answer['question_id']) answer_strings.add(answer['answer']) # Make sure the user doesn't try to reuse the same question if len(question_ids) != num_answers: log.info({'message': 'The user supplied duplicate questions', 'user': username}) raise ValidationError( 'One or more questions were the same. Please provide unique questions.') # If duplicate answers aren't allowed, then verify the answers are unique allow_dup_answers = current_app.config['ALLOW_DUPLICATE_ANSWERS'] if allow_dup_answers is False and num_answers != len(answer_strings): log.info({'message': 'The user supplied duplicate answers', 'user': username}) raise ValidationError('One or more answers were the same. Please provide unique answers.') # Now that the input is validated, add the entries to the database answer_objects = [] for answer in req_json: hashed_answer = Answer.hash_answer(answer['answer']) answer_obj = Answer( answer=hashed_answer, question_id=answer['question_id'], user_id=user_id) db.session.add(answer_obj) answer_objects.append(answer_obj) db.session.commit() # This must be run after the session is committed because the ID needs to be set answers_json = [answer.to_json() for answer in answer_objects] log.info({'message': 'The user successfully set their secret answers', 'user': username}) return jsonify(answers_json), 201
def reset_password(): """ Reset a user's password using their secret answers. :rtype: flask.Response """ req_json = request.get_json(force=True) _validate_api_input(req_json, 'answers', list) _validate_api_input(req_json, 'new_password', string_types) _validate_api_input(req_json, 'username', string_types) answers = req_json['answers'] new_password = req_json['new_password'] username = req_json['username'] not_setup_msg = ( 'You must have configured at least {0} secret answers before resetting your ' 'password').format(current_app.config['REQUIRED_ANSWERS']) # Verify the user exists in the database ad = adreset.ad.AD() ad.service_account_login() user_id = User.get_id_from_ad_username(username, ad) if not user_id: msg = 'The user attempted a password reset but does not exist in the database' log.debug({'message': msg, 'user': username}) raise ValidationError(not_setup_msg) # Make sure the user isn't locked out if User.is_user_locked_out(user_id): msg = 'The user attempted a password reset but their account is locked in ADReset' log.info({'message': msg, 'user': username}) raise Unauthorized('Your account is locked. Please try again later.') db_answers = Answer.query.filter_by(user_id=user_id).all() # Create a dictionary of question_id to answer from entries in the database. This will avoid # the need to continuously loop through these answers looking for specific answers later on. q_id_to_answer_db = {} for answer in db_answers: q_id_to_answer_db[answer.question_id] = answer.answer # Make sure the user has all their answers configured if len(q_id_to_answer_db.keys()) != current_app.config['REQUIRED_ANSWERS']: msg = ( 'The user did not have their secret answers configured and attempted to reset their ' 'password') log.debug({'message': msg, 'user': username}) raise ValidationError(not_setup_msg) seen_question_ids = set() for answer in answers: if not isinstance( answer, dict) or 'question_id' not in answer or 'answer' not in answer: raise ValidationError( 'The answers must be an object with the keys "question_id" and "answer"' ) _validate_api_input(answer, 'question_id', int) _validate_api_input(answer, 'answer', string_types) if answer['question_id'] not in q_id_to_answer_db: msg = ( 'The user answered a question they did not previously configure while ' 'attempting to reset their password') log.info({'message': msg, 'user': username}) raise ValidationError( 'One of the answers was to a question that wasn\'t previously configured' ) # Don't allow an attacker to enter in the same question and answer combination more than # once if answer['question_id'] in seen_question_ids: msg = ( 'The user answered the same question multiple times while attempting to reset ' 'their password') log.info({'message': msg, 'user': username}) raise ValidationError( 'You must answer {0} different questions'.format( current_app.config['REQUIRED_ANSWERS'])) seen_question_ids.add(answer['question_id']) # Only check if the answers are correct after knowing the input is valid as to not give away # any hints as to which answer is incorrect for an attacker for answer in answers: if current_app.config['CASE_SENSITIVE_ANSWERS'] is True: input_answer = answer['answer'] else: input_answer = answer['answer'].lower() is_correct_answer = Answer.verify_answer( input_answer, q_id_to_answer_db[answer['question_id']]) if is_correct_answer is not True: log.info({ 'message': 'The user entered an incorrect answer', 'user': username }) failed_attempt = FailedAttempt(user_id=user_id, time=datetime.utcnow()) db.session.add(failed_attempt) db.session.commit() if User.is_user_locked_out(user_id): msg = 'The user failed too many password reset attempts. They are now locked out.' log.info({'message': msg, 'user': username}) raise Unauthorized( 'You have answered incorrectly too many times. Your account is ' 'now locked. Please try again later.') raise Unauthorized( 'One or more answers were incorrect. Please try again.') log.debug({ 'message': 'The user successfully answered their questions', 'user': username }) ad.reset_password(username, new_password) log.info({ 'message': 'The user successfully reset their password', 'user': username }) return jsonify({}), 204
def reset_password(username): """ Reset a user's password using their secret answers. :rtype: flask.Response """ req_json = request.get_json(force=True) _validate_api_input(req_json, 'answers', list) _validate_api_input(req_json, 'new_password', string_types) answers = req_json['answers'] new_password = req_json['new_password'] not_setup_msg = ('You must have configured at least {0} secret answers before resetting your ' 'password').format(current_app.config['REQUIRED_ANSWERS']) # Verify the user exists in the database ad = adreset.ad.AD() ad.service_account_login() user_id = User.get_id_from_ad_username(username, ad) if not user_id: msg = 'The user attempted a password reset but does not exist in the database' log.debug({'message': msg, 'user': username}) raise ValidationError(not_setup_msg) # Make sure the user isn't locked out if User.is_user_locked_out(user_id): msg = 'The user attempted a password reset but their account is locked in ADReset' log.info({'message': msg, 'user': username}) raise Unauthorized('Your account is locked. Please try again later.') db_answers = Answer.query.filter_by(user_id=user_id).all() # Create a dictionary of question_id to answer from entries in the database. This will avoid # the need to continuously loop through these answers looking for specific answers later on. q_id_to_answer_db = {} for answer in db_answers: q_id_to_answer_db[answer.question_id] = answer.answer # Make sure the user has all their answers configured if len(q_id_to_answer_db.keys()) != current_app.config['REQUIRED_ANSWERS']: msg = ('The user did not have their secret answers configured and attempted to reset their ' 'password') log.debug({'message': msg, 'user': username}) raise ValidationError(not_setup_msg) seen_question_ids = set() for answer in answers: if not isinstance(answer, dict) or 'question_id' not in answer or 'answer' not in answer: raise ValidationError( 'The answers must be an object with the keys "question_id" and "answer"') _validate_api_input(answer, 'question_id', int) _validate_api_input(answer, 'answer', string_types) if answer['question_id'] not in q_id_to_answer_db: msg = ('The user answered a question they did not previously configure while ' 'attempting to reset their password') log.info({'message': msg, 'user': username}) raise ValidationError( 'One of the answers was to a question that wasn\'t previously configured') # Don't allow an attacker to enter in the same question and answer combination more than # once if answer['question_id'] in seen_question_ids: msg = ('The user answered the same question multiple times while attempting to reset ' 'their password') log.info({'message': msg, 'user': username}) raise ValidationError('You must answer {0} different questions'.format( current_app.config['REQUIRED_ANSWERS'])) seen_question_ids.add(answer['question_id']) # Only check if the answers are correct after knowing the input is valid as to not give away # any hints as to which answer is incorrect for an attacker for answer in answers: if current_app.config['CASE_SENSITIVE_ANSWERS'] is True: input_answer = answer['answer'] else: input_answer = answer['answer'].lower() is_correct_answer = Answer.verify_answer( input_answer, q_id_to_answer_db[answer['question_id']]) if is_correct_answer is not True: log.info({'message': 'The user entered an incorrect answer', 'user': username}) failed_attempt = FailedAttempt(user_id=user_id, time=datetime.utcnow()) db.session.add(failed_attempt) db.session.commit() if User.is_user_locked_out(user_id): msg = 'The user failed too many password reset attempts. They are now locked out.' log.info({'message': msg, 'user': username}) raise Unauthorized('You have answered incorrectly too many times. Your account is ' 'now locked. Please try again later.') raise Unauthorized('One or more answers were incorrect. Please try again.') log.debug({'message': 'The user successfully answered their questions', 'user': username}) ad.reset_password(username, new_password) log.info({'message': 'The user successfully reset their password', 'user': username}) return jsonify({}), 204