Exemple #1
0
    def handle(self, cid, url_match, channel_item, wsgi_environ, raw_request, worker_store, simple_io_config, post_data,
            path_info, soap_action, channel_type=CHANNEL.HTTP_SOAP, _response_404=response_404):
        """ Create a new instance of a service and invoke it.
        """
        service, is_active = self.server.service_store.new_instance(channel_item.service_impl_name)
        if not is_active:
            logger.warn('Could not invoke an inactive service:`%s`, cid:`%s`', service.get_name(), cid)
            raise NotFound(cid, _response_404.format(cid, path_info, soap_action))

        if channel_item.merge_url_params_req:
            channel_params = self.create_channel_params(url_match, channel_item,
                wsgi_environ, raw_request, post_data)
        else:
            channel_params = None

        # If caching is configured for this channel, we need to first check if there is no response already
        if channel_item['cache_type']:
            cache_key, response = self.get_response_from_cache(service, raw_request, channel_item, channel_params, wsgi_environ)
            if response:
                return response

        # No cache for this channel or no cached response, invoke the service then.
        response = service.update_handle(self._set_response_data, service, raw_request,
            channel_type, channel_item.data_format, channel_item.transport, self.server, worker_store.broker_client,
            worker_store, cid, simple_io_config, wsgi_environ=wsgi_environ,
            url_match=url_match, channel_item=channel_item, channel_params=channel_params,
            merge_channel_params=channel_item.merge_url_params_req,
            params_priority=channel_item.params_pri)

        # Cache the response if needed (cache_key was already created on return from get_response_from_cache)
        if channel_item['cache_type']:
            self.set_response_in_cache(channel_item, cache_key, response)

        # Having used the cache or not, we can return the response now
        return response
Exemple #2
0
    def validate_input(self):
        if not asbool(self.server.fs_server_config.apispec.pub_enabled):

            # Note that we are using the same format that regular 404 does
            raise NotFound(
                self.cid, '[{}] Unknown URL:[{}] or SOAP action:[]'.format(
                    self.cid,
                    self.wsgi_environ['zato.channel_item']['url_path']))
Exemple #3
0
    def init(self, cid, path_info, request, headers, transport, data_format):

        if transport == 'soap':
            # HTTP headers are all uppercased at this point.
            soap_action = headers.get('HTTP_SOAPACTION')

            if not soap_action:
                raise BadRequest(cid,
                                 'Client did not send the SOAPAction header')

            # SOAP clients may send an empty header, i.e. SOAPAction: "",
            # as opposed to not sending the header at all.
            soap_action = soap_action.lstrip('"').rstrip('"')

            if not soap_action:
                raise BadRequest(cid, 'Client sent an empty SOAPAction header')
        else:
            soap_action = ''

        _soap_actions = self.http_soap.getall(path_info)

        for _soap_action_info in _soap_actions:

            # TODO: Remove the call to .keys() when this pull request is merged in
            #       https://github.com/dsc/bunch/pull/4
            if soap_action in _soap_action_info.keys():
                service_info = _soap_action_info[soap_action]
                break
        else:
            msg = '[{0}] Could not find the service config for URL:[{1}], SOAP action:[{2}]'.format(
                cid, path_info, soap_action)
            logger.warn(msg)
            raise NotFound(cid, msg)

        logger.debug('[{0}] impl_name:[{1}]'.format(cid,
                                                    service_info.impl_name))

        if (logger.isEnabledFor(TRACE1)):
            buff = StringIO()
            pprint(self.server.service_store.services, stream=buff)
            logger.log(
                TRACE1, '[{0}] service_store.services:[{1}]'.format(
                    cid, buff.getvalue()))
            buff.close()

        self.server.service_store.service_data(
            service_info.impl_name
        )  # TODO - check if this call is needed at all?

        return service_info
Exemple #4
0
    def handle(self,
               cid,
               url_match,
               channel_item,
               wsgi_environ,
               raw_request,
               worker_store,
               simple_io_config,
               post_data,
               path_info,
               soap_action,
               channel_type=CHANNEL.HTTP_SOAP,
               _response_404=response_404):
        """ Create a new instance of a service and invoke it.
        """
        service, is_active = self.server.service_store.new_instance(
            channel_item.service_impl_name)
        if not is_active:
            logger.warn('Could not invoke an inactive service:`%s`, cid:`%s`',
                        service.get_name(), cid)
            raise NotFound(cid,
                           _response_404.format(cid, path_info, soap_action))

        if channel_item.merge_url_params_req:
            channel_params = self.create_channel_params(
                url_match, channel_item, wsgi_environ, raw_request, post_data)
        else:
            channel_params = None

        response = service.update_handle(
            self._set_response_data,
            service,
            raw_request,
            channel_type,
            channel_item.data_format,
            channel_item.transport,
            self.server,
            worker_store.broker_client,
            worker_store,
            cid,
            simple_io_config,
            wsgi_environ=wsgi_environ,
            url_match=url_match,
            channel_item=channel_item,
            channel_params=channel_params,
            merge_channel_params=channel_item.merge_url_params_req,
            params_priority=channel_item.params_pri)

        return response
    def dispatch(self, cid, req_timestamp, wsgi_environ, worker_store, _status_response=status_response,
        no_url_match=(None, False), _response_404=response_404, _has_debug=_has_debug,
        _http_soap_action='HTTP_SOAPACTION', _stringio=StringIO, _gzipfile=GzipFile):
        """ Base method for dispatching incoming HTTP/SOAP messages. If the security
        configuration is one of the technical account or HTTP basic auth,
        the security validation is being performed. Otherwise, that step
        is postponed until a concrete transport-specific handler is invoked.
        """
        # Needed in later steps
        path_info = wsgi_environ['PATH_INFO'].decode('utf-8')

        if _http_soap_action in wsgi_environ:
            soap_action = self._handle_quotes_soap_action(wsgi_environ[_http_soap_action])
        else:
            soap_action = ''

        # Can we recognize this combination of URL path and SOAP action at all?
        # This gives us the URL info and security data - but note that here
        # we still haven't validated credentials, only matched the URL.
        # Credentials are checked in a call to self.url_data.check_security
        url_match, channel_item = self.url_data.match(path_info, soap_action, bool(soap_action))

        if _has_debug and channel_item:
            logger.debug('url_match:`%r`, channel_item:`%r`', url_match, sorted(channel_item.items()))

        # This is needed in parallel.py's on_wsgi_request
        wsgi_environ['zato.channel_item'] = channel_item

        payload = wsgi_environ['wsgi.input'].read()

        # OK, we can possibly handle it
        if url_match not in no_url_match:

            try:

                # Raise 404 if the channel is inactive
                if not channel_item['is_active']:
                    logger.warn('url_data:`%s` is not active, raising NotFound', sorted(url_match.items()))
                    raise NotFound(cid, 'Channel inactive')

                expected_method = channel_item['method']
                if expected_method:
                    actual_method = wsgi_environ['REQUEST_METHOD']
                    if expected_method != actual_method:
                        logger.warn(
                            'Expected `%s` instead of `%s` for `%s`', expected_method, actual_method, channel_item['url_path'])
                        raise MethodNotAllowed(cid, 'Method `{}` is not allowed here'.format(actual_method))

                # Need to read security info here so we know if POST needs to be
                # parsed. If so, we do it here and reuse it in other places
                # so it doesn't have to be parsed two or more times.
                post_data = {}
                sec = self.url_data.url_sec[channel_item['match_target']]

                if sec.sec_def != ZATO_NONE or sec.sec_use_rbac is True:

                    if sec.sec_def != ZATO_NONE:

                        if sec.sec_def.sec_type == SEC_DEF_TYPE.OAUTH:
                            post_data.update(QueryDict(payload, encoding='utf-8'))

                        # Eagerly parse the request but only if we expect XPath-based credentials. The request will be re-used
                        # in later steps, it won't be parsed twice or more.
                        elif sec.sec_def.sec_type == SEC_DEF_TYPE.XPATH_SEC:
                            wsgi_environ['zato.request.payload'] = payload_from_request(
                                cid, payload, channel_item.data_format, channel_item.transport)

                    # Will raise an exception on any security violation
                    self.url_data.check_security(
                        sec, cid, channel_item, path_info, payload, wsgi_environ, post_data, worker_store)

                # This is handy if someone invoked URLData's OAuth API manually
                wsgi_environ['zato.oauth.post_data'] = post_data

                # OK, no security exception at that point means we can finally invoke the service.
                response = self.request_handler.handle(cid, url_match, channel_item, wsgi_environ,
                    payload, worker_store, self.simple_io_config, post_data, path_info, soap_action)

                wsgi_environ['zato.http.response.headers']['Content-Type'] = response.content_type
                wsgi_environ['zato.http.response.headers'].update(response.headers)
                wsgi_environ['zato.http.response.status'] = _status_response[response.status_code]

                if channel_item['content_encoding'] == 'gzip':

                    s = _stringio()
                    with _gzipfile(fileobj=s, mode='w') as f:
                        f.write(response.payload)
                    response.payload = s.getvalue()
                    s.close()

                    wsgi_environ['zato.http.response.headers']['Content-Encoding'] = 'gzip'

                # Finally return payload to the client
                return response.payload

            except Exception, e:
                _format_exc = format_exc(e)
                status = _status_internal_server_error

                if isinstance(e, ClientHTTPError):

                    response = e.msg
                    status_code = e.status

                    # TODO: Refactor this series of if/else's into a lookup dict.

                    if isinstance(e, Unauthorized):
                        status = _status_unauthorized
                        wsgi_environ['zato.http.response.headers']['WWW-Authenticate'] = e.challenge

                    elif isinstance(e, BadRequest):
                        status = _status_bad_request

                    elif isinstance(e, NotFound):
                        status = _status_not_found

                    elif isinstance(e, MethodNotAllowed):
                        status = _status_method_not_allowed

                    elif isinstance(e, Forbidden):
                        status = _status_forbidden

                    elif isinstance(e, TooManyRequests):
                        status = _status_too_many_requests

                else:
                    status_code = INTERNAL_SERVER_ERROR
                    response = _format_exc if self.return_tracebacks else self.default_error_message

                # TODO: This should be configurable. Some people may want such
                # things to be on DEBUG whereas for others ERROR will make most sense
                # in given circumstances.
                logger.error('Caught an exception, cid:`%s`, status_code:`%s`, _format_exc:`%s`', cid, status_code, _format_exc)

                try:
                    error_wrapper = get_client_error_wrapper(channel_item['transport'], channel_item['data_format'])
                except KeyError:
                    # It's OK. Apparently it's neither 'soap' nor json'
                    if logger.isEnabledFor(TRACE1):
                        msg = 'No client error wrapper for transport:`{}`, data_format:`{}`'.format(
                            channel_item.get('transport'), channel_item.get('data_format'))
                        logger.log(TRACE1, msg)
                else:
                    response = error_wrapper(cid, response)

                wsgi_environ['zato.http.response.status'] = status

                return response
Exemple #6
0
    def dispatch(self,
                 cid,
                 req_timestamp,
                 wsgi_environ,
                 worker_store,
                 _status_response=status_response,
                 no_url_match=(None, False),
                 _response_404=response_404,
                 _has_debug=_has_debug,
                 _http_soap_action='HTTP_SOAPACTION',
                 _stringio=StringIO,
                 _gzipfile=GzipFile,
                 _accept_any_http=accept_any_http,
                 _accept_any_internal=accept_any_internal,
                 _rate_limit_type=RATE_LIMIT.OBJECT_TYPE.HTTP_SOAP,
                 _stack_format=stack_format,
                 _exc_sep='*' * 80):

        # Needed as one of the first steps
        http_method = wsgi_environ['REQUEST_METHOD']
        http_method = http_method if isinstance(
            http_method, unicode) else http_method.decode('utf8')

        http_accept = wsgi_environ.get('HTTP_ACCEPT') or _accept_any_http
        http_accept = http_accept.replace('*', _accept_any_internal).replace(
            '/', 'HTTP_SEP')
        http_accept = http_accept if isinstance(
            http_accept, unicode) else http_accept.decode('utf8')

        # Needed in later steps
        path_info = wsgi_environ['PATH_INFO'] if PY3 else wsgi_environ[
            'PATH_INFO'].decode('utf8')

        # Immediately reject the request if it is not a support HTTP method, no matter what channel
        # it would have otherwise matched.
        if http_method not in self.http_methods_allowed:
            wsgi_environ[
                'zato.http.response.status'] = _status_method_not_allowed
            return client_json_error(cid, 'Unsupported HTTP method')

        if _http_soap_action in wsgi_environ:
            soap_action = self._handle_quotes_soap_action(
                wsgi_environ[_http_soap_action])
        else:
            soap_action = ''

        # Can we recognize this combination of URL path and SOAP action at all?
        # This gives us the URL info and security data - but note that here
        # we still haven't validated credentials, only matched the URL.
        # Credentials are checked in a call to self.url_data.check_security
        url_match, channel_item = self.url_data.match(path_info, soap_action,
                                                      http_method, http_accept,
                                                      bool(soap_action))

        if _has_debug and channel_item:
            logger.debug('url_match:`%r`, channel_item:`%r`', url_match,
                         sorted(channel_item.items()))

        # This is needed in parallel.py's on_wsgi_request
        wsgi_environ['zato.channel_item'] = channel_item

        payload = wsgi_environ['wsgi.input'].read()

        # OK, we can possibly handle it
        if url_match not in no_url_match:

            try:

                # Raise 404 if the channel is inactive
                if not channel_item['is_active']:
                    logger.warn(
                        'url_data:`%s` is not active, raising NotFound',
                        sorted(url_match.items()))
                    raise NotFound(cid, 'Channel inactive')

                # We need to read security info here so we know if POST needs to be
                # parsed. If so, we do it here and reuse it in other places
                # so it doesn't have to be parsed two or more times.
                post_data = {}

                match_target = channel_item['match_target']
                sec = self.url_data.url_sec[match_target]

                if sec.sec_def != ZATO_NONE or sec.sec_use_rbac is True:

                    if sec.sec_def != ZATO_NONE:

                        if sec.sec_def.sec_type == SEC_DEF_TYPE.OAUTH:
                            post_data.update(
                                QueryDict(payload, encoding='utf-8'))

                        # Eagerly parse the request but only if we expect XPath-based credentials. The request will be re-used
                        # in later steps, it won't be parsed twice or more.
                        elif sec.sec_def.sec_type == SEC_DEF_TYPE.XPATH_SEC:
                            wsgi_environ[
                                'zato.request.payload'] = payload_from_request(
                                    cid, payload, channel_item.data_format,
                                    channel_item.transport)

                    # Will raise an exception on any security violation
                    self.url_data.check_security(sec, cid, channel_item,
                                                 path_info, payload,
                                                 wsgi_environ, post_data,
                                                 worker_store)

                # Check rate limiting now - this could not have been done earlier because we wanted
                # for security checks to be made first. Otherwise, someone would be able to invoke
                # our endpoint without credentials as many times as it is needed to exhaust the rate limit
                # denying in this manner access to genuine users.
                if channel_item.get('is_rate_limit_active'):
                    self.server.rate_limiting.check_limit(
                        cid, _rate_limit_type, channel_item['name'],
                        wsgi_environ['zato.http.remote_addr'])

                # This is handy if someone invoked URLData's OAuth API manually
                wsgi_environ['zato.oauth.post_data'] = post_data

                # OK, no security exception at that point means we can finally invoke the service.
                response = self.request_handler.handle(
                    cid, url_match, channel_item, wsgi_environ, payload,
                    worker_store, self.simple_io_config, post_data, path_info,
                    soap_action)

                wsgi_environ['zato.http.response.headers'][
                    'Content-Type'] = response.content_type
                wsgi_environ['zato.http.response.headers'].update(
                    response.headers)
                wsgi_environ['zato.http.response.status'] = _status_response[
                    response.status_code]

                if channel_item['content_encoding'] == 'gzip':

                    s = _stringio()
                    with _gzipfile(fileobj=s, mode='w') as f:
                        f.write(response.payload)
                    response.payload = s.getvalue()
                    s.close()

                    wsgi_environ['zato.http.response.headers'][
                        'Content-Encoding'] = 'gzip'

                # Finally, return payload to the client
                return response.payload

            except Exception as e:
                _format_exc = format_exc()
                status = _status_internal_server_error

                if isinstance(e, ClientHTTPError):

                    response = e.msg
                    status_code = e.status

                    # TODO: Refactor this series of if/else's into a lookup dict.

                    if isinstance(e, Unauthorized):
                        status = _status_unauthorized
                        wsgi_environ['zato.http.response.headers'][
                            'WWW-Authenticate'] = e.challenge

                    elif isinstance(e, BadRequest):
                        status = _status_bad_request

                    elif isinstance(e, NotFound):
                        status = _status_not_found

                    elif isinstance(e, MethodNotAllowed):
                        status = _status_method_not_allowed

                    elif isinstance(e, Forbidden):
                        status = _status_forbidden

                    elif isinstance(e, TooManyRequests):
                        status = _status_too_many_requests

                else:

                    # JSON Schema validation
                    if isinstance(e, JSONSchemaValidationException):
                        status_code = _status_bad_request
                        needs_prefix = False if e.needs_err_details else True
                        response = JSONSchemaDictError(
                            cid,
                            e.needs_err_details,
                            e.error_msg,
                            needs_prefix=needs_prefix).serialize()

                    # Rate limiting and whitelisting
                    elif isinstance(e, RateLimitingException):
                        response, status_code, status = self._on_rate_limiting_exception(
                            cid, e, channel_item)

                    else:
                        status_code = INTERNAL_SERVER_ERROR
                        response = _format_exc if self.return_tracebacks else self.default_error_message

                _exc = _stack_format(
                    e,
                    style='color',
                    show_vals='like_source',
                    truncate_vals=5000,
                    add_summary=True,
                    source_lines=20) if _stack_format else _format_exc

                # TODO: This should be configurable. Some people may want such
                # things to be on DEBUG whereas for others ERROR will make most sense
                # in given circumstances.
                logger.error(
                    'Caught an exception, cid:`%s`, status_code:`%s`, e:\n%s\n`%s`',
                    cid, status_code, _exc_sep, _exc)

                try:
                    error_wrapper = get_client_error_wrapper(
                        channel_item['transport'], channel_item['data_format'])
                except KeyError:
                    # It's OK. Apparently it's neither 'soap' nor json'
                    if logger.isEnabledFor(TRACE1):
                        msg = 'No client error wrapper for transport:`{}`, data_format:`{}`'.format(
                            channel_item.get('transport'),
                            channel_item.get('data_format'))
                        logger.log(TRACE1, msg)
                else:
                    response = error_wrapper(cid, response)

                wsgi_environ['zato.http.response.status'] = status

                return response

        # This is 404, no such URL path and SOAP action is not known either.
        else:
            response = _response_404.format(path_info,
                                            wsgi_environ.get('REQUEST_METHOD'),
                                            wsgi_environ.get('HTTP_ACCEPT'),
                                            cid)
            wsgi_environ['zato.http.response.status'] = _status_not_found

            logger.error(response)
            return response
Exemple #7
0
    def dispatch(self, cid, req_timestamp, wsgi_environ, worker_store):
        """ Base method for dispatching incoming HTTP/SOAP messages. If the security
        configuration is one of the technical account or HTTP basic auth, 
        the security validation is being performed. Otherwise, that step 
        is postponed until a concrete transport-specific handler is invoked.
        """
        path_info = wsgi_environ['PATH_INFO']
        soap_action = wsgi_environ.get('HTTP_SOAPACTION', '')
        url_data = self.security.url_sec_get(path_info, soap_action)
        payload = wsgi_environ['wsgi.input'].read()

        if logger.isEnabledFor(logging.DEBUG):
            msg = 'cid:[{}], payload:[{}], wsgi_environ:[{}]'.format(
                cid, payload, wsgi_environ)
            logger.debug(msg)

        if url_data:
            transport = url_data['transport']
            data_format = url_data['data_format']
            try:
                if not url_data.is_active:
                    msg = 'url_data:[{}] is not active, raising NotFound'.format(
                        sorted(url_data.items()))
                    logger.warn(msg)
                    raise NotFound(cid, 'Inactive channel')

                if url_data.sec_def != ZATO_NONE:
                    if url_data.sec_def.sec_type in (
                            security_def_type.tech_account,
                            security_def_type.basic_auth,
                            security_def_type.wss):
                        self.security.handle(cid, url_data, path_info, payload,
                                             wsgi_environ)
                    else:
                        log_msg = '[{0}] sec_def.sec_type:[{1}] needs no auth'.format(
                            cid, url_data.sec_def.sec_type)
                        logger.debug(log_msg)
                else:
                    log_msg = '[{0}] No security for URL [{1}]'.format(
                        cid, path_info)
                    logger.debug(log_msg)

                handler = getattr(self, '{0}_handler'.format(transport))

                service_info, response = handler.handle(
                    cid, wsgi_environ, payload, transport, worker_store,
                    self.simple_io_config, data_format, path_info)
                wsgi_environ['zato.http.response.headers'][
                    'Content-Type'] = response.content_type
                wsgi_environ['zato.http.response.headers'].update(
                    response.headers)
                wsgi_environ['zato.http.response.status'] = b'{} {}'.format(
                    response.status_code, responses[response.status_code])

                return response.payload

            except Exception, e:
                _format_exc = format_exc(e)
                status = _status_internal_server_error
                if isinstance(e, ClientHTTPError):
                    response = e.msg
                    status_code = e.status
                    if isinstance(e, Unauthorized):
                        status = _status_unauthorized
                        wsgi_environ['zato.http.response.headers'][
                            'WWW-Authenticate'] = e.challenge
                    elif isinstance(e, NotFound):
                        status = _status_not_found
                else:
                    status_code = INTERNAL_SERVER_ERROR
                    response = _format_exc

                # TODO: This should be configurable. Some people may want such
                # things to be on DEBUG whereas for others ERROR will make most sense
                # in given circumstances.
                if logger.isEnabledFor(logging.ERROR):
                    msg = 'Caught an exception, cid:[{0}], status_code:[{1}], _format_exc:[{2}]'.format(
                        cid, status_code, _format_exc)
                    logger.log(logging.ERROR, msg)

                try:
                    error_wrapper = get_client_error_wrapper(
                        transport, data_format)
                except KeyError:
                    # It's OK. Apparently it's neither 'soap' nor json'
                    if logger.isEnabledFor(TRACE1):
                        msg = 'No client error wrapper for transport:[{}], data_format:[{}]'.format(
                            transport, data_format)
                        logger.log(TRACE1, msg)
                else:
                    response = error_wrapper(cid, response)

                wsgi_environ['zato.http.response.status'] = status
                return response
Exemple #8
0
    def dispatch(self, cid, req_timestamp, wsgi_environ, worker_store):
        """ Base method for dispatching incoming HTTP/SOAP messages. If the security
        configuration is one of the technical account or HTTP basic auth,
        the security validation is being performed. Otherwise, that step
        is postponed until a concrete transport-specific handler is invoked.
        """

        # Needed in later steps
        path_info = wsgi_environ['PATH_INFO']
        soap_action = wsgi_environ.get('HTTP_SOAPACTION', '')

        # Fix up SOAP action - turns "my:soap:action" into my:soap:action,
        # that is, strips it out of surrounding quotes, if any.
        if soap_action:
            soap_action = self._handle_quotes_soap_action(soap_action)

        # Can we recognize this combination of URL path and SOAP action at all?
        # This gives us the URL info and security data - but note that here
        # we still haven't validated credentials, only matched the URL.
        # Credentials are checked in a call to self.url_data.check_security
        url_match, channel_item = self.url_data.match(path_info, soap_action)

        if channel_item:
            logger.debug('url_match:`%r`, channel_item:`%r`', url_match,
                         sorted(channel_item.items()))

        # This is needed in parallel.py's on_wsgi_request
        wsgi_environ['zato.http.channel_item'] = channel_item

        payload = wsgi_environ['wsgi.input'].read()

        # OK, we can possibly handle it
        if url_match not in (None, False):

            # This is a synchronous call so that whatever happens next we are always
            # able to have at least initial audit log of requests.
            if channel_item['audit_enabled']:
                self.url_data.audit_set_request(cid, channel_item, payload,
                                                wsgi_environ)

            try:

                # Raise 404 if the channel is inactive
                if not channel_item['is_active']:
                    logger.warn(
                        'url_data:`%s` is not active, raising NotFound',
                        sorted(url_match.items()))
                    raise NotFound(cid, 'Channel inactive')

                expected_method = channel_item['method']
                if expected_method:
                    actual_method = wsgi_environ['REQUEST_METHOD']
                    if expected_method != actual_method:
                        logger.warn('Expected `%s` instead of `%s` for `%s`',
                                    expected_method, actual_method,
                                    channel_item['url_path'])
                        raise MethodNotAllowed(
                            cid, 'Method `{}` is not allowed here'.format(
                                actual_method))

                # Need to read security info here so we know if POST needs to be
                # parsed. If so, we do it here and reuse it in other places
                # so it doesn't have to be parsed two or more times.
                sec = self.url_data.url_sec[channel_item['match_target']]
                if sec.sec_def != ZATO_NONE and sec.sec_def.sec_type == SEC_DEF_TYPE.OAUTH:
                    post_data = QueryDict(payload, encoding='utf-8')
                else:
                    post_data = {}

                # This is handy if someone invoked URLData's OAuth API manually
                wsgi_environ['zato.oauth.post_data'] = post_data

                # Eagerly parse the request but only if we expect XPath-based credentials. The request will be re-used
                # in later steps, it won't be parsed twice or more.
                if sec.sec_def != ZATO_NONE and sec.sec_def.sec_type == SEC_DEF_TYPE.XPATH_SEC:
                    wsgi_environ[
                        'zato.request.payload'] = payload_from_request(
                            cid, payload, channel_item.data_format,
                            channel_item.transport)

                # Will raise an exception on any security violation
                self.url_data.check_security(sec, cid, channel_item, path_info,
                                             payload, wsgi_environ, post_data,
                                             worker_store)

                # OK, no security exception at that point means we can finally
                # invoke the service.
                response = self.request_handler.handle(
                    cid, url_match, channel_item, wsgi_environ, payload,
                    worker_store, self.simple_io_config, post_data)

                # Got response from the service so we can construct response headers now
                self.add_response_headers(wsgi_environ, response)

                # Return the payload to the client
                return response.payload

            except Exception, e:
                _format_exc = format_exc(e)
                status = _status_internal_server_error

                if isinstance(e, ClientHTTPError):

                    response = e.msg
                    status_code = e.status

                    # TODO: Refactor this series of if/else's into a lookup dict.

                    if isinstance(e, Unauthorized):
                        status = _status_unauthorized
                        wsgi_environ['zato.http.response.headers'][
                            'WWW-Authenticate'] = e.challenge

                    elif isinstance(e, BadRequest):
                        status = _status_bad_request

                    elif isinstance(e, NotFound):
                        status = _status_not_found

                    elif isinstance(e, MethodNotAllowed):
                        status = _status_method_not_allowed

                    elif isinstance(e, Forbidden):
                        status = _status_forbidden

                    elif isinstance(e, TooManyRequests):
                        status = _status_too_many_requests

                else:
                    status_code = INTERNAL_SERVER_ERROR
                    response = _format_exc

                # TODO: This should be configurable. Some people may want such
                # things to be on DEBUG whereas for others ERROR will make most sense
                # in given circumstances.
                logger.error(
                    'Caught an exception, cid:`%s`, status_code:`%s`, _format_exc:`%s`',
                    cid, status_code, _format_exc)

                try:
                    error_wrapper = get_client_error_wrapper(
                        channel_item['transport'], channel_item['data_format'])
                except KeyError:
                    # It's OK. Apparently it's neither 'soap' nor json'
                    if logger.isEnabledFor(TRACE1):
                        msg = 'No client error wrapper for transport:`{}`, data_format:`{}`'.format(
                            channel_item.get('transport'),
                            channel_item.get('data_format'))
                        logger.log(TRACE1, msg)
                else:
                    response = error_wrapper(cid, response)

                wsgi_environ['zato.http.response.status'] = status

                return response
Exemple #9
0
    def dispatch(self, cid, req_timestamp, wsgi_environ, worker_store):
        """ Base method for dispatching incoming HTTP/SOAP messages. If the security
        configuration is one of the technical account or HTTP basic auth, 
        the security validation is being performed. Otherwise, that step 
        is postponed until a concrete transport-specific handler is invoked.
        """

        # Needed in later steps
        path_info = wsgi_environ['PATH_INFO']
        soap_action = wsgi_environ.get('HTTP_SOAPACTION', '')

        # Fix up SOAP action - turns "my:soap:action" into my:soap:action,
        # that is, strips it out of surrounding quotes, if any.
        if soap_action:
            soap_action = self._handle_quotes_soap_action(soap_action)

        # Can we recognize this combination of URL path and SOAP action at all?
        url_match, channel_item = self.url_data.match(path_info, soap_action)

        # OK, we can possibly handle it
        if url_match:

            # Raise 404 if the channel is inactive
            if not channel_item.is_active:
                logger.warn('url_data:[%s] is not active, raising NotFound',
                            sorted(url_match.items()))
                raise NotFound(cid, 'Channel inactive')

            # Read payload only now, right before it's needed the first time,
            # possibly, by security checks.
            payload = wsgi_environ['wsgi.input'].read()

            try:

                # Will raise an exception on any security violation
                self.url_data.check_security(cid, channel_item, path_info,
                                             payload, wsgi_environ)

                # OK, no security exception at that point means we can finally
                # invoke the service.
                response = self.request_handler.handle(cid, url_match,
                                                       channel_item,
                                                       wsgi_environ, payload,
                                                       worker_store,
                                                       self.simple_io_config)

                # Got response from the service so we can construct response headers now
                self.add_response_headers(wsgi_environ, response)

                # Return the payload to the client
                return response.payload

            except Exception, e:

                _format_exc = format_exc(e)
                status = _status_internal_server_error

                if isinstance(e, ClientHTTPError):
                    response = e.msg
                    status_code = e.status

                    if isinstance(e, Unauthorized):
                        status = _status_unauthorized
                        wsgi_environ['zato.http.response.headers'][
                            'WWW-Authenticate'] = e.challenge
                    elif isinstance(e, NotFound):
                        status = _status_not_found
                else:
                    status_code = INTERNAL_SERVER_ERROR
                    response = _format_exc

                # TODO: This should be configurable. Some people may want such
                # things to be on DEBUG whereas for others ERROR will make most sense
                # in given circumstances.
                logger.error(
                    'Caught an exception, cid:[%s], status_code:[%s], _format_exc:[%s]',
                    cid, status_code, _format_exc)

                try:
                    error_wrapper = get_client_error_wrapper(
                        channel_item.transport, channel_item.data_format)
                except KeyError:
                    # It's OK. Apparently it's neither 'soap' nor json'
                    if logger.isEnabledFor(TRACE1):
                        msg = 'No client error wrapper for transport:[{}], data_format:[{}]'.format(
                            channel_item.transport, channel_item.data_format)
                        logger.log(TRACE1, msg)
                else:
                    response = error_wrapper(cid, response)

                wsgi_environ['zato.http.response.status'] = status
                return response
Exemple #10
0
    def dispatch(self, cid, req_timestamp, wsgi_environ, worker_store):
        """ Base method for dispatching incoming HTTP/SOAP messages. If the security
        configuration is one of the technical account or HTTP basic auth,
        the security validation is being performed. Otherwise, that step
        is postponed until a concrete transport-specific handler is invoked.
        """

        # Needed in later steps
        path_info = wsgi_environ['PATH_INFO']
        soap_action = wsgi_environ.get('HTTP_SOAPACTION', '')

        # Fix up SOAP action - turns "my:soap:action" into my:soap:action,
        # that is, strips it out of surrounding quotes, if any.
        if soap_action:
            soap_action = self._handle_quotes_soap_action(soap_action)

        # Can we recognize this combination of URL path and SOAP action at all?
        # This gives us the URL info and security data - but note that here
        # we still haven't validated credentials, only matched the URL.
        # Credentials are checked in a call to self.url_data.check_security
        url_match, channel_item = self.url_data.match(path_info, soap_action)

        if channel_item:
            logger.debug('url_match:[%r], channel_item:[%r]', url_match,
                         sorted(channel_item.items()))

        # This is needed in parallel.py's on_wsgi_request
        wsgi_environ['zato.http.channel_item'] = channel_item

        payload = wsgi_environ['wsgi.input'].read()

        # OK, we can possibly handle it
        if url_match:

            # This is a synchronous call so that whatever happens next we are always
            # able to have at least initial audit log of requests.
            if channel_item['audit_enabled']:
                self.url_data.audit_set_request(cid, channel_item, payload,
                                                wsgi_environ)

            # Raise 404 if the channel is inactive
            if not channel_item['is_active']:
                logger.warn('url_data:[%s] is not active, raising NotFound',
                            sorted(url_match.items()))
                raise NotFound(cid, 'Channel inactive')

            try:

                # Need to read security info here so we know if POST needs to be
                # parsed. If so, we do it here and reuse it in other places
                # so it doesn't have to be parsed two or more times.
                sec = self.url_data.url_sec[channel_item['match_target']]
                if sec.sec_def != ZATO_NONE and sec.sec_def.sec_type == security_def_type.oauth:
                    post_data = QueryDict(payload, encoding='utf-8')
                else:
                    post_data = None

                # Will raise an exception on any security violation
                self.url_data.check_security(sec, cid, channel_item, path_info,
                                             payload, wsgi_environ, post_data)

                # OK, no security exception at that point means we can finally
                # invoke the service.
                response = self.request_handler.handle(
                    cid, url_match, channel_item, wsgi_environ, payload,
                    worker_store, self.simple_io_config, post_data)

                # Got response from the service so we can construct response headers now
                self.add_response_headers(wsgi_environ, response)

                # Return the payload to the client
                return response.payload

            except Exception, e:

                _format_exc = format_exc(e)
                status = _status_internal_server_error

                if isinstance(e, ClientHTTPError):
                    response = e.msg
                    status_code = e.status

                    if isinstance(e, Unauthorized):
                        status = _status_unauthorized
                        wsgi_environ['zato.http.response.headers'][
                            'WWW-Authenticate'] = e.challenge
                    elif isinstance(e, NotFound):
                        status = _status_not_found
                else:
                    status_code = INTERNAL_SERVER_ERROR
                    response = _format_exc

                # TODO: This should be configurable. Some people may want such
                # things to be on DEBUG whereas for others ERROR will make most sense
                # in given circumstances.
                logger.error(
                    'Caught an exception, cid:[%s], status_code:[%s], _format_exc:[%s]',
                    cid, status_code, _format_exc)

                try:
                    error_wrapper = get_client_error_wrapper(
                        channel_item['transport'], channel_item['data_format'])
                except KeyError:
                    # It's OK. Apparently it's neither 'soap' nor json'
                    if logger.isEnabledFor(TRACE1):
                        msg = 'No client error wrapper for transport:[{}], data_format:[{}]'.format(
                            channel_item['transport'],
                            channel_item['data_format'])
                        logger.log(TRACE1, msg)
                else:
                    response = error_wrapper(cid, response)

                wsgi_environ['zato.http.response.status'] = status

                return response