Example #1
0
    def handle(self):
        payload = self.request.input.get('payload')
        if payload:
            payload = payload_from_request(self.cid, payload.decode('base64'), 
                self.request.input.data_format, self.request.input.transport)

        id = self.request.input.get('id')
        name = self.request.input.get('name')

        channel = self.request.input.get('channel')
        data_format = self.request.input.get('data_format')
        transport = self.request.input.get('transport')
        expiration = self.request.input.get('expiration') or BROKER.DEFAULT_EXPIRATION

        if name and id:
            raise ZatoException('Cannot accept both id:[{}] and name:[{}]'.format(id, name))

        if self.request.input.get('async'):
            if id:
                impl_name = self.server.service_store.id_to_impl_name[id]
                name = self.server.service_store.service_data(impl_name)['name']
            response = self.invoke_async(name, payload, channel, data_format, transport, expiration)
        else:

            func, id_ = (self.invoke, name) if name else (self.invoke_by_id, id)
            response = func(id_, payload, channel, data_format, transport, serialize=True)

        if isinstance(response, basestring):
            if response:
                self.response.payload.response = response.encode('base64') if response else ''
Example #2
0
    def update_handle(self, set_response_func, service, raw_request, channel,
                      data_format, transport, server, broker_client,
                      worker_store, cid, simple_io_config, *args, **kwargs):

        payload = payload_from_request(cid, raw_request, data_format,
                                       transport)

        service.update(service,
                       channel,
                       server,
                       broker_client,
                       worker_store,
                       cid,
                       payload,
                       raw_request,
                       transport,
                       simple_io_config,
                       data_format,
                       kwargs.get('wsgi_environ', {}),
                       job_type=kwargs.get('job_type'))

        service.pre_handle()
        service.call_hooks('before')
        service.handle()
        service.call_hooks('after')
        service.post_handle()
        service.call_hooks('finalize')

        return set_response_func(service,
                                 data_format=data_format,
                                 transport=transport,
                                 **kwargs)
Example #3
0
    def handle(self,
               _channel=CHANNEL.WEBSPHERE_MQ,
               ts_format='YYYYMMDDHHmmssSS'):
        request = loads(self.request.raw_request)
        msg = request['msg']
        service_name = request['service_name']

        # Make MQ-level attributes easier to handle
        correlation_id = unhexlify(
            msg['correlation_id']) if msg['correlation_id'] else None
        expiration = datetime_from_ms(
            msg['expiration']) if msg['expiration'] else None

        timestamp = '{}{}'.format(msg['put_date'], msg['put_time'])
        timestamp = arrow_get(timestamp,
                              ts_format).replace(tzinfo='UTC').datetime

        data = payload_from_request(self.cid, msg['text'],
                                    request['data_format'], None)

        self.invoke(service_name,
                    data,
                    _channel,
                    wmq_ctx={
                        'msg_id': unhexlify(msg['msg_id']),
                        'correlation_id': correlation_id,
                        'timestamp': timestamp,
                        'put_time': msg['put_time'],
                        'put_date': msg['put_date'],
                        'expiration': expiration,
                        'reply_to': msg['reply_to'],
                    })
Example #4
0
 def handle(self):
     payload = payload_from_request(self.request.input.payload, 
         self.request.input.data_format, self.request.input.transport)
         
     response = self.invoke_by_id(self.request.input.id, payload, 
         self.request.input.data_format, self.request.input.transport,
         serialize=True)
     self.response.payload.response = response
Example #5
0
    def handle(self):
        payload = self.request.input.get('payload')
        if payload:
            payload = payload_from_request(self.cid, payload.decode('base64'),
                                           self.request.input.data_format,
                                           self.request.input.transport)

        id = self.request.input.get('id')
        name = self.request.input.get('name')
        pid = self.request.input.get('pid')

        channel = self.request.input.get('channel')
        data_format = self.request.input.get('data_format')
        transport = self.request.input.get('transport')
        expiration = self.request.input.get(
            'expiration') or BROKER.DEFAULT_EXPIRATION

        if name and id:
            raise ZatoException(
                'Cannot accept both id:`{}` and name:`{}`'.format(id, name))

        if self.request.input.get('async'):

            if id:
                impl_name = self.server.service_store.id_to_impl_name[id]
                name = self.server.service_store.service_data(
                    impl_name)['name']

            # If PID is given on input it means we must invoke this particular server process by it ID
            if pid:
                response = self.server.invoke_by_pid(name, payload, pid)
            else:
                response = self.invoke_async(name, payload, channel,
                                             data_format, transport,
                                             expiration)

        else:

            # Same as above in async branch
            if pid:
                response = self.server.invoke(name,
                                              payload,
                                              pid=pid,
                                              data_format=data_format)
            else:
                func, id_ = (self.invoke,
                             name) if name else (self.invoke_by_id, id)
                response = func(id_,
                                payload,
                                channel,
                                data_format,
                                transport,
                                serialize=True)

        if isinstance(response, basestring):
            if response:
                self.response.payload.response = response.encode(
                    'base64') if response else ''
Example #6
0
File: __init__.py Project: aek/zato
    def update_handle(self, set_response_func, service, raw_request, channel, data_format,
            transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs):

        wsgi_environ = kwargs.get('wsgi_environ', {})
        payload = wsgi_environ.get('zato.request.payload')

        # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element
        # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates
        # to False so we'd be parsing the payload again superfluously.
        if not isinstance(payload, ObjectifiedElement) and not payload:
            payload = payload_from_request(cid, raw_request, data_format, transport)

        job_type = kwargs.get('job_type')
        channel_params = kwargs.get('channel_params', {})
        merge_channel_params = kwargs.get('merge_channel_params', True)
        params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT)
        serialize = kwargs.get('serialize')
        as_bunch = kwargs.get('as_bunch')
        channel_item = kwargs.get('channel_item')

        service.update(service, channel, server, broker_client,
            worker_store, cid, payload, raw_request, transport,
            simple_io_config, data_format, wsgi_environ,
            job_type=job_type,
            channel_params=channel_params,
            merge_channel_params=merge_channel_params,
            params_priority=params_priority, in_reply_to=wsgi_environ.get('zato.request_ctx.in_reply_to', None),
            environ=kwargs.get('environ'))

        # It's possible the call will be completely filtered out
        if service.accept():

            # Assume everything goes fine
            e, exc_formatted = None, None

            try:
                service.pre_handle()
                service.call_hooks('before')

                service.validate_input()
                self._invoke(service, channel)
                service.validate_output()

                service.call_hooks('after')
                service.post_handle()
                service.call_hooks('finalize')

            except Exception, e:
                exc_formatted = format_exc(e)
                logger.warn(exc_formatted)

            finally:
Example #7
0
    def update_handle(self, set_response_func, service, raw_request, channel, data_format,
            transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs):

        wsgi_environ = kwargs.get('wsgi_environ', {})
        payload = wsgi_environ.get('zato.request.payload')

        # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element
        # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates
        # to False so we'd be parsing the payload again superfluously.
        if not isinstance(payload, ObjectifiedElement) and not payload:
            payload = payload_from_request(cid, raw_request, data_format, transport)

        job_type = kwargs.get('job_type')
        channel_params = kwargs.get('channel_params', {})
        merge_channel_params = kwargs.get('merge_channel_params', True)
        params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT)
        serialize = kwargs.get('serialize')
        as_bunch = kwargs.get('as_bunch')
        channel_item = kwargs.get('channel_item')

        service.update(service, channel, server, broker_client,
            worker_store, cid, payload, raw_request, transport,
            simple_io_config, data_format, wsgi_environ,
            job_type=job_type,
            channel_params=channel_params,
            merge_channel_params=merge_channel_params,
            params_priority=params_priority)

        # Depending on whether this is a pass-through service or not we invoke
        # the target services or the one we have in hand right now. Note that
        # hooks are always invoked for the one we have a handle to right now
        # even if it's a pass-through one.

        service.pre_handle()
        service.call_hooks('before')

        if service.passthrough_to:
            sio = getattr(service, 'SimpleIO', None)
            return self.invoke(service.passthrough_to, payload, channel, data_format, transport, serialize, as_bunch,
                sio=sio, from_passthrough=True, passthrough_request=self.request, set_response_func=set_response_func,
                channel_item=channel_item)
        else:
            service.validate_input()
            service.handle()
            service.validate_output()

        service.call_hooks('after')
        service.post_handle()
        service.call_hooks('finalize')

        return set_response_func(service, data_format=data_format, transport=transport, **kwargs)
Example #8
0
    def handle(self):
        payload = self.request.input.get('payload')
        if payload:
            payload = b64decode(payload)
            payload = payload_from_request(self.cid, payload,
                self.request.input.data_format, self.request.input.transport)

        id = self.request.input.get('id')
        name = self.request.input.get('name')
        pid = self.request.input.get('pid') or 0
        all_pids = self.request.input.get('all_pids')
        timeout = self.request.input.get('timeout') or None

        channel = self.request.input.get('channel')
        data_format = self.request.input.get('data_format')
        transport = self.request.input.get('transport')
        expiration = self.request.input.get('expiration') or BROKER.DEFAULT_EXPIRATION

        if name and id:
            raise ZatoException('Cannot accept both id:`{}` and name:`{}`'.format(id, name))

        if self.request.input.get('async'):

            if id:
                impl_name = self.server.service_store.id_to_impl_name[id]
                name = self.server.service_store.service_data(impl_name)['name']

            # If PID is given on input it means we must invoke this particular server process by it ID
            if pid and pid != self.server.pid:
                response = self.server.invoke_by_pid(name, payload, pid)
            else:
                response = self.invoke_async(name, payload, channel, data_format, transport, expiration)

        else:

            # Same as above in async branch, except in async there was no all_pids
            if all_pids:
                args = (name, payload, timeout) if timeout else (name, payload)
                response = dumps(self.server.invoke_all_pids(*args))
            else:
                if pid and pid != self.server.pid:
                    response = self.server.invoke(name, payload, pid=pid, data_format=data_format)
                else:
                    func, id_ = (self.invoke, name) if name else (self.invoke_by_id, id)
                    response = func(id_, payload, channel, data_format, transport, serialize=True)

        if isinstance(response, basestring):
            if response:
                response = response if isinstance(response, bytes) else response.encode('utf8')
                self.response.payload.response = b64encode(response) if response else ''
Example #9
0
    def update_handle(self, set_response_func, service, raw_request, channel, data_format,
            transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs):

        wsgi_environ = kwargs.get('wsgi_environ', {})
        payload = wsgi_environ.get('zato.request.payload')

        # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element
        # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates
        # to False so we'd be parsing the payload again superfluously.
        if not isinstance(payload, ObjectifiedElement) and not payload:
            payload = payload_from_request(cid, raw_request, data_format, transport)

        job_type = kwargs.get('job_type')
        channel_params = kwargs.get('channel_params', {})
        merge_channel_params = kwargs.get('merge_channel_params', True)
        params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT)

        service.update(service, channel, server, broker_client,
            worker_store, cid, payload, raw_request, transport,
            simple_io_config, data_format, wsgi_environ,
            job_type=job_type,
            channel_params=channel_params,
            merge_channel_params=merge_channel_params,
            params_priority=params_priority, in_reply_to=wsgi_environ.get('zato.request_ctx.in_reply_to', None),
            environ=kwargs.get('environ'))

        # It's possible the call will be completely filtered out
        if service.accept():

            # Assume everything goes fine
            e, exc_formatted = None, None

            try:
                service.pre_handle()
                service.call_hooks('before')

                service.validate_input()
                self._invoke(service, channel)
                service.validate_output()

                service.call_hooks('after')
                service.post_handle()
                service.call_hooks('finalize')

            except Exception, e:
                exc_formatted = format_exc(e)
                logger.warn(exc_formatted)

            finally:
Example #10
0
    def init(self, cid, path_info, request, headers, transport, data_format):
        logger.debug('[{0}] request:[{1}] headers:[{2}]'.format(cid, request, headers))

        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()
            
        service_data = self.server.service_store.service_data(service_info.impl_name)
        
        return payload_from_request(request, data_format, transport), service_info, service_data
Example #11
0
    def update_handle(self, set_response_func, service, raw_request, channel, data_format, 
            transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs):

        payload = payload_from_request(cid, raw_request, data_format, transport)
        
        service.update(service, channel, server, broker_client, 
            worker_store, cid, payload, raw_request, transport, 
            simple_io_config, data_format, kwargs.get('wsgi_environ', {}), 
            job_type=kwargs.get('job_type'))
            
        service.pre_handle()
        service.call_hooks('before')
        service.handle()
        service.call_hooks('after')
        service.post_handle()
        service.call_hooks('finalize')
        
        return set_response_func(service, data_format=data_format, transport=transport, **kwargs)
Example #12
0
    def update_handle(self, set_response_func, service, raw_request, channel, data_format,
            transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs):

        payload = payload_from_request(cid, raw_request, data_format, transport)

        job_type = kwargs.get('job_type')
        channel_params = kwargs.get('channel_params', {})
        merge_channel_params = kwargs.get('merge_channel_params', True)
        params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT)
        wsgi_environ = kwargs.get('wsgi_environ', {})
        serialize = kwargs.get('serialize')
        as_bunch = kwargs.get('as_bunch')
        channel_item = kwargs.get('channel_item')

        service.update(service, channel, server, broker_client,
            worker_store, cid, payload, raw_request, transport,
            simple_io_config, data_format, wsgi_environ,
            job_type=job_type,
            channel_params=channel_params,
            merge_channel_params=merge_channel_params,
            params_priority=params_priority)

        # Depending on whether this is a pass-through service or not we invoke
        # the target services or the one we have in hand right now. Note that
        # hooks are always invoked for the one we have a handle to right now
        # even if it's a pass-through one.

        service.pre_handle()
        service.call_hooks('before')

        if service.passthrough_to:
            sio = getattr(service, 'SimpleIO', None)
            return self.invoke(service.passthrough_to, payload, channel, data_format, transport, serialize, as_bunch,
                sio=sio, from_passthrough=True, passthrough_request=self.request, set_response_func=set_response_func,
                channel_item=channel_item)
        else:
            service.handle()

        service.call_hooks('after')
        service.post_handle()
        service.call_hooks('finalize')

        return set_response_func(service, data_format=data_format, transport=transport, **kwargs)
Example #13
0
    def handle(self):
        payload = self.request.input.get('payload')
        if payload:
            payload = payload_from_request(self.cid, payload.decode('base64'),
                                           self.request.input.data_format,
                                           self.request.input.transport)

        id = self.request.input.get('id')
        name = self.request.input.get('name')

        channel = self.request.input.get('channel')
        data_format = self.request.input.get('data_format')
        transport = self.request.input.get('transport')
        expiration = self.request.input.get(
            'expiration') or BROKER.DEFAULT_EXPIRATION

        if name and id:
            raise ZatoException(
                'Cannot accept both id:[{}] and name:[{}]'.format(id, name))

        if self.request.input.get('async'):
            if id:
                impl_name = self.server.service_store.id_to_impl_name[id]
                name = self.server.service_store.service_data(
                    impl_name)['name']
            response = self.invoke_async(name, payload, channel, data_format,
                                         transport, expiration)
        else:

            func, id_ = (self.invoke, name) if name else (self.invoke_by_id,
                                                          id)
            response = func(id_,
                            payload,
                            channel,
                            data_format,
                            transport,
                            serialize=True)

        if isinstance(response, basestring):
            if response:
                self.response.payload.response = response.encode(
                    'base64') if response else ''
Example #14
0
    def update_handle(self, set_response_func, service, raw_request, channel, data_format,
            transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs):

        payload = payload_from_request(cid, raw_request, data_format, transport)

        job_type = kwargs.get('job_type')
        channel_params = kwargs.get('channel_params', {})
        merge_channel_params = kwargs.get('merge_channel_params', True)
        params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT)
        wsgi_environ = kwargs.get('wsgi_environ', {})
        serialize = kwargs.get('serialize')
        as_bunch = kwargs.get('as_bunch')

        service.update(service, channel, server, broker_client,
            worker_store, cid, payload, raw_request, transport,
            simple_io_config, data_format, wsgi_environ,
            job_type=job_type,
            channel_params=channel_params,
            merge_channel_params=merge_channel_params,
            params_priority=params_priority)

        # Depending on whether this is a pass-through service or not we invoke
        # the target services or the one we have in hand right now. Note that
        # hooks are always invoked for the one we have a handle to right now
        # even if it's a pass-through one.

        service.pre_handle()
        service.call_hooks('before')

        if service.passthrough_to:
            sio = getattr(service, 'SimpleIO', None)
            return self.invoke(service.passthrough_to, raw_request, channel, data_format,
                    transport, serialize, as_bunch, sio=sio, from_passthrough=True,
                    passthrough_request=self.request, set_response_func=set_response_func)
        else:
            service.handle()

        service.call_hooks('after')
        service.post_handle()
        service.call_hooks('finalize')

        return set_response_func(service, data_format=data_format, transport=transport, **kwargs)
Example #15
0
    def update_handle(self, set_response_func, service, raw_request, channel, data_format, 
            transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs):

        payload = payload_from_request(cid, raw_request, data_format, transport)
        
        service.update(service, channel, server, broker_client, 
            worker_store, cid, payload, raw_request, transport, 
            simple_io_config, data_format, kwargs.get('wsgi_environ', {}), 
            job_type=kwargs.get('job_type'),
            channel_params=kwargs.get('channel_params', {}),
            merge_channel_params=kwargs.get('merge_channel_params', True),
            params_priority=kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT))
            
        service.pre_handle()
        service.call_hooks('before')
        service.handle()
        service.call_hooks('after')
        service.post_handle()
        service.call_hooks('finalize')
        
        return set_response_func(service, data_format=data_format, transport=transport, **kwargs)
Example #16
0
    def test_handle_security_xpath_sec(self):

        test_data = [
            [True, rand_string(), rand_string()],
            [False, rand_string(), rand_string()],
            [True, rand_string(), None],
            [False, rand_string(), None]
        ]

        username_expr = "//*[local-name()='mydoc']/@user"
        for is_valid, valid_username, password in test_data:

            password_expr = "//*[local-name()='mydoc']/@password" if password else None
            xml_username = valid_username if is_valid else rand_string()

            cid = rand_string()
            ud = url_data.URLData(None, [])
            sec_def = Bunch(username=valid_username, password=password, username_expr=username_expr, password_expr=password_expr)

            xml = """<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:foo="http://foo.example.com">
                <soapenv:Header/>
                <soapenv:Body>
                  <foo:mydoc user="******" password="******"/>
                </soapenv:Body>
                </soapenv:Envelope>""".format(xml_username, password)

            payload = payload_from_request(cid, xml, DATA_FORMAT.XML, URL_TYPE.SOAP)

            if is_valid:
                result = ud._handle_security_xpath_sec(cid, sec_def, None, None, {'zato.request.payload':payload})
                self.assertEqual(result, True)
            else:
                try:
                    ud._handle_security_xpath_sec(cid, sec_def, None, None, {'zato.request.payload':payload})
                except Unauthorized:
                    pass
                else:
                    self.fail('Expected Unauthorized, `{}`, `{}`, `{}`, `{}`, `{}`'.format(
                        is_valid, valid_username, xml_username, password, xml))
Example #17
0
    def update_handle(self, set_response_func, service, raw_request, channel,
                      data_format, transport, server, broker_client,
                      worker_store, cid, simple_io_config, *args, **kwargs):

        payload = payload_from_request(cid, raw_request, data_format,
                                       transport)

        service.update(service,
                       channel,
                       server,
                       broker_client,
                       worker_store,
                       cid,
                       payload,
                       raw_request,
                       transport,
                       simple_io_config,
                       data_format,
                       kwargs.get('wsgi_environ', {}),
                       job_type=kwargs.get('job_type'),
                       channel_params=kwargs.get('channel_params', {}),
                       merge_channel_params=kwargs.get('merge_channel_params',
                                                       True),
                       params_priority=kwargs.get('params_priority',
                                                  PARAMS_PRIORITY.DEFAULT))

        service.pre_handle()
        service.call_hooks('before')
        service.handle()
        service.call_hooks('after')
        service.post_handle()
        service.call_hooks('finalize')

        return set_response_func(service,
                                 data_format=data_format,
                                 transport=transport,
                                 **kwargs)
Example #18
0
    def handle(self):
        payload = self.request.input.get('payload')
        if payload:
            payload = b64decode(payload)
            payload = payload_from_request(self.cid, payload,
                                           self.request.input.data_format,
                                           self.request.input.transport)

        id = self.request.input.get('id')
        name = self.request.input.get('name')
        pid = self.request.input.get('pid') or 0
        all_pids = self.request.input.get('all_pids')
        timeout = self.request.input.get('timeout') or None

        channel = self.request.input.get('channel')
        data_format = self.request.input.get('data_format')
        transport = self.request.input.get('transport')
        expiration = self.request.input.get(
            'expiration') or BROKER.DEFAULT_EXPIRATION

        if name and id:
            raise ZatoException(
                'Cannot accept both id:`{}` and name:`{}`'.format(id, name))

        if self.request.input.get('async'):

            if id:
                impl_name = self.server.service_store.id_to_impl_name[id]
                name = self.server.service_store.service_data(
                    impl_name)['name']

            # If PID is given on input it means we must invoke this particular server process by it ID
            if pid and pid != self.server.pid:
                response = self.server.invoke_by_pid(name, payload, pid)
            else:
                response = self.invoke_async(name, payload, channel,
                                             data_format, transport,
                                             expiration)

        else:
            # This branch the same as above in async branch, except in async there was no all_pids

            # It is possible that we were given the all_pids flag on input but we know
            # ourselves that there is only one process, the current one, so we can just
            # invoke it directly instead of going through IPC.
            if all_pids and self.server.fs_server_config.main.gunicorn_workers > 1:
                use_all_pids = True
            else:
                use_all_pids = False

            if use_all_pids:
                args = (name, payload, timeout) if timeout else (name, payload)
                response = dumps(self.server.invoke_all_pids(*args))
            else:
                if pid and pid != self.server.pid:
                    response = self.server.invoke(name,
                                                  payload,
                                                  pid=pid,
                                                  data_format=data_format)
                else:
                    func, id_ = (self.invoke,
                                 name) if name else (self.invoke_by_id, id)
                    response = func(id_,
                                    payload,
                                    channel,
                                    data_format,
                                    transport,
                                    serialize=True)

        if isinstance(response, basestring):
            if response:
                response = response if isinstance(
                    response, bytes) else response.encode('utf8')
                self.response.payload.response = b64encode(
                    response) if response else ''
Example #19
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
Example #20
0
    def update_handle(self,
                      set_response_func,
                      service,
                      raw_request,
                      channel,
                      data_format,
                      transport,
                      server,
                      broker_client,
                      worker_store,
                      cid,
                      simple_io_config,
                      _utcnow=datetime.utcnow,
                      _call_hook_with_service=call_hook_with_service,
                      _call_hook_no_service=call_hook_no_service,
                      _CHANNEL_SCHEDULER=CHANNEL.SCHEDULER,
                      _pattern_channels=(CHANNEL.FANOUT_CALL,
                                         CHANNEL.PARALLEL_EXEC_CALL),
                      *args,
                      **kwargs):

        wsgi_environ = kwargs.get('wsgi_environ', {})
        payload = wsgi_environ.get('zato.request.payload')

        # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element
        # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates
        # to False so we'd be parsing the payload again superfluously.
        if not isinstance(payload, ObjectifiedElement) and not payload:
            payload = payload_from_request(cid, raw_request, data_format,
                                           transport)

        job_type = kwargs.get('job_type')
        channel_params = kwargs.get('channel_params', {})
        merge_channel_params = kwargs.get('merge_channel_params', True)
        params_priority = kwargs.get('params_priority',
                                     PARAMS_PRIORITY.DEFAULT)

        service.update(service,
                       channel,
                       server,
                       broker_client,
                       worker_store,
                       cid,
                       payload,
                       raw_request,
                       transport,
                       simple_io_config,
                       data_format,
                       wsgi_environ,
                       job_type=job_type,
                       channel_params=channel_params,
                       merge_channel_params=merge_channel_params,
                       params_priority=params_priority,
                       in_reply_to=wsgi_environ.get(
                           'zato.request_ctx.in_reply_to', None),
                       environ=kwargs.get('environ'),
                       wmq_ctx=kwargs.get('wmq_ctx'))

        # It's possible the call will be completely filtered out. The uncommonly looking not self.accept shortcuts
        # if ServiceStore replaces self.accept with None in the most common case of this method's not being
        # implemented by user services.
        if (not self.accept) or service.accept():

            # Assume everything goes fine
            e, exc_formatted = None, None

            try:

                if service.server.component_enabled.stats:
                    service.usage = service.kvdb.conn.incr('{}{}'.format(
                        KVDB.SERVICE_USAGE, service.name))
                service.invocation_time = _utcnow()

                # All hooks are optional so we check if they have not been replaced with None by ServiceStore.

                # Call before job hooks if any are defined and we are called from the scheduler
                if service._has_before_job_hooks and self.channel.type == _CHANNEL_SCHEDULER:
                    for elem in service._before_job_hooks:
                        if elem:
                            _call_hook_with_service(elem, service)

                # Called before .handle - catches exceptions
                if service.before_handle:
                    _call_hook_no_service(service.before_handle)

                # Called before .handle - does not catch exceptions
                if service.validate_input:
                    service.validate_input()

                # This is the place where the service is invoked
                self._invoke(service, channel)

                # Called after .handle - does not catch exceptions
                if service.validate_output:
                    service.validate_output()

                # Called after .handle - catches exceptions
                if service.after_handle:
                    _call_hook_no_service(service.after_handle)

                # Call after job hooks if any are defined and we are called from the scheduler
                if service._has_after_job_hooks and self.channel.type == _CHANNEL_SCHEDULER:
                    for elem in service._after_job_hooks:
                        if elem:
                            _call_hook_with_service(elem, service)

                # Internal method - always defined and called
                service.post_handle()

                # Optional, almost never overridden.
                if service.finalize_handle:
                    _call_hook_no_service(service.finalize_handle)

            except Exception, e:
                exc_formatted = format_exc(e)
                logger.warn(exc_formatted)

            finally:
Example #21
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):
        """ 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
Example #22
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
Example #23
0
    def update_handle(self, set_response_func, service, raw_request, channel,
                      data_format, transport, server, broker_client,
                      worker_store, cid, simple_io_config, *args, **kwargs):

        wsgi_environ = kwargs.get('wsgi_environ', {})
        payload = wsgi_environ.get('zato.request.payload')

        # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element
        # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates
        # to False so we'd be parsing the payload again superfluously.
        if not isinstance(payload, ObjectifiedElement) and not payload:
            payload = payload_from_request(cid, raw_request, data_format,
                                           transport)

        job_type = kwargs.get('job_type')
        channel_params = kwargs.get('channel_params', {})
        merge_channel_params = kwargs.get('merge_channel_params', True)
        params_priority = kwargs.get('params_priority',
                                     PARAMS_PRIORITY.DEFAULT)
        serialize = kwargs.get('serialize')
        as_bunch = kwargs.get('as_bunch')
        channel_item = kwargs.get('channel_item')

        service.update(service,
                       channel,
                       server,
                       broker_client,
                       worker_store,
                       cid,
                       payload,
                       raw_request,
                       transport,
                       simple_io_config,
                       data_format,
                       wsgi_environ,
                       job_type=job_type,
                       channel_params=channel_params,
                       merge_channel_params=merge_channel_params,
                       params_priority=params_priority)

        # Depending on whether this is a pass-through service or not we invoke
        # the target services or the one we have in hand right now. Note that
        # hooks are always invoked for the one we have a handle to right now
        # even if it's a pass-through one.

        service.pre_handle()
        service.call_hooks('before')

        if service.passthrough_to:
            sio = getattr(service, 'SimpleIO', None)
            return self.invoke(service.passthrough_to,
                               payload,
                               channel,
                               data_format,
                               transport,
                               serialize,
                               as_bunch,
                               sio=sio,
                               from_passthrough=True,
                               passthrough_request=self.request,
                               set_response_func=set_response_func,
                               channel_item=channel_item)
        else:
            service.validate_input()

            #
            # If channel is HTTP and there are any per-HTTP verb methods, it means we want for the service to be a REST target.
            # Let's say it is POST. If we have handle_POST, it is invoked. If there is no handle_POST,
            # '405 Method Not Allowed is returned'.
            #
            # However, if we have 'handle' only, it means this is always invoked and no default 405 is returned.
            #
            # In short, implement handle_* if you want REST behaviour. Otherwise, keep everything in handle.
            #

            # Ok, this is HTTP
            if channel in (CHANNEL.HTTP_SOAP, CHANNEL.INVOKE):

                # We have at least one per-HTTP verb handler
                if service.http_method_handlers:

                    # But do we have any handler matching current request's verb?
                    if service.request.http.method in service.http_method_handlers:

                        # Yes, call the handler
                        service.http_method_handlers[
                            service.request.http.method](service)

                    # No, return 405
                    else:
                        service.response.status_code = METHOD_NOT_ALLOWED

                # We have no customer handlers so we always call 'handle'
                else:
                    service.handle()

            # It's not HTTP so we simply call 'handle'
            else:
                service.handle()

            service.validate_output()

        service.call_hooks('after')
        service.post_handle()
        service.call_hooks('finalize')

        return set_response_func(service,
                                 data_format=data_format,
                                 transport=transport,
                                 **kwargs)
Example #24
0
    def update_handle(self, set_response_func, service, raw_request, channel, data_format,
            transport, server, broker_client, worker_store, cid, simple_io_config, *args, **kwargs):

        wsgi_environ = kwargs.get('wsgi_environ', {})
        payload = wsgi_environ.get('zato.request.payload')

        # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element
        # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates
        # to False so we'd be parsing the payload again superfluously.
        if not isinstance(payload, ObjectifiedElement) and not payload:
            payload = payload_from_request(cid, raw_request, data_format, transport)

        job_type = kwargs.get('job_type')
        channel_params = kwargs.get('channel_params', {})
        merge_channel_params = kwargs.get('merge_channel_params', True)
        params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT)
        serialize = kwargs.get('serialize')
        as_bunch = kwargs.get('as_bunch')
        channel_item = kwargs.get('channel_item')

        service.update(service, channel, server, broker_client,
            worker_store, cid, payload, raw_request, transport,
            simple_io_config, data_format, wsgi_environ,
            job_type=job_type,
            channel_params=channel_params,
            merge_channel_params=merge_channel_params,
            params_priority=params_priority)

        # Depending on whether this is a pass-through service or not we invoke
        # the target services or the one we have in hand right now. Note that
        # hooks are always invoked for the one we have a handle to right now
        # even if it's a pass-through one.

        service.pre_handle()
        service.call_hooks('before')

        if service.passthrough_to:
            sio = getattr(service, 'SimpleIO', None)
            return self.invoke(service.passthrough_to, payload, channel, data_format, transport, serialize, as_bunch,
                sio=sio, from_passthrough=True, passthrough_request=self.request, set_response_func=set_response_func,
                channel_item=channel_item)
        else:
            service.validate_input()

            #
            # If channel is HTTP and there are any per-HTTP verb methods, it means we want for the service to be a REST target.
            # Let's say it is POST. If we have handle_POST, it is invoked. If there is no handle_POST,
            # '405 Method Not Allowed is returned'.
            #
            # However, if we have 'handle' only, it means this is always invoked and no default 405 is returned.
            #
            # In short, implement handle_* if you want REST behaviour. Otherwise, keep everything in handle.
            #

            # Ok, this is HTTP
            if channel == CHANNEL.HTTP_SOAP:

                # We have at least one per-HTTP verb handler
                if service.http_method_handlers:

                    # But do we have any handler matching current request's verb?
                    if service.request.http.method in service.http_method_handlers:

                        # Yes, call the handler
                        service.http_method_handlers[service.request.http.method](service)

                    # No, return 405
                    else:
                        service.response.status_code = METHOD_NOT_ALLOWED

                # We have no customer handlers so we always call 'handle'
                else:
                    service.handle()

            # It's not HTTP so we simply call 'handle'
            else:
                service.handle()

            service.validate_output()

        service.call_hooks('after')
        service.post_handle()
        service.call_hooks('finalize')

        return set_response_func(service, data_format=data_format, transport=transport, **kwargs)
Example #25
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
Example #26
0
    def update_handle(self,
        set_response_func, # type: Callable
        service,       # type: Service
        raw_request,   # type: object
        channel,       # type: ChannelInfo
        data_format,   # type: unicode
        transport,     # type: unicode
        server,        # type: ParallelServer
        broker_client, # type: BrokerClient
        worker_store,  # type: WorkerStore
        cid,           # type: unicode
        simple_io_config, # type: dict
        _utcnow=datetime.utcnow,
        _call_hook_with_service=call_hook_with_service,
        _call_hook_no_service=call_hook_no_service,
        _CHANNEL_SCHEDULER=CHANNEL.SCHEDULER,
        _CHANNEL_SERVICE=CHANNEL.SERVICE,
        _pattern_channels=(CHANNEL.FANOUT_CALL, CHANNEL.PARALLEL_EXEC_CALL),
        *args, **kwargs):

        wsgi_environ = kwargs.get('wsgi_environ', {})
        payload = wsgi_environ.get('zato.request.payload')

        # Here's an edge case. If a SOAP request has a single child in Body and this child is an empty element
        # (though possibly with attributes), checking for 'not payload' alone won't suffice - this evaluates
        # to False so we'd be parsing the payload again superfluously.
        if not isinstance(payload, ObjectifiedElement) and not payload:
            payload = payload_from_request(cid, raw_request, data_format, transport)

        job_type = kwargs.get('job_type')
        channel_params = kwargs.get('channel_params', {})
        merge_channel_params = kwargs.get('merge_channel_params', True)
        params_priority = kwargs.get('params_priority', PARAMS_PRIORITY.DEFAULT)

        service.update(service, channel, server, broker_client,
            worker_store, cid, payload, raw_request, transport, simple_io_config, data_format, wsgi_environ,
            job_type=job_type, channel_params=channel_params,
            merge_channel_params=merge_channel_params, params_priority=params_priority,
            in_reply_to=wsgi_environ.get('zato.request_ctx.in_reply_to', None), environ=kwargs.get('environ'),
            wmq_ctx=kwargs.get('wmq_ctx'))

        # It's possible the call will be completely filtered out. The uncommonly looking not self.accept shortcuts
        # if ServiceStore replaces self.accept with None in the most common case of this method's not being
        # implemented by user services.
        if (not self.accept) or service.accept():

            # Assumes it goes fine by default
            e, exc_formatted = None, None

            try:

                # Check rate limiting first
                if self._has_rate_limiting:
                    self.server.rate_limiting.check_limit(self.cid, _CHANNEL_SERVICE, self.name,
                        self.wsgi_environ['zato.http.remote_addr'])

                if service.server.component_enabled.stats:
                    service.usage = service.kvdb.conn.incr('{}{}'.format(KVDB.SERVICE_USAGE, service.name))
                service.invocation_time = _utcnow()

                # Check if there is a JSON Schema validator attached to the service and if so,
                # validate input before proceeding any further.
                if service._json_schema_validator and service._json_schema_validator.is_initialized:
                    validation_result = service._json_schema_validator.validate(cid, self.request.payload)
                    if not validation_result:
                        error = validation_result.get_error()

                        error_msg = error.get_error_message()
                        error_msg_details = error.get_error_message(True)

                        raise JSONSchemaValidationException(cid, CHANNEL.SERVICE, service.name,
                            error.needs_err_details, error_msg, error_msg_details)

                # All hooks are optional so we check if they have not been replaced with None by ServiceStore.

                # Call before job hooks if any are defined and we are called from the scheduler
                if service._has_before_job_hooks and self.channel.type == _CHANNEL_SCHEDULER:
                    for elem in service._before_job_hooks:
                        if elem:
                            _call_hook_with_service(elem, service)

                # Called before .handle - catches exceptions
                if service.before_handle:
                    _call_hook_no_service(service.before_handle)

                # Called before .handle - does not catch exceptions
                if service.validate_input:
                    service.validate_input()

                # This is the place where the service is invoked
                self._invoke(service, channel)

                # Called after .handle - does not catch exceptions
                if service.validate_output:
                    service.validate_output()

                # Called after .handle - catches exceptions
                if service.after_handle:
                    _call_hook_no_service(service.after_handle)

                # Call after job hooks if any are defined and we are called from the scheduler
                if service._has_after_job_hooks and self.channel.type == _CHANNEL_SCHEDULER:
                    for elem in service._after_job_hooks:
                        if elem:
                            _call_hook_with_service(elem, service)

                # Internal method - always defined and called
                service.post_handle()

                # Optional, almost never overridden.
                if service.finalize_handle:
                    _call_hook_no_service(service.finalize_handle)

            except Exception as ex:
                e = ex
                exc_formatted = format_exc()
                logger.warn(exc_formatted)

            finally:
                try:
                    response = set_response_func(service, data_format=data_format, transport=transport, **kwargs)

                    # If this was fan-out/fan-in we need to always notify our callbacks no matter the result
                    if channel in _pattern_channels:
                        func = self.patterns.fanout.on_call_finished if channel == CHANNEL.FANOUT_CALL else \
                            self.patterns.parallel.on_call_finished
                        spawn(func, self, service.response.payload, exc_formatted)

                except Exception as resp_e:

                    # If we already have an exception around, log the new one but don't overwrite the old one with it.
                    logger.warn('Exception in service `%s`, e:`%s`', service.name, format_exc())

                    if e:
                        if isinstance(e, Reportable):
                            raise e
                        else:
                            raise Exception(exc_formatted)
                    raise resp_e

                else:
                    if e:
                        raise e if isinstance(e, Exception) else Exception(e)

        # We don't accept it but some response needs to be returned anyway.
        else:
            response = service.response
            response.payload = ''
            response.status_code = BAD_REQUEST

        return response