# Tested code: payee/utils/permissions.py """Shared permissions logic unit tests.""" from functools import wraps from http import HTTPStatus from unittest.mock import call, MagicMock, patch from uuid import uuid4 from owsrequest.context import RequestContext from pytest_mock import MockerFixture import application from payee.constants.constants import ( COLLABORATOR_RESOURCE_NAME, LABEL_RESOURCE_NAME, PERMISSIONS_ACTIONS, PROFILES, ) from payee.constants.error import ( ERROR_DONT_HAVE_PERMISSIONS, ERROR_NO_VALID_IDENTITY_IN_CONTEXT, ERROR_NOT_PERMITTED_IDENTITY, ) from payee.utils.permissions import check_access, check_jwt_identity, or_ from payee.utils.validations import ResourceAccessCheck from tests.utils.factories import PayeeCollaboratorFactory, PayeeFactory def _make_label_resource_access_check(ids): return ResourceAccessCheck(type=LABEL_RESOURCE_NAME, ids=ids) def _make_collab_resource_access_check(ids): return ResourceAccessCheck(type=COLLABORATOR_RESOURCE_NAME, ids=ids) @patch('payee.utils.permissions._get_account_ids_from_account_payee_ids') @patch('payee.utils.permissions.validate_record_owner') @patch('payee.utils.permissions.request', new_callable=MagicMock) def test_check_access_document_profile( mock_request, mock_validate, mock_get_account_ids_from_account_payee_ids, ): """Test check_access function with document profile.""" check = _make_label_resource_access_check mock_request.headers.get.return_value = PROFILES.DOCUMENTS mock_get_account_ids_from_account_payee_ids.side_effect = lambda ids: ids @check_access(resource_type='test', action=PERMISSIONS_ACTIONS.VIEW) def test_func(): return 'test' mock_request.get_json.return_value = {'account_payee_ids': [1]} mock_validate.return_value = True assert test_func() == 'test' assert mock_validate.call_args_list == [call(check([1]))] mock_validate.return_value = False mock_validate.reset_mock() mock_request.get_json.return_value = {'account_payee_ids': []} mock_get_account_ids_from_account_payee_ids.return_value = [] assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) assert mock_validate.call_args_list == [call(check([]))] mock_validate.reset_mock() mock_request.get_json.return_value = {} assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) assert mock_validate.call_args_list == [call(check([]))] mock_validate.reset_mock() mock_request.get_json.return_value = None assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) assert mock_validate.call_args_list == [call(check([]))] mock_validate.return_value = True mock_validate.reset_mock() mock_request.get_json.return_value = {'account_payee_ids': [1]} assert test_func() == 'test' assert mock_validate.call_args_list == [call(check([1]))] mock_validate.return_value = False mock_validate.reset_mock() mock_request.get_json.return_value = {'account_payee_ids': [1, 2]} assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) assert mock_validate.call_args_list == [call(check([1, 2]))] @patch('payee.utils.permissions.validate_record_owner') @patch('payee.utils.permissions.request', new_callable=MagicMock) def test_check_access_document_profile_payee( mock_request, mock_validate, ): """Test check_access function with document profile and payee.""" check = _make_collab_resource_access_check mock_request.headers.get.return_value = PROFILES.DOCUMENTS payee = PayeeFactory.build() payee.get_typed_payee = MagicMock() payee.get_typed_payee.return_value = PayeeCollaboratorFactory.build() def inject_payee(f): @wraps(f) def wrapper(*args, **kwargs): return f(payee=payee, *args, **kwargs) return wrapper @inject_payee @check_access(resource_type='test', action=PERMISSIONS_ACTIONS.VIEW) def test_func(payee): return 'test' mock_validate.return_value = True mock_validate.reset_mock() mock_request.get_json.return_value = {} assert test_func() == 'test' assert mock_validate.call_args_list == [call(check([1]))] payee.get_typed_payee.return_value = PayeeCollaboratorFactory.build( collaborator_id=42 ) mock_validate.return_value = False mock_validate.reset_mock() mock_request.get_json.return_value = {} assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) assert mock_validate.call_args_list == [call(check([42]))] @patch('payee.utils.permissions.ForwardKwargsGetter') @patch('payee.utils.permissions.authorization_backend') @patch('payee.utils.permissions.request', new_callable=MagicMock) def test_check_access_abacus_profile(mock_request, mock_auth, mock_kwargs_getter): """Test check_access function with abacus profile.""" mock_request.headers.get.return_value = PROFILES.ABACUS @check_access(resource_type='test', action=PERMISSIONS_ACTIONS.VIEW) def test_func(): return 'test' mock_auth.is_authorized.return_value = False assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) assert mock_auth.is_authorized.call_args_list == [ call( action='view', resource_id=0, resource_type='test', resource_getter=mock_kwargs_getter.return_value, identity_uuid='', ) ] mock_request.headers.get.return_value = PROFILES.ABACUS mock_request.get_json.return_value = {'account_payee_ids': [1]} mock_auth.reset_mock() assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) assert mock_auth.is_authorized.call_args_list == [ call( action='view', resource_id=0, resource_type='test', resource_getter=mock_kwargs_getter.return_value, identity_uuid='', ) ] mock_request.get_json.return_value = {'account_payee_ids': []} assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) mock_request.get_json.return_value = {} assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) mock_request.get_json.return_value = None assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) mock_request.get_json.return_value = {'account_payee_ids': [1]} assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) mock_request.get_json.return_value = {'account_payee_ids': [1, 2]} assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) @patch('payee.utils.permissions.ForwardKwargsGetter') @patch('payee.utils.permissions.authorization_backend') @patch('payee.utils.permissions.request', new_callable=MagicMock) def test_check_access_abacus_profile_with_allowed_profiles( mock_request, mock_auth, mock_kwargs_getter ): """Test check_access function with abacus profile and allowed profiles.""" mock_request.headers.get.return_value = PROFILES.ABACUS @check_access( resource_type='test', action=PERMISSIONS_ACTIONS.VIEW, allowed_profiles=[PROFILES.ABACUS], ) def test_func(): return 'test' assert test_func() == 'test' mock_request.headers.get.return_value = PROFILES.ABACUS mock_request.get_json.return_value = {'account_payee_ids': [1]} assert test_func() == 'test' mock_request.get_json.return_value = {'account_payee_ids': []} assert test_func() == 'test' mock_request.get_json.return_value = {} assert test_func() == 'test' mock_request.get_json.return_value = None assert test_func() == 'test' mock_request.get_json.return_value = {'account_payee_ids': [1]} assert test_func() == 'test' mock_request.get_json.return_value = {'account_payee_ids': [1, 2]} assert test_func() == 'test' mock_request.headers.get.return_value = PROFILES.DOCUMENTS assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) @patch('payee.utils.permissions.request', new_callable=MagicMock) def test_check_access_abacus_profile_with_allowed_profiles_not_allowed(mock_request): """Test check_access function with abacus profile and not allowed profiles.""" mock_request.headers.get.return_value = PROFILES.ABACUS @check_access( resource_type='test', action=PERMISSIONS_ACTIONS.VIEW, allowed_profiles=[PROFILES.DOCUMENTS], ) def test_func(): return 'test' assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) mock_request.headers.get.return_value = PROFILES.ABACUS mock_request.get_json.return_value = {'account_payee_ids': [1]} assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) mock_request.get_json.return_value = {'account_payee_ids': []} assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) mock_request.get_json.return_value = {} assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) mock_request.get_json.return_value = None assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) mock_request.get_json.return_value = {'account_payee_ids': [1]} assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) mock_request.get_json.return_value = {'account_payee_ids': [1, 2]} assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) mock_request.headers.get.return_value = PROFILES.DOCUMENTS assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) @patch('payee.utils.permissions.ForwardKwargsGetter') @patch('payee.utils.permissions.authorization_backend') @patch('payee.utils.permissions.request', new_callable=MagicMock) def test_check_access_abacus_profile_with_allowed_profiles_empty( mock_request, mock_auth, mock_kwargs_getter ): """Test check_access function with abacus profile and empty allowed profiles.""" mock_request.headers.get.return_value = PROFILES.ABACUS @check_access( resource_type='test', action=PERMISSIONS_ACTIONS.VIEW, allowed_profiles=[] ) def test_func(): return 'test' assert test_func() == 'test' mock_request.headers.get.return_value = PROFILES.ABACUS mock_request.get_json.return_value = {'account_payee_ids': [1]} assert test_func() == 'test' mock_request.get_json.return_value = {'account_payee_ids': []} assert test_func() == 'test' mock_request.get_json.return_value = {} assert test_func() == 'test' mock_request.get_json.return_value = None assert test_func() == 'test' mock_request.get_json.return_value = {'account_payee_ids': [1]} assert test_func() == 'test' mock_request.get_json.return_value = {'account_payee_ids': [1, 2]} assert test_func() == 'test' mock_request.headers.get.return_value = PROFILES.DOCUMENTS assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) @patch('payee.utils.permissions.ForwardKwargsGetter') @patch('payee.utils.permissions.authorization_backend') @patch('payee.utils.permissions.request', new_callable=MagicMock) def test_check_account_payee_ids_from_request( mock_request, mock_auth, mock_kwargs_getter ): """Test check_access function with abacus profile and empty allowed profiles.""" mock_request.headers.get.return_value = PROFILES.ABACUS @check_access( resource_type='test', action=PERMISSIONS_ACTIONS.VIEW, allowed_profiles=[] ) def test_func(): return 'test' assert test_func() == 'test' mock_request.headers.get.return_value = PROFILES.ABACUS mock_request.get_json.return_value = {'account_payee_ids': [1]} assert test_func() == 'test' mock_request.get_json.return_value = {'account_payee_ids': []} assert test_func() == 'test' mock_request.get_json.return_value = {} assert test_func() == 'test' mock_request.get_json.return_value = None assert test_func() == 'test' mock_request.get_json.return_value = {'account_payee_ids': [1]} assert test_func() == 'test' mock_request.get_json.return_value = {'account_payee_ids': [1, 2]} assert test_func() == 'test' mock_request.headers.get.return_value = PROFILES.DOCUMENTS assert test_func() == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) @patch('payee.utils.permissions.ForwardKwargsGetter') @patch('payee.utils.permissions.authorization_backend') @patch('payee.utils.permissions.request', new_callable=MagicMock) def test_check_account_payee_id_from_kwargs( mock_request, mock_auth, mock_kwargs_getter ): """Test check_access function with abacus profile and empty allowed profiles.""" mock_request.headers.get.return_value = PROFILES.ABACUS @check_access( resource_type='test', action=PERMISSIONS_ACTIONS.VIEW, allowed_profiles=[] ) def test_func(account_payee_id): return account_payee_id assert test_func(account_payee_id=1) == 1 assert test_func(account_payee_id=2) == 2 assert test_func(account_payee_id=3) == 3 assert test_func(account_payee_id=4) == 4 assert test_func(account_payee_id=5) == 5 assert test_func(account_payee_id=6) == 6 mock_request.headers.get.return_value = PROFILES.DOCUMENTS assert test_func(account_payee_id=7) == ( {'error': ERROR_DONT_HAVE_PERMISSIONS}, HTTPStatus.UNAUTHORIZED, ) def test_check_jwt_identity_success(mocker: MockerFixture, faker) -> None: """ Tests that if the request context has the correct identity, we return the decorated function result. """ test_identity = str(uuid4()) test_message = faker.pystr() mock_feature = mocker.patch( 'payee.utils.permissions.is_abacus_tap_bypass_pdp_check_enabled' ) mock_feature.return_value = False mock_context = mocker.MagicMock(spec=RequestContext) mock_context.jwt_identity_id = test_identity @check_jwt_identity(test_identity) def to_be_decorated(obscure_pii=True): return test_message, HTTPStatus.OK mock_g = mocker.patch('payee.utils.permissions.g') mock_g.request_context = mock_context with application.app.test_request_context(): result = to_be_decorated() assert result == (test_message, 200) def test_check_jwt_identity_bypassed_success(mocker: MockerFixture, faker) -> None: """ Tests that if the request context has the correct identity, we return the decorated function result. """ test_identity = str(uuid4()) mock_feature = mocker.patch( 'payee.utils.permissions.is_abacus_tap_bypass_pdp_check_enabled' ) mock_feature.return_value = True @check_jwt_identity(test_identity) def to_be_decorated(obscure_pii=True): return obscure_pii, HTTPStatus.OK with application.app.test_request_context(): result = to_be_decorated() assert result == (False, 200) def test_check_jwt_identity_multiple_success(mocker: MockerFixture, faker) -> None: """ Tests that if the request context has the correct identity, we return the decorated function result. """ test_identity1 = str(uuid4()) test_identity2 = str(uuid4()) mock_feature = mocker.patch( 'payee.utils.permissions.is_abacus_tap_bypass_pdp_check_enabled' ) mock_feature.return_value = False mock_context = mocker.MagicMock(spec=RequestContext) for test_identity in (test_identity1, test_identity2): mock_context.jwt_identity_id = test_identity @check_jwt_identity([test_identity1, test_identity2]) def to_be_decorated(obscure_pii=True): return obscure_pii, HTTPStatus.OK mock_g = mocker.patch('payee.utils.permissions.g') mock_g.request_context = mock_context with application.app.test_request_context(): result = to_be_decorated() assert result == (False, 200) def test_check_jwt_identity_failure_no_identity(mocker: MockerFixture, faker) -> None: """ Tests that if the request context does not have the correct identity, we return 403. """ test_identity = str(uuid4()) test_message = faker.pystr() mock_feature = mocker.patch( 'payee.utils.permissions.is_abacus_tap_bypass_pdp_check_enabled' ) mock_feature.return_value = False mock_context = mocker.MagicMock(spec=RequestContext) mock_context.jwt_identity_id = None @check_jwt_identity(test_identity) def to_be_decorated(): return test_message, HTTPStatus.OK mock_g = mocker.patch('payee.utils.permissions.g') mock_g.request_context = mock_context with application.app.test_request_context(): result = to_be_decorated() assert result == ( {'error': ERROR_NO_VALID_IDENTITY_IN_CONTEXT}, 401, ) def test_check_jwt_identity_failure_wrong_identity( mocker: MockerFixture, faker ) -> None: """ Tests that if the request context does not have the correct identity, we return 403. """ test_identity = str(uuid4()) wrong_identity = str(uuid4()) test_message = faker.pystr() mock_feature = mocker.patch( 'payee.utils.permissions.is_abacus_tap_bypass_pdp_check_enabled' ) mock_feature.return_value = False mock_context = mocker.MagicMock(spec=RequestContext) mock_context.jwt_identity_id = wrong_identity @check_jwt_identity(test_identity) def to_be_decorated(): return test_message, HTTPStatus.OK mock_g = mocker.patch('payee.utils.permissions.g') mock_g.request_context = mock_context with application.app.test_request_context(): result = to_be_decorated() assert result == ( {'error': ERROR_NOT_PERMITTED_IDENTITY.format(jwt_identity=wrong_identity)}, HTTPStatus.UNAUTHORIZED, ) def test_or_success_correct_jwt(mocker: MockerFixture, faker) -> None: """Test or_ decorator success.""" test_identity = str(uuid4()) test_message = faker.pystr() mock_feature = mocker.patch( 'payee.utils.permissions.is_abacus_tap_bypass_pdp_check_enabled' ) mock_feature.return_value = False mock_context = mocker.MagicMock(spec=RequestContext) mock_context.jwt_identity_id = test_identity mock_g = mocker.patch('payee.utils.permissions.g') mock_g.request_context = mock_context @or_( check_jwt_identity(test_identity), check_access(resource_type='test', action=PERMISSIONS_ACTIONS.VIEW), ) def to_be_decorated(): return test_message, HTTPStatus.OK with application.app.test_request_context(): result = to_be_decorated() assert result == (test_message, HTTPStatus.OK) @patch( 'payee.utils.permissions.is_abacus_tap_bypass_pdp_check_enabled', return_value=False ) @patch('payee.utils.permissions.g') @patch('payee.utils.permissions.validate_record_owner') @patch('payee.utils.permissions.request', new_callable=MagicMock) def test_or_success_correct_profile(mock_request, moc_validate, mock_g, faker) -> None: """Test or_ decorator success.""" mock_request.headers.get.return_value = PROFILES.DOCUMENTS mock_request.get_json.return_value = {'account_payee_ids': [1]} test_identity = str(uuid4()) test_message = faker.pystr() mock_context = MagicMock(spec=RequestContext) mock_context.jwt_identity_id = test_identity mock_g.request_context = mock_context @or_( check_jwt_identity(test_identity), check_access(resource_type='test', action=PERMISSIONS_ACTIONS.VIEW), ) def to_be_decorated(): return test_message, HTTPStatus.OK with application.app.test_request_context(): result = to_be_decorated() assert result == (test_message, HTTPStatus.OK) @patch( 'payee.utils.permissions.is_abacus_tap_bypass_pdp_check_enabled', return_value=False ) @patch('payee.utils.permissions.g') @patch('payee.utils.permissions.validate_record_owner') @patch('payee.utils.permissions.request', new_callable=MagicMock) def test_or_failure(mock_request, moc_validate, mock_g, faker) -> None: """Test or_ decorator success.""" mock_request.headers.get.return_value = PROFILES.DOCUMENTS mock_request.get_json.return_value = {'account_payee_ids': [1]} moc_validate.return_value = False test_identity = str(uuid4()) wrong_identity = str(uuid4()) test_message = faker.pystr() mock_context = MagicMock(spec=RequestContext) mock_context.jwt_identity_id = wrong_identity mock_g.request_context = mock_context @or_( check_jwt_identity(test_identity), check_access(resource_type='test', action=PERMISSIONS_ACTIONS.VIEW), ) def to_be_decorated(): return test_message, HTTPStatus.OK with application.app.test_request_context(): result = to_be_decorated() assert result == ( { 'error': ', '.join( [ ERROR_NOT_PERMITTED_IDENTITY.format( jwt_identity=wrong_identity ), ERROR_DONT_HAVE_PERMISSIONS, ] ) }, HTTPStatus.UNAUTHORIZED, )