"""Test for auth.py.""" import datetime import random from unittest import mock from unittest.mock import MagicMock from jwtauth import JWTAuth from jwtauth.exceptions import JWTAuthError import pytest from owsrequest import auth from owsrequest.config import logger from owsrequest.constants import auth as auth_constants mock_datetime = datetime.datetime(2020, 1, 1, 1, 0, 0, 123) @pytest.mark.parametrize(('token', 'response'), [ ('Bearer token', 'token'), ('Monster token', None), ('Bearer token extra', None), ('ows-product/ows-account:hmac1234', None), ('', None) ]) def test_extract_jwt_token(token, response): """Test extraction of an authorization token.""" assert auth.extract_jwt_token(token) == response def test_cache_jwt_token(monkeypatch): """Test caching of jwt token.""" token_expiration_time = str( datetime.datetime.utcnow() + datetime.timedelta( seconds=3600)) jwt_token = { 'jwt_token_expiration': token_expiration_time, 'jwt_token': 'jwttoken' } expiration = { 'jwt_expiration_time': token_expiration_time, 'jwt_expiry_secs': 3000 } cache_mock = MagicMock() cache_mock.set = MagicMock() monkeypatch.setattr( auth, 'get_jwt', MagicMock(return_value=jwt_token, spec=auth.get_jwt)) monkeypatch.setattr( auth, 'get_jwt_cache_expiry', MagicMock(return_value=expiration, spec=auth.get_jwt_cache_expiry)) monkeypatch.setattr( auth, 'get_uwsgi_cache_object', MagicMock(return_value=cache_mock)) auth.cache_jwt_token() cache_mock.set.assert_any_call('jwt_token', 'jwttoken', 3000) cache_mock.set.assert_any_call( 'jwt_expiration_time', token_expiration_time, 3000) def test_cache_jwks(monkeypatch): """Test caching jwks.""" jwks = {'keys': [{'test'}]} cache_mock = MagicMock() cache_mock.set = MagicMock() monkeypatch.setattr( auth, 'get_uwsgi_cache_object', MagicMock(return_value=cache_mock)) auth.cache_jwks(jwks) cache_mock.set.assert_any_call('jwks', jwks, 0) def test_get_jwt_cache_expiry(monkeypatch): """Test fetching expiration of jwt token.""" token_expiry_seconds = 36000 token_expiration_time = str( mock_datetime + datetime.timedelta(seconds=token_expiry_seconds)) jitter = 1 expected_cache_timeout = \ token_expiry_seconds - auth_constants.TOKEN_EXPIRATION_DELTA + jitter expected_expiation_time = mock_datetime + datetime.timedelta( seconds=expected_cache_timeout) expiration = { 'jwt_expiration_time': expected_expiation_time, 'jwt_expiry_secs': expected_cache_timeout } monkeypatch.setattr(random, 'randint', MagicMock(return_value=jitter)) auth.datetime = MockTime cache_timeout = auth.get_jwt_cache_expiry(token_expiration_time) assert cache_timeout == expiration def test_get_jwt_cache_expiry_error(monkeypatch): """Test fetching expiration of jwt token in case of error.""" jitter = 1 monkeypatch.setattr(random, 'randint', MagicMock(return_value=jitter)) auth.datetime = MockTime cache_timeout = auth.get_jwt_cache_expiry('incorrect format') assert cache_timeout == { 'jwt_expiration_time': None, 'jwt_expiry_secs': -1 } def test_get_jwt(monkeypatch): """Test for get_jwt.""" mock_expiration_time = str(datetime.datetime.utcnow()) mock_jwt = 'jwt_token' secrets_manager_client = MagicMock() secrets_manager_client.get_cred.side_effect = \ [mock_jwt, mock_expiration_time] response = auth.get_jwt(secrets_manager_client) assert response == { auth_constants.JWT_SECRET_KEY: mock_jwt, auth_constants.JWT_EXPIRY_SECRET_KEY: mock_expiration_time } def test_get_jwt_error(monkeypatch): """Test for get_jwt with exception.""" with mock.patch('owsrequest.auth.get_secret') as get_secret_mock: secrets_manager_client = MagicMock() monkeypatch.setattr(logger, 'info', MagicMock()) get_secret_mock.side_effect = Exception('test') response = auth.get_jwt(secrets_manager_client) assert response == {} assert logger.info.called @pytest.mark.parametrize( 'receiver, jwt_services_list, jwt_services_cached,' 'jwt_cached, expected_response', [ ('ows-account', ['ows-account'], True, True, True), ('ows-non-jwt-service', ['ows-account'], True, True, False), ('ows-account', ['ows-account'], True, False, False), ('ows-account', [], False, True, False) ]) def test_is_jwt_cached( monkeypatch, receiver, jwt_services_list, jwt_services_cached, jwt_cached, expected_response): """Test for is_jwt_cached.""" uwsgi_cache_mock = MagicMock() uwsgi_cache_mock.has.side_effect = [jwt_services_cached, jwt_cached] uwsgi_cache_mock.get.side_effect = [ jwt_services_list, 'jwt_token', 'jwt_token'] monkeypatch.setattr( auth, 'get_uwsgi_cache_object', MagicMock(return_value=uwsgi_cache_mock)) response = auth.is_jwt_cached('ows-any-service', receiver) assert response is expected_response def test_initialize_cache(monkeypatch): """Test for initialize_cache called and cache set from async call.""" uwsgi_cache_mock = MagicMock() uwsgi_cache_mock.has = MagicMock(return_value=False) monkeypatch.setattr( auth, 'cache_jwt_token', MagicMock(return_value=None)) monkeypatch.setattr( auth, 'cache_jwt_enabled_services', MagicMock(return_value=None)) monkeypatch.setattr( auth, 'get_uwsgi_cache_object', MagicMock(return_value=uwsgi_cache_mock)) auth.initialize_cache('service_name') assert auth.cache_jwt_token.called assert auth.cache_jwt_enabled_services.called assert uwsgi_cache_mock.get('jwt_token') assert uwsgi_cache_mock.get('jwt_expiration_time') assert uwsgi_cache_mock.get('jwt_enabled_services') assert auth.cache_jwt_token.call_count == 1 assert auth.cache_jwt_enabled_services.call_count == 1 def test_get_auth0_jwks(monkeypatch): """Test for get_auth0_jwks.""" with mock.patch('json.loads') as load_mock: auth.get_auth0_jwks() assert load_mock.called def test_get_jwt_enabled_services(monkeypatch): """Test for get_jwt_enabled_services.""" mock_jwt_enabled_services = ['ows-account'] secrets_manager_client = MagicMock() secrets_manager_client.get_cred = MagicMock( return_value=mock_jwt_enabled_services) response = auth.get_jwt_enabled_services(secrets_manager_client) assert response == mock_jwt_enabled_services def test_get_jwt_enabled_services_error(monkeypatch): """Test for get_jwt_enabled_services with exception.""" with mock.patch('owsrequest.auth.get_secret') as get_secret_mock: secrets_manager_client = MagicMock() monkeypatch.setattr(logger, 'info', MagicMock()) get_secret_mock.side_effect = Exception('test') response = auth.get_jwt_enabled_services(secrets_manager_client) assert response is None assert logger.info.called def test_cache_jwt_enabled_services(monkeypatch): """Test caching of jwt_enabled_services.""" expected_jwt_enabled_service = ['ows-account', 'ows-service'] mock_jwt_enabled_services = ['ows-account'] uwsgi_cache_mock = MagicMock() uwsgi_cache_mock.set = MagicMock() update_secret_mock = MagicMock() update_secret_mock.update_secret = MagicMock(return_value=None) secrets_manager_client = MagicMock() monkeypatch.setattr( auth, 'get_secrets_manager_client', MagicMock(return_value=secrets_manager_client)) monkeypatch.setattr( auth, 'get_jwt_enabled_services', MagicMock(return_value=mock_jwt_enabled_services)) monkeypatch.setattr( auth, 'get_uwsgi_cache_object', MagicMock(return_value=uwsgi_cache_mock)) auth.cache_jwt_enabled_services('ows-service') uwsgi_cache_mock.set.assert_called_with( 'jwt_enabled_services', expected_jwt_enabled_service, 0) def test_cache_jwt_enabled_services_error(monkeypatch): """Test caching of jwt_enabled_services with exception.""" with mock.patch('owsrequest.auth.update_secret') as update_secret_mock: mock_jwt_enabled_services = ['ows-account'] uwsgi_cache_mock = MagicMock() uwsgi_cache_mock.set = MagicMock() secrets_manager_client = MagicMock() monkeypatch.setattr( auth, 'get_secrets_manager_client', MagicMock(return_value=secrets_manager_client)) monkeypatch.setattr( auth, 'get_jwt_enabled_services', MagicMock(return_value=mock_jwt_enabled_services)) update_secret_mock.side_effect = Exception('test') monkeypatch.setattr(logger, 'info', MagicMock()) auth.cache_jwt_enabled_services('ows-service') uwsgi_cache_mock.set.assert_not_called() assert logger.info.called class MockTime: """Mock for utcnow and strptime.""" @staticmethod def utcnow(): """Set mock value for utcnow.""" return mock_datetime @staticmethod def strptime(datetime_string, time_format): """Set mock value for strptime.""" return datetime.datetime.strptime(datetime_string, time_format) @mock.patch('owsrequest.auth.jwt_auth_from_environment') def test_validate_and_decode_jwt_token_use_client( mock_jwt_auth_from_environment): """Test validation of jwt token uses provided client.""" token = 'some token' decoded_token = {'hello': 'decoded token'} mock_jwt_auth = MagicMock(spec=JWTAuth) mock_jwt_auth.get_token.return_value = decoded_token mock_jwt_auth_from_environment.return_value = mock_jwt_auth response = auth.validate_and_decode_jwt_token( token, mock_jwt_auth, ) assert response.status == 200 assert response.message == decoded_token mock_jwt_auth_from_environment.assert_not_called() mock_jwt_auth.get_token.assert_called_once_with(token) @pytest.mark.parametrize('environment', [('qa'), ('prod')]) @mock.patch('owsrequest.auth.config') @mock.patch('owsrequest.auth.jwt_auth_from_environment') def test_validate_and_decode_jwt_token_returns_token( mock_jwt_auth_from_environment, mock_config, environment): """Test validation of jwt token returns token as response.""" token = 'some token' decoded_token = {'hello': 'decoded token'} mock_config.ENVIRONMENT = environment mock_jwt_auth = MagicMock(spec=JWTAuth) mock_jwt_auth.get_token.return_value = decoded_token mock_jwt_auth_from_environment.return_value = mock_jwt_auth response = auth.validate_and_decode_jwt_token( token, ) assert response.status == 200 assert response.message == decoded_token mock_jwt_auth_from_environment.assert_called_once_with(environment) mock_jwt_auth.get_token.assert_called_once_with(token) @pytest.mark.parametrize('environment', [('qa'), ('prod')]) @mock.patch('owsrequest.auth.config') @mock.patch('owsrequest.auth.jwt_auth_from_environment') def test_validate_and_decode_jwt_token_returns_error( mock_jwt_auth_from_environment, mock_config, environment): """Test validation of jwt token returns error on JwtAuthError.""" token = 'some invalid token' error_message = 'some jwt auth error message' mock_config.ENVIRONMENT = environment mock_jwt_auth = MagicMock(spec=JWTAuth) mock_jwt_auth.get_token.side_effect = JWTAuthError(error_message) mock_jwt_auth_from_environment.return_value = mock_jwt_auth response = auth.validate_and_decode_jwt_token( token, ) assert response.status == 401 assert response.errors['message'] == error_message mock_jwt_auth_from_environment.assert_called_once_with(environment) mock_jwt_auth.get_token.assert_called_once_with(token)