Ejemplo n.º 1
0
    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.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 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)
Ejemplo n.º 3
0
 def make_service_client(self, service_id):
     """
     Retrieves a client to make service calls.
     :param service_id: Service id
     :return: launchkey.clients.ServiceClient
     """
     return ServiceClient(service_id, self._transport)
Ejemplo n.º 4
0
 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 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_id = uuid4()
     self._issuer = "svc:{}".format(self._service_id)
     self._service_client = ServiceClient(self._service_id, self._transport)
     self._service_client._transport._verify_jwt_response = MagicMock()
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
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_id = uuid4()
        self._issuer = "svc:{}".format(self._service_id)
        self._service_client = ServiceClient(self._service_id, self._transport)
        self._service_client._transport._verify_jwt_response = MagicMock()

    def test_authorize_calls_authorization_request(self):
        policy = AuthPolicy()
        auth_response = AuthorizationRequest(str(uuid4()), None)
        self._service_client.authorization_request = MagicMock(
            return_value=auth_response)
        self._service_client.authorize('user', 'context', policy, 'title', 30,
                                       'push_title', 'push_body')
        self._service_client.authorization_request.assert_called_once_with(
            'user', 'context', policy, 'title', 30, 'push_title', 'push_body')

    def test_authorize_returns_auth_request_id_from_authorization_request_response(
            self):
        expected = str(uuid4())
        self._response.data = {"auth_request": expected}
        actual = self._service_client.authorize('user', 'context',
                                                AuthPolicy())
        self.assertEqual(actual, expected)

    def test_authorization_request_success(self):
        self._response.data = {"auth_request": "value"}
        policy = MagicMock(spec=AuthPolicy)
        policy.get_policy.return_value = "policy"
        self._service_client.authorization_request("user", "context", policy)
        self._transport.post.assert_called_once_with('/service/v3/auths',
                                                     self._issuer,
                                                     username="******",
                                                     context="context",
                                                     policy="policy")

    def test_authorization_request_response_has_auth_request(self):
        self._response.data = {"auth_request": "expected value"}
        self.assertEqual(
            'expected value',
            self._service_client.authorization_request(ANY).auth_request)

    def test_authorization_request_response_has_push_package(self):
        self._response.data = {
            "auth_request": "auth",
            "push_package": "expected package"
        }
        self.assertEqual(
            'expected package',
            self._service_client.authorization_request(ANY).push_package)

    def test_authorization_request_invalid_policy_input(self):
        self._response.data = {"auth_request": ANY}
        with self.assertRaises(InvalidParameters):
            self._service_client.authorization_request(ANY, ANY, ANY)

    def test_authorization_request_unexpected_result(self):
        self._response.data = {MagicMock(spec=str): ANY}
        with self.assertRaises(UnexpectedAPIResponse):
            self._service_client.authorization_request(ANY)

    def test_authorization_request_invalid_params(self):
        self._transport.post.side_effect = LaunchKeyAPIException(
            {
                "error_code": "ARG-001",
                "error_detail": ""
            }, 400)
        with self.assertRaises(InvalidParameters):
            self._service_client.authorization_request(ANY)

    def test_authorization_request_invalid_policy(self):
        self._transport.post.side_effect = LaunchKeyAPIException(
            {
                "error_code": "SVC-002",
                "error_detail": ""
            }, 400)
        with self.assertRaises(InvalidPolicyInput):
            self._service_client.authorization_request(ANY)

    def test_authorization_request_policy_failure(self):
        self._transport.post.side_effect = LaunchKeyAPIException(
            {
                "error_code": "SVC-003",
                "error_detail": ""
            }, 400)
        with self.assertRaises(PolicyFailure):
            self._service_client.authorization_request(ANY)

    def test_authorization_request_entity_not_found(self):
        self._transport.post.side_effect = LaunchKeyAPIException({}, 404)
        with self.assertRaises(EntityNotFound):
            self._service_client.authorization_request(ANY)

    def test_authorization_request_rate_limited(self):
        self._transport.post.side_effect = LaunchKeyAPIException({}, 429)
        with self.assertRaises(RateLimited):
            self._service_client.authorization_request(ANY)

    def test_authorization_request_default(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request("my_user")
        self._transport.post.assert_called_with(ANY, ANY, username="******")

    def test_authorization_request_context(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request(
            "my_user", context="Here's some context!")
        self._transport.post.assert_called_with(ANY,
                                                ANY,
                                                username="******",
                                                context="Here's some context!")

    def test_authorization_request_title(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request("my_user",
                                                   title="Here's a title!")
        self._transport.post.assert_called_with(ANY,
                                                ANY,
                                                username="******",
                                                title="Here's a title!")

    def test_authorization_request_push_title(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request("my_user",
                                                   push_title="A Push Title")
        self._transport.post.assert_called_with(ANY,
                                                ANY,
                                                username="******",
                                                push_title="A Push Title")

    def test_authorization_request_push_body(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request("my_user",
                                                   push_body="Push Body")
        self._transport.post.assert_called_with(ANY,
                                                ANY,
                                                username="******",
                                                push_body="Push Body")

    def test_authorization_request_ttl(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request("my_user", ttl=336)
        self._transport.post.assert_called_with(ANY,
                                                ANY,
                                                username="******",
                                                ttl=336)

    def test_authorization_request_denial_reasons_as_list(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request(
            "my_user",
            denial_reasons=[
                DenialReason('fraud', 'Fraud Reason', True),
                DenialReason('not', 'Not Fraud Reason', False)
            ])
        self._transport.post.assert_called_with(ANY,
                                                ANY,
                                                username="******",
                                                denial_reasons=[{
                                                    "id": 'fraud',
                                                    "reason": 'Fraud Reason',
                                                    "fraud": True
                                                }, {
                                                    "id": 'not',
                                                    "reason":
                                                    'Not Fraud Reason',
                                                    "fraud": False
                                                }])

    def test_authorization_request_denial_reasons_as_set(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request(
            "my_user",
            denial_reasons={
                DenialReason('fraud', 'Fraud Reason', True),
                DenialReason('not', 'Not Fraud Reason', False)
            })
        denial_reasons = self._transport.post.call_args[1]['denial_reasons']
        self.assertEqual(len(denial_reasons), 2)
        self.assertIn({
            "id": 'fraud',
            "reason": 'Fraud Reason',
            "fraud": True
        }, denial_reasons)
        self.assertIn(
            {
                "id": 'not',
                "reason": 'Not Fraud Reason',
                "fraud": False
            }, denial_reasons)

    @data("e6e809ab-9e83-47a2-924a-64ae3d424a45", True, False,
          {"Test": "Data"}, DenialReason(1, 2, 3))
    def test_authorization_request_denial_reasons_invalid_input(self, reasons):
        with self.assertRaises(InvalidParameters):
            self._service_client.authorization_request("my_user",
                                                       denial_reasons=reasons)

    @data("e6e809ab-9e83-47a2-924a-64ae3d424a45", True, False,
          {"Test": "Data"})
    def test_authorization_request_denial_reasons_invalid_reason(self, reason):
        with self.assertRaises(InvalidParameters):
            self._service_client.authorization_request("my_user",
                                                       denial_reasons=[reason])

    @patch("launchkey.entities.service.loads")
    @patch("launchkey.entities.service.AuthorizationResponsePackageValidator")
    def test_get_authorization_response_success(
            self, json_loads_patch, auth_response_package_validator_patch):
        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
        }
        actual = self._service_client.get_authorization_response(
            "auth-request-id")
        self._transport.get.assert_called_once_with(
            "/service/v3/auths/auth-request-id", self._issuer)
        self.assertIsInstance(actual, 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_response_exists(self):
        self._transport.get.side_effect = LaunchKeyAPIException(
            {
                "error_code": "SVC-006",
                "error_detail": ""
            }, 400)
        with self.assertRaises(AuthorizationResponseExists):
            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_cancel_authorization_request_success(self):
        self._service_client.cancel_authorization_request("auth-request-id")
        self._transport.delete.assert_called_once_with(
            "/service/v3/auths/auth-request-id", self._issuer)

    def test_cancel_authorization_request_invalid_params(self):
        self._transport.delete.side_effect = LaunchKeyAPIException(
            {
                "error_code": "ARG-001",
                "error_detail": ""
            }, 400)
        with self.assertRaises(InvalidParameters):
            self._service_client.cancel_authorization_request(
                "auth-request-id")

    def test_cancel_authorization_request_authorization_response_exists(self):
        self._transport.delete.side_effect = LaunchKeyAPIException(
            {
                "error_code": "SVC-006",
                "error_detail": ""
            }, 400)
        with self.assertRaises(AuthorizationResponseExists):
            self._service_client.cancel_authorization_request(
                "auth-request-id")

    def test_cancel_authorization_request_authorization_request_canceled(self):
        self._transport.delete.side_effect = LaunchKeyAPIException(
            {
                "error_code": "SVC-007",
                "error_detail": ""
            }, 400)
        with self.assertRaises(AuthorizationRequestCanceled):
            self._service_client.cancel_authorization_request(
                "auth-request-id")

    def test_cancel_authorization_request_invalid_params(self):
        self._transport.delete.side_effect = LaunchKeyAPIException(
            {
                "error_code": "ARG-001",
                "error_detail": ""
            }, 400)
        with self.assertRaises(InvalidParameters):
            self._service_client.cancel_authorization_request(
                "auth-request-id")

    def test_session_start_success(self):
        self._service_client.session_start("user-id", "auth-request-id")
        self._transport.post.assert_called_once_with(
            "/service/v3/sessions",
            self._issuer,
            username="******",
            auth_request="auth-request-id")

    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._service_client.session_end("user-id")
        self._transport.delete.assert_called_once_with("/service/v3/sessions",
                                                       self._issuer,
                                                       username="******")

    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)
Ejemplo n.º 9
0
 def make_service_client(self):
     """
     Retrieves a client to make service calls.
     :return: launchkey.clients.ServiceClient
     """
     return ServiceClient(self._issuer_id, self._transport)
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)
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_id = uuid4()
        self._issuer = "svc:{}".format(self._service_id)
        self._service_client = ServiceClient(self._service_id, self._transport)
        self._service_client._transport._verify_jwt_response = MagicMock()

    def test_authorize_calls_authorization_request(self):
        policy = AuthPolicy()
        auth_response = AuthorizationRequest(str(uuid4()), None)
        self._service_client.authorization_request = MagicMock(return_value=auth_response)
        self._service_client.authorize('user', 'context', policy, 'title', 30,
                                       'push_title', 'push_body')
        self._service_client.authorization_request.assert_called_once_with(
            'user', 'context', policy, 'title', 30, 'push_title', 'push_body')

    def test_authorize_returns_auth_request_id_from_authorization_request_response(self):
        expected = str(uuid4())
        self._response.data = {"auth_request": expected}
        actual = self._service_client.authorize('user', 'context', AuthPolicy())
        self.assertEqual(actual, expected)

    def test_authorization_request_success(self):
        self._response.data = {"auth_request": "value"}
        policy = MagicMock(spec=AuthPolicy)
        policy.get_policy.return_value = "policy"
        self._service_client.authorization_request("user", "context", policy)
        self._transport.post.assert_called_once_with(
            '/service/v3/auths', self._issuer, username="******",
            context="context", policy="policy"
        )

    def test_authorization_request_response_has_auth_request(self):
        self._response.data = {"auth_request": "expected value"}
        self.assertEqual('expected value', self._service_client.authorization_request(ANY).auth_request)

    def test_authorization_request_response_has_push_package(self):
        self._response.data = {"auth_request": "auth", "push_package": "expected package"}
        self.assertEqual('expected package', self._service_client.authorization_request(ANY).push_package)

    def test_authorization_request_invalid_policy_input(self):
        self._response.data = {"auth_request": ANY}
        with self.assertRaises(InvalidParameters):
            self._service_client.authorization_request(ANY, ANY, ANY)

    def test_authorization_request_unexpected_result(self):
        self._response.data = {MagicMock(spec=str): ANY}
        with self.assertRaises(UnexpectedAPIResponse):
            self._service_client.authorization_request(ANY)

    def test_authorization_request_invalid_params(self):
        self._transport.post.side_effect = LaunchKeyAPIException({"error_code": "ARG-001", "error_detail": ""}, 400)
        with self.assertRaises(InvalidParameters):
            self._service_client.authorization_request(ANY)

    def test_authorization_request_invalid_policy(self):
        self._transport.post.side_effect = LaunchKeyAPIException({"error_code": "SVC-002", "error_detail": ""}, 400)
        with self.assertRaises(InvalidPolicyInput):
            self._service_client.authorization_request(ANY)

    def test_authorization_request_policy_failure(self):
        self._transport.post.side_effect = LaunchKeyAPIException({"error_code": "SVC-003", "error_detail": ""}, 400)
        with self.assertRaises(PolicyFailure):
            self._service_client.authorization_request(ANY)

    def test_authorization_request_entity_not_found(self):
        self._transport.post.side_effect = LaunchKeyAPIException({}, 404)
        with self.assertRaises(EntityNotFound):
            self._service_client.authorization_request(ANY)

    def test_authorization_request_rate_limited(self):
        self._transport.post.side_effect = LaunchKeyAPIException({}, 429)
        with self.assertRaises(RateLimited):
            self._service_client.authorization_request(ANY)

    def test_authorization_request_default(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request("my_user")
        self._transport.post.assert_called_with(ANY, ANY, username="******")

    def test_authorization_request_context(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request("my_user", context="Here's some context!")
        self._transport.post.assert_called_with(ANY, ANY, username="******", context="Here's some context!")

    def test_authorization_request_title(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request("my_user", title="Here's a title!")
        self._transport.post.assert_called_with(ANY, ANY, username="******", title="Here's a title!")

    def test_authorization_request_push_title(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request("my_user",
                                                   push_title="A Push Title")
        self._transport.post.assert_called_with(ANY, ANY, username="******",
                                                push_title="A Push Title")

    def test_authorization_request_push_body(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request("my_user",
                                                   push_body="Push Body")
        self._transport.post.assert_called_with(ANY, ANY, username="******",
                                                push_body="Push Body")

    def test_authorization_request_ttl(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request("my_user", ttl=336)
        self._transport.post.assert_called_with(ANY, ANY, username="******", ttl=336)

    def test_authorization_request_denial_reasons_as_list(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request("my_user", denial_reasons=[
            DenialReason('fraud', 'Fraud Reason', True),
            DenialReason('not', 'Not Fraud Reason', False)
        ])
        self._transport.post.assert_called_with(
            ANY, ANY, username="******",
            denial_reasons=[
                {"id": 'fraud', "reason": 'Fraud Reason', "fraud": True},
                {"id": 'not', "reason": 'Not Fraud Reason', "fraud": False}
            ]
        )

    def test_authorization_request_denial_reasons_as_set(self):
        self._response.data = {"auth_request": "expected value"}
        self._service_client.authorization_request("my_user", denial_reasons={
            DenialReason('fraud', 'Fraud Reason', True),
            DenialReason('not', 'Not Fraud Reason', False)
        })
        denial_reasons = self._transport.post.call_args[1]['denial_reasons']
        self.assertEqual(len(denial_reasons), 2)
        self.assertIn(
            {"id": 'fraud', "reason": 'Fraud Reason', "fraud": True},
            denial_reasons
        )
        self.assertIn(
            {"id": 'not', "reason": 'Not Fraud Reason', "fraud": False},
            denial_reasons
        )

    @data(
        "e6e809ab-9e83-47a2-924a-64ae3d424a45",
        True,
        False,
        {"Test": "Data"},
        DenialReason(1, 2, 3)
    )
    def test_authorization_request_denial_reasons_invalid_input(self, reasons):
        with self.assertRaises(InvalidParameters):
            self._service_client.authorization_request(
                "my_user",
                denial_reasons=reasons
            )

    @data(
        "e6e809ab-9e83-47a2-924a-64ae3d424a45",
        True,
        False,
        {"Test": "Data"}
    )
    def test_authorization_request_denial_reasons_invalid_reason(self,
                                                                 reason):
        with self.assertRaises(InvalidParameters):
            self._service_client.authorization_request(
                "my_user",
                denial_reasons=[reason]
            )

    @patch("launchkey.entities.service.loads")
    @patch("launchkey.entities.service.AuthorizationResponsePackageValidator")
    def test_get_authorization_response_success(
            self, json_loads_patch,
            auth_response_package_validator_patch):
        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
        }
        actual = self._service_client.get_authorization_response(
            "auth-request-id")
        self._transport.get.assert_called_once_with(
            "/service/v3/auths/auth-request-id", self._issuer)
        self.assertIsInstance(actual, 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._service_client.session_start("user-id", "auth-request-id")
        self._transport.post.assert_called_once_with(
            "/service/v3/sessions", self._issuer, username="******",
            auth_request="auth-request-id")

    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._service_client.session_end("user-id")
        self._transport.delete.assert_called_once_with(
            "/service/v3/sessions", self._issuer, username="******")

    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)