class TestHandleWebhook(unittest.TestCase): _subject_id = uuid4() PUBLIC_KEY_ID = "Public Key ID" def setUp(self): patcher = patch("launchkey.clients.service.XiovJWTService") self._x_iov_jwt_service_patch = patcher.start().return_value self.addCleanup(patcher.stop) self._x_iov_jwt_service_patch.decrypt_jwe.return_value = '{"public_key_id":"' + self.PUBLIC_KEY_ID + '", "auth": null}' self._transport = MagicMock(spec=JOSETransport) self._service_client = ServiceClient(self._subject_id, self._transport) self._headers = {"X-IOV-JWT": "jwt", "Other Header": "jwt"} self._issuer_private_key = MagicMock() self._transport.loaded_issuer_private_keys = { self.PUBLIC_KEY_ID: self._issuer_private_key } patcher = patch( "launchkey.entities.validation.AuthorizationResponsePackageValidator.to_python" ) self._authorization_response_validator_patch = patcher.start() self.addCleanup(patcher.stop) self._authorization_response_validator_patch.return_value = MagicMock( spec=dict) patcher = patch("launchkey.clients.service.loads") self._service_client_loads_patch = patcher.start() self.addCleanup(patcher.stop) self._service_client_loads_patch.side_effect = json.loads patcher = patch("launchkey.entities.service.loads") self._service_entity_loads_patch = patcher.start() self.addCleanup(patcher.stop) self._service_entity_loads_patch.return_value = MagicMock(spec=dict) patcher = patch("launchkey.entities.validation.AuthorizeSSEValidator") self._authorize_sse_validator_patch = patcher.start() self.addCleanup(patcher.stop) self._authorize_sse_validator_patch.return_value = MagicMock(spec=dict) def test_webhook_session_end(self): body = dumps({ "service_user_hash": str(uuid4()), "api_time": str(datetime.utcnow())[:19].replace(" ", "T") + "Z" }) self._x_iov_jwt_service_patch.verify_jwt_request.return_value = body self.assertIsInstance( self._service_client.handle_webhook(body, self._headers), SessionEndRequest) def test_webhook_session_end_invalid_input(self): body = dumps({"service_user_hash": str(uuid4())}) self._x_iov_jwt_service_patch.verify_jwt_request.return_value = body with self.assertRaises(UnexpectedWebhookRequest): self.assertIsInstance( self._service_client.handle_webhook(body, self._headers), SessionEndRequest) def test_webhook_authorization_response_returns_authorization_response( self): self.assertIsInstance( self._service_client.handle_webhook(MagicMock(), self._headers), AuthorizationResponse) def test_calls_decrypt_jwe_request_with_expected_parameters(self): self._service_client.handle_webhook('body', self._headers, 'method', 'path') self._x_iov_jwt_service_patch.decrypt_jwe.assert_called_with( "body", self._headers, "method", "path") def test_handle_webhook_handles_jwt_validation_errors(self): self._x_iov_jwt_service_patch.decrypt_jwe.side_effect = XiovJWTValidationFailure with self.assertRaises(UnexpectedWebhookRequest): self._service_client.handle_webhook(MagicMock(), self._headers) def test_handle_webhook_session_end_requests_handles_data_validation_errors( self): self._authorize_sse_validator_patch.to_python.side_effect = Invalid body = dumps({"service_user_hash": str(uuid4())}) self._x_iov_jwt_service_patch.verify_jwt_request.return_value = body with self.assertRaises(UnexpectedWebhookRequest): self._service_client.handle_webhook(body, self._headers) def test_handle_webhook_auth_response_handles_json_loads_errors(self): self._transport.decrypt_response.return_value = '{"public_key_id":"' + self.PUBLIC_KEY_ID + '","auth":null}' self._service_entity_loads_patch.side_effect = ValueError with self.assertRaises(UnexpectedDeviceResponse): self._service_client.handle_webhook(MagicMock(), self._headers) def test_handle_webhook_auth_response_requests_handles_unexpected_key( self, ): self._x_iov_jwt_service_patch.decrypt_jwe.side_effect = UnexpectedKeyID with self.assertRaises(UnexpectedKeyID): self._service_client.handle_webhook(MagicMock(), self._headers) def test_handle_webhook_for_authorization_response_handles_jwe_decryption_errors( self): self._x_iov_jwt_service_patch.decrypt_jwe.side_effect = XiovJWTDecryptionFailure with self.assertRaises(UnableToDecryptWebhookRequest): self._service_client.handle_webhook(MagicMock(), self._headers) def test_handle_webhook_for_invalid_response_when_validating_auth_response( self): self._authorization_response_validator_patch.side_effect = Invalid with self.assertRaises(UnexpectedDeviceResponse): self._service_client.handle_webhook(MagicMock(), self._headers) def test_handle_webhook_for_response_without_auth_package_parsing_auth_response( self): self._x_iov_jwt_service_patch.decrypt_jwe.return_value = '{"public_key_id":"' + self.PUBLIC_KEY_ID + '"}' with self.assertRaises(UnexpectedAuthorizationResponse): self._service_client.handle_webhook(MagicMock(), self._headers)
class TestServiceClient(unittest.TestCase): def setUp(self): self._transport = MagicMock() self._response = APIResponse({}, {}, 200) self._transport.post.return_value = self._response self._transport.get.return_value = self._response self._transport.put.return_value = self._response self._transport.delete.return_value = self._response self._device_response = { "auth_request": str(uuid4()), "response": True, "device_id": str(uuid4()), "service_pins": ["1234", "3456", "5678"] } self._transport.loaded_issuer_private_key.decrypt.return_value = dumps( self._device_response) self._service_client = ServiceClient(uuid4(), self._transport) self._service_client._transport._verify_jwt_response = MagicMock() def test_authorize_success(self): self._response.data = {"auth_request": ANY} self._service_client.authorize(ANY, ANY, MagicMock(spec=AuthPolicy)) def test_authorize_invalid_policy_input(self): self._response.data = {"auth_request": ANY} with self.assertRaises(InvalidParameters): self._service_client.authorize(ANY, ANY, ANY) def test_authorize_unexpected_result(self): self._response.data = {MagicMock(spec=str): ANY} with self.assertRaises(UnexpectedAPIResponse): self._service_client.authorize(ANY) def test_authorize_invalid_params(self): self._transport.post.side_effect = LaunchKeyAPIException( { "error_code": "ARG-001", "error_detail": "" }, 400) with self.assertRaises(InvalidParameters): self._service_client.authorize(ANY) def test_authorize_invalid_policy(self): self._transport.post.side_effect = LaunchKeyAPIException( { "error_code": "SVC-002", "error_detail": "" }, 400) with self.assertRaises(InvalidPolicyInput): self._service_client.authorize(ANY) def test_authorize_policy_failure(self): self._transport.post.side_effect = LaunchKeyAPIException( { "error_code": "SVC-003", "error_detail": "" }, 400) with self.assertRaises(PolicyFailure): self._service_client.authorize(ANY) def test_authorize_entity_not_found(self): self._transport.post.side_effect = LaunchKeyAPIException({}, 404) with self.assertRaises(EntityNotFound): self._service_client.authorize(ANY) def test_authorize_rate_limited(self): self._transport.post.side_effect = LaunchKeyAPIException({}, 429) with self.assertRaises(RateLimited): self._service_client.authorize(ANY) @patch("launchkey.entities.service.b64decode") @patch("launchkey.entities.service.loads") @patch("launchkey.entities.service.AuthorizationResponsePackageValidator") def test_get_authorization_response_success( self, b64decode_patch, json_loads_patch, auth_response_package_validator_patch): b64decode_patch.return_value = MagicMock(spec=str) json_loads_patch.return_value = MagicMock(spec=dict) auth_response_package_validator_patch.return_value = MagicMock( spec=dict) public_key_id = str(uuid4()) self._service_client._transport.loaded_issuer_private_keys = { public_key_id: MagicMock() } self._response.data = { "auth": ANY, "service_user_hash": ANY, "user_push_id": ANY, "org_user_hash": ANY, "public_key_id": public_key_id } self.assertIsInstance( self._service_client.get_authorization_response(ANY), AuthorizationResponse) def test_get_authorization_response_unexpected_response(self): self._response.data = {MagicMock(spec=str): ANY} with self.assertRaises(UnexpectedAPIResponse): self._service_client.get_authorization_response(ANY) def test_get_authorization_response_no_response(self): self._response.status_code = 204 self.assertIsNone(self._service_client.get_authorization_response(ANY)) def test_get_authorization_response_invalid_params(self): self._transport.get.side_effect = LaunchKeyAPIException( { "error_code": "ARG-001", "error_detail": "" }, 400) with self.assertRaises(InvalidParameters): self._service_client.get_authorization_response(ANY) def test_get_authorization_response_timeout(self): self._transport.get.side_effect = LaunchKeyAPIException({}, 408) with self.assertRaises(RequestTimedOut): self._service_client.get_authorization_response(ANY) def test_session_start_success(self): self.assertIsNone(self._service_client.session_start(ANY, ANY)) def test_session_start_invalid_params(self): self._transport.post.side_effect = LaunchKeyAPIException( { "error_code": "ARG-001", "error_detail": "" }, 400) with self.assertRaises(InvalidParameters): self._service_client.session_start(ANY, ANY) def test_session_start_entity_not_found(self): self._transport.post.side_effect = LaunchKeyAPIException({}, 404) with self.assertRaises(EntityNotFound): self._service_client.session_start(ANY, ANY) def test_session_end_success(self): self.assertIsNone(self._service_client.session_end(ANY)) def test_session_end_invalid_params(self): self._transport.delete.side_effect = LaunchKeyAPIException( { "error_code": "ARG-001", "error_detail": "" }, 400) with self.assertRaises(InvalidParameters): self._service_client.session_end(ANY) def test_session_end_entity_not_found(self): self._transport.delete.side_effect = LaunchKeyAPIException({}, 404) with self.assertRaises(EntityNotFound): self._service_client.session_end(ANY) def test_webhook_session_end(self): request = dumps({ "service_user_hash": str(uuid4()), "api_time": str(datetime.utcnow())[:19].replace(" ", "T") + "Z" }) self.assertIsInstance( self._service_client.handle_webhook(request, ANY), SessionEndRequest) def test_webhook_session_end_invalid_input(self): request = dumps({"service_user_hash": str(uuid4())}) with self.assertRaises(UnexpectedAPIResponse): self.assertIsInstance( self._service_client.handle_webhook(request, ANY), SessionEndRequest) @patch("launchkey.entities.service.b64decode") @patch("launchkey.clients.service.loads") @patch("launchkey.entities.service.loads") @patch("launchkey.entities.validation.AuthorizeSSEValidator") @patch("launchkey.entities.service.AuthorizationResponsePackageValidator") def test_webhook_authorization_response( self, auth_response_package_validator_patch, auth_sse_validator_patch, json_loads_patch, json_loads_patch_2, b64decode_patch): b64decode_patch.return_value = MagicMock(spec=str) json_loads_patch.return_value = MagicMock(spec=dict) json_loads_patch_2.return_value = MagicMock(spec=dict) auth_sse_validator_patch.return_value = MagicMock(spec=dict) auth_response_package_validator_patch.return_value = MagicMock( spec=dict) self._transport.loaded_issuer_private_keys = { json_loads_patch_2().get(): MagicMock() } self.assertIsInstance( self._service_client.handle_webhook(MagicMock(), ANY), AuthorizationResponse)
class TestHandleWebhook(unittest.TestCase): _subject_id = uuid4() PUBLIC_KEY_ID = "Public Key ID" def setUp(self): self._transport = MagicMock(spec=JOSETransport) self._transport.decrypt_response.return_value = '{"public_key_id":"' + self.PUBLIC_KEY_ID + '", "auth": null}' self._service_client = ServiceClient(self._subject_id, self._transport) self._headers = {"X-IOV-JWT": "jwt", "Other Header": "jwt"} self._issuer_private_key = MagicMock() self._transport.loaded_issuer_private_keys = {self.PUBLIC_KEY_ID: self._issuer_private_key} patcher = patch("launchkey.entities.validation.AuthorizationResponsePackageValidator.to_python") self._authorization_response_validator_patch = patcher.start() self._authorization_response_validator_patch.return_value = MagicMock(spec=dict) patcher = patch("launchkey.clients.service.loads") self._service_client_loads_patch = patcher.start() self.addCleanup(patcher.stop) self._service_client_loads_patch.side_effect = json.loads patcher = patch("launchkey.entities.service.loads") self._service_entity_loads_patch = patcher.start() self.addCleanup(patcher.stop) self._service_entity_loads_patch.return_value = MagicMock(spec=dict) patcher = patch("launchkey.entities.validation.AuthorizeSSEValidator") self._authorize_sse_validator_patch = patcher.start() self.addCleanup(patcher.stop) self._authorize_sse_validator_patch.return_value = MagicMock(spec=dict) def test_webhook_session_end(self): request = dumps({"service_user_hash": str(uuid4()), "api_time": str(datetime.utcnow())[:19].replace(" ", "T") + "Z"}) self.assertIsInstance(self._service_client.handle_webhook(request, self._headers), SessionEndRequest) def test_webhook_session_end_invalid_input(self): request = dumps({"service_user_hash": str(uuid4())}) with self.assertRaises(UnexpectedWebhookRequest): self.assertIsInstance(self._service_client.handle_webhook(request, self._headers), SessionEndRequest) def test_webhook_authorization_response_returns_authorization_response(self): self.assertIsInstance(self._service_client.handle_webhook(MagicMock(), self._headers), AuthorizationResponse) def test_calls_verify_jwt_request_with_expected_parameters(self): self._headers['X-IOV-JWT'] = 'compact.jwt.string' self._service_client.handle_webhook('body', self._headers, 'method', 'path') self._transport.verify_jwt_request.assert_called_with("compact.jwt.string", 'svc:' + str(self._subject_id), 'method', 'path', 'body') def test_handle_webhook_handles_jwt_validation_errors(self): self._transport.verify_jwt_request.side_effect = InvalidJWTResponse with self.assertRaises(UnexpectedWebhookRequest): self._service_client.handle_webhook(MagicMock(), self._headers) def test_handle_webhook_session_end_requests_handles_data_validation_errors(self): self._authorize_sse_validator_patch.to_python.side_effect = Invalid with self.assertRaises(UnexpectedWebhookRequest): self._service_client.handle_webhook(dumps({"service_user_hash": str(uuid4())}), self._headers) def test_handle_webhook_auth_response_handles_json_loads_errors(self): self._transport.decrypt_response.return_value = '{"public_key_id":"' + self.PUBLIC_KEY_ID + '","auth":null}' self._service_entity_loads_patch.side_effect = ValueError with self.assertRaises(UnexpectedDeviceResponse): self._service_client.handle_webhook(MagicMock(), self._headers) def test_handle_webhook_auth_response_requests_handles_unexpected_key(self,): self._transport.decrypt_response.side_effect = UnexpectedKeyID with self.assertRaises(UnexpectedKeyID): self._service_client.handle_webhook(MagicMock(), self._headers) def test_handle_webhook_for_authorization_response_handles_jwe_decryption_errors(self): self._transport.decrypt_response.side_effect = JWKESTException with self.assertRaises(UnableToDecryptWebhookRequest): self._service_client.handle_webhook(MagicMock(), self._headers) def test_handle_webhook_for_invalid_response_when_validating_auth_response(self): self._authorization_response_validator_patch.side_effect = Invalid with self.assertRaises(UnexpectedDeviceResponse): self._service_client.handle_webhook(MagicMock(), self._headers) def test_handle_webhook_for_response_without_auth_package_parsing_auth_response(self): self._transport.decrypt_response.return_value = '{"public_key_id":"' + self.PUBLIC_KEY_ID + '"}' with self.assertRaises(UnexpectedAuthorizationResponse): self._service_client.handle_webhook(MagicMock(), self._headers) def test_no_jwt_header_raises_webhook_authorization_error(self): del self._headers['X-IOV-JWT'] with self.assertRaises(WebhookAuthorizationError): self._service_client.handle_webhook(MagicMock(), self._headers)