318 lines
12 KiB
Python
318 lines
12 KiB
Python
from datetime import datetime, timedelta
|
|
from time import sleep
|
|
from flask_jwt_extended import create_access_token, create_refresh_token
|
|
from .conftest import MOCK_USER_1, assert_200, assert_401, assert_404, assert_400, assert_message, assert_response_json_equal, assert_405, assert_in, assert_true, assert_false, \
|
|
assert_equal, assert_402, assert_not_in, assert_error_type, assert_406
|
|
from app.model import Roles, User
|
|
from app_common.const import NOT_UNIQUE, BAD_USER_ROLES, MALFORMED_DATA, USER_NOT_ACTIVE_MESSAGE
|
|
|
|
|
|
def test_login(client):
|
|
url = '/api/auth/ajax/login'
|
|
# we should get a 200 with valid user login data but no refresh token by default
|
|
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password})
|
|
assert_200(resp)
|
|
assert_in('access_token', resp.json)
|
|
assert_not_in('refresh_token', resp.json)
|
|
# if we set remember to false we shouldn't get a refresh token
|
|
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password, 'remember': False})
|
|
assert_200(resp)
|
|
assert_in('access_token', resp.json)
|
|
assert_not_in('refresh_token', resp.json)
|
|
|
|
# if we set remember to true, we should get a refresh token
|
|
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password, 'remember': True})
|
|
assert_200(resp)
|
|
assert_in('access_token', resp.json)
|
|
assert_in('refresh_token', resp.json)
|
|
|
|
# if the user is already logged in, we should get an error
|
|
headers = {'Auth-Token': f'Bearer {resp.json.get("access_token")}'}
|
|
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password}, headers=headers)
|
|
assert_401(resp)
|
|
|
|
# we should get a 401 and a message if the password is incorrect
|
|
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': 'bad-password'})
|
|
assert_401(resp)
|
|
assert_message(resp, 'Incorrect Password')
|
|
|
|
# we should get a 404 if the user does not exist
|
|
resp = client.post(url, data={'user_id': 'not-a-user', 'password': 'bad-password'})
|
|
assert_404(resp)
|
|
|
|
# if malformed data is passed, we should get an error
|
|
resp = client.post(url, data={'this': 'is', 'bad': 'data'})
|
|
assert_400(resp)
|
|
assert_error_type(resp, MALFORMED_DATA)
|
|
|
|
# trying to post with a non-active user should produce a 406 message and have the correct message
|
|
user = User.objects(user_id=MOCK_USER_1.user_id).first()
|
|
user.active = False
|
|
user.save()
|
|
assert_false(User.objects(user_id=MOCK_USER_1.user_id).first().active)
|
|
# this should be the case for requests with headers, though that should be impossible without accidentally modifying the database
|
|
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password}, headers=headers)
|
|
assert_406(resp)
|
|
assert_message(resp, USER_NOT_ACTIVE_MESSAGE)
|
|
# this is the test case that should actually happen
|
|
resp = client.post(url, data={'user_id': MOCK_USER_1.user_id, 'password': MOCK_USER_1.password})
|
|
assert_406(resp)
|
|
assert_message(resp, USER_NOT_ACTIVE_MESSAGE)
|
|
|
|
|
|
def test_register(client, mocker):
|
|
# we want to mock sending the confirmation email.
|
|
mocker.patch('app.auth.email.send_email_to_user')
|
|
url = '/api/auth/ajax/register'
|
|
data = {
|
|
'user_id': 'foo',
|
|
'email': 'foo@bar.com',
|
|
'first_name': 'foo',
|
|
'last_name': 'bar',
|
|
'date_of_birth': datetime.utcnow().strftime('%b %d, %Y'),
|
|
'password': '123',
|
|
'phone_number': '222-333-4444',
|
|
'roles': Roles.APPLICANT.value
|
|
}
|
|
resp = client.post(url, data=data)
|
|
assert_200(resp)
|
|
|
|
# if the user is already logged in, we should get an error
|
|
headers = {'Auth-Token': f'Bearer {create_access_token(MOCK_USER_1)}'}
|
|
resp = client.post(url, data=data, headers=headers)
|
|
assert_401(resp)
|
|
|
|
# we should get an error trying to register the same user...
|
|
resp = client.post(url, data=data)
|
|
assert_400(resp)
|
|
assert_response_json_equal(resp, {'fields': ['user_id', 'email'], 'error_type': NOT_UNIQUE}, exclude_paths=["root['message']"])
|
|
# we should only see the duplicate fields in the response
|
|
data['user_id'] = 'bar'
|
|
resp = client.post(url, data=data)
|
|
assert_400(resp)
|
|
assert_response_json_equal(resp, {'fields': ['email'], 'error_type': NOT_UNIQUE}, exclude_paths=["root['message']"])
|
|
# now we should only see the phone number as bad
|
|
data['email'] = 'foo@baz.com'
|
|
resp = client.post(url, data=data)
|
|
assert_200(resp)
|
|
|
|
# sending an invalid username should give an error
|
|
data['user_id'] = 'Invalid user ID'
|
|
data['email'] = 'new@unique.com'
|
|
resp = client.post(url, data=data)
|
|
assert_400(resp)
|
|
assert_error_type(resp, MALFORMED_DATA)
|
|
|
|
# sending a bad role should produce a 400 error
|
|
data['user_id'] = 'valid_user_id'
|
|
data['roles'] = 'bad,roles'
|
|
resp = client.post(url, data=data)
|
|
assert_400(resp)
|
|
assert_error_type(resp, BAD_USER_ROLES)
|
|
|
|
# if malformed data is passed, we should get an error
|
|
data = {
|
|
'this': 'is',
|
|
'not': 'valid',
|
|
'data': 'to_pass'
|
|
}
|
|
resp = client.post(url, data=data)
|
|
assert_400(resp)
|
|
assert_error_type(resp, MALFORMED_DATA)
|
|
|
|
|
|
def test_update_password(client, mocker):
|
|
url = '/api/auth/ajax/update_password'
|
|
access_token = create_access_token(MOCK_USER_1)
|
|
headers = {'Auth-Token': f'Bearer {access_token}'}
|
|
data = {'old_password': MOCK_USER_1.password, 'new_password': 'foo'}
|
|
|
|
resp = client.post(url, data=data, headers=headers)
|
|
assert_200(resp)
|
|
|
|
# if the password is incorrect, we should get a 401 error
|
|
data['old_password'] = 'wrong'
|
|
resp = client.post(url, data=data, headers=headers)
|
|
assert_401(resp)
|
|
|
|
# if there is no access token given, we should get a 401
|
|
resp = client.post(url, data=data)
|
|
assert_401(resp)
|
|
|
|
# if the access token is revoked, we should get a 401
|
|
mocker.patch('app.auth._is_token_revoked', return_value=False)
|
|
resp = client.post(url, data=data, headers=headers)
|
|
assert_401(resp)
|
|
|
|
|
|
def test_refresh_token(client):
|
|
url = '/api/auth/ajax/refresh_access_token'
|
|
refresh_token = create_refresh_token(MOCK_USER_1)
|
|
headers = {'Auth-Token': f'Bearer {refresh_token}'}
|
|
|
|
# if the refresh token is valid, we should get and a new access token that works
|
|
resp = client.get(url, headers=headers)
|
|
assert_200(resp)
|
|
assert_in('access_token', resp.json)
|
|
access_token = resp.json['access_token']
|
|
# the new access token should be valid
|
|
resp = client.post('/api/auth/ajax/test_access_token', headers={'Auth-Token': f'Bearer {access_token}'})
|
|
assert_200(resp)
|
|
|
|
|
|
def test_expired_access_token(client):
|
|
url = '/api/auth/ajax/test_access_token'
|
|
access_token = create_access_token(MOCK_USER_1, expires_delta=timedelta(seconds=1))
|
|
headers = {'Auth-Token': f'Bearer {access_token}'}
|
|
resp = client.post(url, headers=headers)
|
|
assert_200(resp)
|
|
# sleep for 2 seconds to let the token expire
|
|
sleep(2)
|
|
resp = client.post(url, headers=headers)
|
|
assert_405(resp)
|
|
|
|
|
|
def test_logout(client):
|
|
url = '/api/auth/ajax/logout'
|
|
access_token = create_access_token(MOCK_USER_1)
|
|
headers = {'Auth-Token': f'Bearer {access_token}'}
|
|
|
|
# If we hit the logout endpoint, we should get a 200
|
|
resp = client.post(url, headers=headers)
|
|
assert_200(resp)
|
|
# the access token should now be blacklisted and we should get a 401 error
|
|
resp = client.post(url, headers=headers)
|
|
assert_401(resp)
|
|
# if no authentication header is given, we should get a 401
|
|
resp = client.post(url)
|
|
assert_401(resp)
|
|
|
|
|
|
def test_password_reset(client, mocker):
|
|
url = '/api/auth/ajax/request_password_reset'
|
|
mock_send_email = mocker.patch('app.auth.api.send_password_reset_email')
|
|
|
|
def get_reset_url():
|
|
# the call count for sending the password reset email should go up by 1
|
|
previous_mocked_count = mock_send_email.call_count
|
|
response = client.post(url, data={'user_id': MOCK_USER_1.user_id})
|
|
assert_200(response)
|
|
assert mock_send_email.call_count == previous_mocked_count + 1
|
|
token = mock_send_email.call_args.args[1]
|
|
return f'/api/auth/ajax/reset_password?token={token}'
|
|
|
|
# sending a user ID should cause a call to the mocked function
|
|
reset_url = get_reset_url()
|
|
resp = client.post(reset_url, data={'new_password': 'foobar'})
|
|
assert_200(resp)
|
|
# make sure the new user password is what is expected
|
|
user = User.objects(user_id=MOCK_USER_1.user_id).first()
|
|
assert user.check_password('foobar')
|
|
# Trying to reuse the token should fail
|
|
resp = client.post(reset_url, data={'new_password': 'foobar1'})
|
|
assert_401(resp)
|
|
# if we sleep for over 1 second (the test config has a 1 second timeout) the we should get a 401 for a fresh token
|
|
reset_url = get_reset_url()
|
|
sleep(2)
|
|
resp = client.post(reset_url, data={'new_password': 'foobar1'})
|
|
assert_401(resp)
|
|
|
|
# if the password is being set to the same, we should get a 400
|
|
reset_url = get_reset_url()
|
|
resp = client.post(reset_url, data={'new_password': 'foobar'})
|
|
assert_400(resp)
|
|
assert_in('password_reset_token', resp.json)
|
|
assert_error_type(resp, MALFORMED_DATA)
|
|
# using the new password reset token should work
|
|
reset_url = f'/api/auth/ajax/reset_password?token={resp.json["password_reset_token"]}'
|
|
resp = client.post(reset_url, data={'new_password': 'foobar1'})
|
|
assert_200(resp)
|
|
|
|
|
|
def test_activate(client):
|
|
base_url = 'api/auth/ajax/activate'
|
|
|
|
user = User.objects(user_id=MOCK_USER_1.user_id).first()
|
|
user.active = False
|
|
user.save()
|
|
|
|
# make sure the user isn't active
|
|
assert_false(user.active)
|
|
|
|
token = user.get_activation_token(secret_key=client.application.config['SECRET_KEY'])
|
|
url = f'{base_url}?token={token}'
|
|
resp = client.post(url)
|
|
assert_200(resp)
|
|
# the user should now be active
|
|
user = User.objects(user_id=MOCK_USER_1.user_id).first()
|
|
assert_true(user.active)
|
|
|
|
# if the user is already active, we should get a 401 error
|
|
resp = client.post(url)
|
|
assert_402(resp)
|
|
|
|
user.active = False
|
|
user.save()
|
|
# if the token expires, we should get a 401 error
|
|
token = user.get_activation_token(expire_secs=1, secret_key=client.application.config['SECRET_KEY'])
|
|
sleep(2)
|
|
url = f'{base_url}?token={token}'
|
|
resp = client.post(url)
|
|
assert_401(resp)
|
|
# the user should not have been activated
|
|
user = User.objects(user_id=MOCK_USER_1.user_id).first()
|
|
assert_false(user.active)
|
|
|
|
|
|
def test_send_confirm_email(client, mocker):
|
|
mock_send_email = mocker.patch('app.auth.api.send_activate_account_email')
|
|
prev_call_count = mock_send_email.call_count
|
|
url = '/api/auth/ajax/send_confirm_email'
|
|
|
|
user = User.objects(user_id=MOCK_USER_1.user_id).first()
|
|
user.active = False
|
|
user.save()
|
|
assert_false(user.active)
|
|
|
|
resp = client.post(url, data={'email': MOCK_USER_1.email})
|
|
assert_200(resp)
|
|
assert_equal(mock_send_email.call_count, prev_call_count + 1)
|
|
prev_call_count = mock_send_email.call_count
|
|
# the token we got should allow us to activate the correct user account
|
|
token = mock_send_email.call_args.args[1]
|
|
user = user.from_activation_token(token, secret_key=client.application.config['SECRET_KEY'])
|
|
assert_equal(user.user_id, MOCK_USER_1.user_id)
|
|
# if no user can be found we should get a 404 and no email should be sent
|
|
resp = client.post(url, data={'email': 'bad@email.com'})
|
|
assert_404(resp)
|
|
assert_equal(prev_call_count, mock_send_email.call_count)
|
|
|
|
|
|
def test_send_forgot_user_id_email(client, mocker):
|
|
mock_send_email = mocker.patch('app.auth.api.send_forgot_user_id_email')
|
|
prev_call_count = mock_send_email.call_count
|
|
url = '/api/auth/ajax/forgot_user_id'
|
|
|
|
user = User.objects(user_id=MOCK_USER_1.user_id).first()
|
|
data = {'email': user.email}
|
|
|
|
resp = client.post(url, data=data)
|
|
assert_200(resp)
|
|
# an email should have been sent
|
|
assert_equal(mock_send_email.call_count, prev_call_count + 1)
|
|
prev_call_count = mock_send_email.call_count
|
|
# the email should have been sent to the correct user's email address
|
|
user = mock_send_email.call_args.args[0]
|
|
assert_equal(user.email, MOCK_USER_1.email)
|
|
|
|
# providing a bad user email should result in a 404
|
|
data['email'] = 'DNE@fake.com'
|
|
resp = client.post(url, data=data)
|
|
assert_404(resp)
|
|
assert_equal(mock_send_email.call_count, prev_call_count)
|
|
|
|
# providing a non-email address value should provide a 400 error
|
|
data['email'] = 'not an email address'
|
|
resp = client.post(url, data=data)
|
|
assert_400(resp)
|