def _handle_error_response(self, response: httpclient.HTTPResponse):
        try:
            parsed_body = ET.fromstring(response.body)

            if EbxmlErrorEnvelope.is_ebxml_error(parsed_body):
                _, parsed_response = ebxml_handler.handle_ebxml_error(response.code,
                                                                      response.headers,
                                                                      response.body)
                logger.warning('Received ebxml errors from Spine. {HTTPStatus} {Errors}',
                               fparams={'HTTPStatus': response.code, 'Errors': parsed_response})
            elif SOAPFault.is_soap_fault(parsed_body):
                _, parsed_response, _ = handle_soap_error(response.code,
                                                          response.headers,
                                                          response.body)
                logger.warning('Received soap errors from Spine. {HTTPStatus} {Errors}',
                               fparams={'HTTPStatus': response.code, 'Errors': parsed_response})
            else:
                logger.warning("Received an unexpected response from Spine",
                               fparams={'HTTPStatus': response.code})
                parsed_response = "Didn't get expected response from Spine"

        except ET.ParseError:
            logger.exception('Unable to parse response from Spine.')
            parsed_response = 'Unable to handle response returned from Spine'

        return 500, parsed_response, None
Esempio n. 2
0
    def _handle_error_response(self, response: httpclient.HTTPResponse,
                               num_of_retries: int,
                               retries_remaining: List[int]):
        try:
            parsed_body = ET.fromstring(response.body)

            if EbxmlErrorEnvelope.is_ebxml_error(parsed_body):
                _, parsed_response = ebxml_handler.handle_ebxml_error(
                    response.code, response.headers, response.body)
                logger.warning(
                    '0007',
                    'Received ebxml errors from Spine. {HTTPStatus} {Errors}',
                    {
                        'HTTPStatus': response.code,
                        'Errors': parsed_response
                    })

            elif SOAPFault.is_soap_fault(parsed_body):
                _, parsed_response, soap_fault_codes = handle_soap_error(
                    response.code, response.headers, response.body)
                logger.warning(
                    '0008',
                    'Received soap errors from Spine. {HTTPStatus} {Errors}', {
                        'HTTPStatus': response.code,
                        'Errors': parsed_response
                    })

                if SOAPFault.is_soap_fault_retriable(soap_fault_codes):
                    logger.warning(
                        "0015",
                        "A retriable error was encountered {error} {retries_remaining} "
                        "{max_retries}", {
                            "error": parsed_response,
                            "retries_remaining": retries_remaining[0],
                            "max_retries": num_of_retries
                        })
                    if retries_remaining[0] <= 0:
                        # exceeded the number of retries so return the SOAP error response
                        logger.error(
                            "0016",
                            "A request has exceeded the maximum number of retries, {max_retries} "
                            "retries", {"max_retries": num_of_retries})
                    else:
                        raise _NeedToRetryException()
            else:
                logger.warning('0017',
                               "Received an unexpected response from Spine",
                               {'HTTPStatus': response.code})
                parsed_response = "Didn't get expected response from Spine"

        except ET.ParseError as pe:
            logger.warning('0010',
                           'Unable to parse response from Spine. {Exception}',
                           {'Exception': repr(pe)})
            parsed_response = 'Unable to handle response returned from Spine'

        return 500, parsed_response, None
Esempio n. 3
0
    def test_from_string_single(self):
        message = file_utilities.get_file_string(
            self.message_dir / 'ebxml_response_error_single.xml')
        ebxml_error: EbxmlErrorEnvelope = EbxmlErrorEnvelope.from_string(
            message)
        self.assertEqual(len(ebxml_error.errors), 1)

        self.assertEqual(ebxml_error.errors[0]['codeContext'],
                         'urn:oasis:names:tc:ebxml-msg:service:errors')
        self.assertEqual(ebxml_error.errors[0]['errorCode'],
                         'ValueNotRecognized')
        self.assertEqual(ebxml_error.errors[0]['severity'], 'Error')
        self.assertEqual(ebxml_error.errors[0]['Description'],
                         '501319:Unknown eb:CPAId')
def handle_ebxml_error(code: int, headers: Dict,
                       body: AnyStr) -> Tuple[int, Optional[AnyStr]]:
    """
    Analyzes response from MHS and returns result of interpretation to external client
    Normally MHS doesn't return HTTP code 500 in case of ebXML error occurred. HTTP 200 will be returned instead
    with content-type text/xml and body represented as XML and having following structure:

    <?xml version="1.0" encoding="utf-8"?>
    <SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" xmlns:eb="http://www.oasis-open.org/committees/ebxml-msg/schema/msg-header-2_0.xsd">
        <SOAP:Header>
            <eb:MessageHeader SOAP:mustUnderstand="1" eb:version="2.0">
                <eb:From>
                    <eb:PartyId eb:type="urn:nhs:names:partyType:ocs+serviceInstance">YEA-801248</eb:PartyId>
                </eb:From>
                <eb:To>
                    <eb:PartyId eb:type="urn:nhs:names:partyType:ocs+serviceInstance">RHM-810292</eb:PartyId>
                </eb:To>
                <eb:CPAId>S2001919A2011852</eb:CPAId>
                <eb:ConversationId>19D02203-3CBE-11E3-9D44-9D223D2F4DB0</eb:ConversationId>
                <eb:Service>urn:oasis:names:tc:ebxml-msg:service</eb:Service>
                <eb:Action>MessageError</eb:Action>
                <eb:MessageData>
                    <eb:MessageId>97111C1C-48B8-B2FA-DE13-B64B2ADEB391</eb:MessageId>
                    <eb:Timestamp>2013-10-24T15:08:07</eb:Timestamp>
                    <eb:RefToMessageId>19D02203-3CBE-11E3-9D44-9D223D2F4DB0</eb:RefToMessageId>
                </eb:MessageData>
            </eb:MessageHeader>
            <eb:ErrorList SOAP:mustUnderstand="1" eb:highestSeverity="Error" eb:version="2.0">
                <eb:Error eb:codeContext="urn:oasis:names:tc:ebxml-msg:service:errors" eb:errorCode="ValueNotRecognized" eb:severity="Error">
                    <eb:Description xml:lang="en-GB">501319:Unknown eb:CPAId</eb:Description>
                </eb:Error>
            </eb:ErrorList>
        </SOAP:Header>
        <SOAP:Body/>
    </SOAP:Envelope>

    Function checks for HTTP code 200, content-type text/xml and body for the structure as above.
    In case there is not HTTP code 200 passed in, `code` and `body` will be returned back.
    In case body isn't structured as ebXML error message passed `code` and `body` will be returned back.
    In case content-type is not presented or is not text/xml `ValueError` exception should be raised.
    In case the response actually is ebXML error all the error details like severity, description, error code and
    error context will be logged according to logging policy with `errorType` equal to `ebxml_error`.

    :param code: HTTP response code
    :param headers: HTTP response headers
    :param body: HTTP response body
    :return: Response to external client represented as HTTP status code and body
    """
    if not body:
        logger.info(
            '0003',
            "HTTP 200 success response received with empty body, so can assume this isn't an ebXML error."
        )
        return code, body

    if 'Content-Type' not in headers:
        raise ValueError(
            'No Content-Type header in Spine response, response cannot be handled!'
        )

    if headers['Content-Type'] != 'text/xml':
        raise ValueError('Unexpected Content-Type {}!'.format(
            headers['Content-Type']))

    parsed_body = ET.fromstring(body)

    if not EbxmlErrorEnvelope.is_ebxml_error(parsed_body):
        logger.info('0004', 'Not ebXML error.')
        return code, body

    ebxml_error_envelope: EbxmlErrorEnvelope = EbxmlErrorEnvelope.from_string(
        body)

    errors_text = ''
    for idx, error_fields in enumerate(ebxml_error_envelope.errors):
        all_fields = {**error_fields, **ERROR_RESPONSE_DEFAULTS}
        errors_text += '{}: {}\n'.format(
            idx, ' '.join([f'{k}={v}' for k, v in all_fields.items()]))
        logger.error(
            '0005', 'ebXML error returned: {}'.format(' '.join(
                f'{{{i}}}' for i in all_fields.keys())), all_fields)

    return 500, f'Error(s) received from Spine. Contact system administrator.\n{errors_text}'
Esempio n. 5
0
 def test_ebxml_error_empty(self):
     self.assertFalse(EbxmlErrorEnvelope.is_ebxml_error(None))
Esempio n. 6
0
 def test_ebxml_error_multiple(self):
     message = file_utilities.get_file_string(
         self.message_dir / 'ebxml_response_error_multiple.xml')
     self.assertTrue(
         EbxmlErrorEnvelope.is_ebxml_error(ElementTree.fromstring(message)))
Esempio n. 7
0
 def test_is_ebxml_negative(self):
     message = file_utilities.get_file_string(self.message_dir /
                                              'ebxml_header.xml')
     self.assertFalse(
         EbxmlErrorEnvelope.is_ebxml_error(ElementTree.fromstring(message)))