Beispiel #1
0
 def __init__(self, transport, config):
     super(DeliverShortMessageProcessor, self).__init__(transport, config)
     self.transport = transport
     self.redis = transport.redis
     self.config = self.CONFIG_CLASS(config, static=True)
     self.session_manager = SessionManager(
         self.redis, max_session_length=self.config.max_session_length)
Beispiel #2
0
 def __init__(self, transport, config):
     super(SubmitShortMessageProcessor, self).__init__(transport, config)
     self.transport = transport
     self.redis = transport.redis
     self.config = self.CONFIG_CLASS(config, static=True)
     self.session_manager = SessionManager(
         self.redis, max_session_length=self.config.max_session_length)
Beispiel #3
0
 def setup_transport(self):
     yield super(DmarkUssdTransport, self).setup_transport()
     config = self.get_static_config()
     r_prefix = "vumi.transports.dmark_ussd:%s" % self.transport_name
     self.session_manager = yield SessionManager.from_redis_config(
         config.redis_manager, r_prefix,
         max_session_length=config.ussd_session_timeout)
Beispiel #4
0
    def setup_transport(self):
        super(TrueAfricanUssdTransport, self).setup_transport()
        config = self.get_static_config()

        # Session handling
        key_prefix = "trueafrican:%s" % self.transport_name
        self.session_manager = yield SessionManager.from_redis_config(
            config.redis_manager, key_prefix, config.session_timeout)

        # XMLRPC Resource
        self.web_resource = reactor.listenTCP(config.port,
                                              server.Site(
                                                  XmlRpcResource(self)),
                                              interface=config.interface)

        # request tracking
        self.clock = self.get_clock()
        self._requests = {}
        self.request_timeout = config.request_timeout
        self.timeout_task = LoopingCall(self.request_timeout_cb)
        self.timeout_task.clock = self.clock
        self.timeout_task_d = self.timeout_task.start(
            self.TIMEOUT_TASK_INTERVAL, now=False)
        self.timeout_task_d.addErrback(log.err,
                                       "Request timeout handler failed")
Beispiel #5
0
    def setup_transport(self):
        super(TrueAfricanUssdTransport, self).setup_transport()
        config = self.get_static_config()

        # Session handling
        key_prefix = "trueafrican:%s" % self.transport_name
        self.session_manager = yield SessionManager.from_redis_config(
            config.redis_manager,
            key_prefix,
            config.session_timeout
        )

        # XMLRPC Resource
        self.web_resource = reactor.listenTCP(
            config.port,
            server.Site(XmlRpcResource(self)),
            interface=config.interface
        )

        # request tracking
        self.clock = self.get_clock()
        self._requests = {}
        self.request_timeout = config.request_timeout
        self.timeout_task = LoopingCall(self.request_timeout_cb)
        self.timeout_task.clock = self.clock
        self.timeout_task_d = self.timeout_task.start(
            self.TIMEOUT_TASK_INTERVAL,
            now=False
        )
        self.timeout_task_d.addErrback(
            log.err,
            "Request timeout handler failed"
        )
Beispiel #6
0
 def setup_transport(self):
     super(MtechUssdTransport, self).setup_transport()
     r_config = self.config.get('redis_manager', {})
     r_prefix = "mtech_ussd:%s" % self.transport_name
     session_timeout = int(self.config.get("ussd_session_timeout", 600))
     self.session_manager = yield SessionManager.from_redis_config(
             r_config, r_prefix, max_session_length=session_timeout)
Beispiel #7
0
 def setup_transport(self):
     super(CellulantTransport, self).setup_transport()
     r_config = self.config.get('redis_manager', {})
     r_prefix = "vumi.transports.cellulant:%s" % self.transport_name
     session_timeout = int(self.config.get("ussd_session_timeout", 600))
     self.session_manager = yield SessionManager.from_redis_config(
         r_config, r_prefix, session_timeout)
Beispiel #8
0
 def setup_transport(self):
     super(CellulantTransport, self).setup_transport()
     r_config = self.config.get('redis_manager', {})
     r_prefix = "vumi.transports.cellulant:%s" % self.transport_name
     session_timeout = int(self.config.get("ussd_session_timeout", 600))
     self.session_manager = yield SessionManager.from_redis_config(
         r_config, r_prefix, session_timeout)
Beispiel #9
0
 def setup_transport(self):
     super(MtechUssdTransport, self).setup_transport()
     r_config = self.config.get('redis_manager', {})
     r_prefix = "mtech_ussd:%s" % self.transport_name
     session_timeout = int(self.config.get("ussd_session_timeout", 600))
     self.session_manager = yield SessionManager.from_redis_config(
         r_config, r_prefix, max_session_length=session_timeout)
Beispiel #10
0
    def setup_transport(self):
        log.msg('Starting the OperaInboundTransport config: %s' %
            self.transport_name)
        r_prefix = "%(transport_name)s@%(url)s" % self.config
        self.session_manager = yield SessionManager.from_redis_config(
            self.r_config, r_prefix, self.message_id_lifetime)

        self.proxy = xmlrpc.Proxy(self.opera_url)
        self.default_values = {
            'Service': self.opera_service,
            'Password': self.opera_password,
            'Channel': self.opera_channel,
        }

        # start receipt web resource
        self.web_resource = yield self.start_web_resources(
            [
                (OperaReceiptResource(self.handle_raw_incoming_receipt),
                 self.web_receipt_path),
                (OperaReceiveResource(self.publish_message),
                 self.web_receive_path),
                (OperaHealthResource(), 'health'),
            ],
            self.web_port
        )
Beispiel #11
0
 def setup_transport(self):
     super(AirtelUSSDTransport, self).setup_transport()
     config = self.get_static_config()
     self.session_manager = yield SessionManager.from_redis_config(
         config.redis_manager, self.get_session_key_prefix(),
         config.ussd_session_timeout)
     if config.to_addr_pattern is not None:
         self.to_addr_re = re.compile(config.to_addr_pattern)
Beispiel #12
0
 def setup_application(self):
     """Start the worker"""
     config = self.get_static_config()
     # Connect to Redis
     r_prefix = "hangman_game:%s:%s" % (config.transport_name,
                                        config.worker_name)
     self.session_manager = yield SessionManager.from_redis_config(
         config.redis_manager, r_prefix)
Beispiel #13
0
 def setup_application(self):
     """Start the worker"""
     config = self.get_static_config()
     # Connect to Redis
     r_prefix = "hangman_game:%s:%s" % (
         config.transport_name, config.worker_name)
     self.session_manager = yield SessionManager.from_redis_config(
         config.redis_manager, r_prefix)
Beispiel #14
0
 def setup_transport(self):
     yield super(DmarkUssdTransport, self).setup_transport()
     config = self.get_static_config()
     r_prefix = "vumi.transports.dmark_ussd:%s" % self.transport_name
     self.session_manager = yield SessionManager.from_redis_config(
         config.redis_manager,
         r_prefix,
         max_session_length=config.ussd_session_timeout)
Beispiel #15
0
 def setup_transport(self):
     super(AirtelUSSDTransport, self).setup_transport()
     config = self.get_static_config()
     self.session_manager = yield SessionManager.from_redis_config(
         config.redis_manager, self.get_session_key_prefix(),
         config.ussd_session_timeout)
     if config.to_addr_pattern is not None:
         self.to_addr_re = re.compile(config.to_addr_pattern)
Beispiel #16
0
    def setup_transport(self):
        super(ImiMobileUssdTransport, self).setup_transport()

        # configure session manager
        r_config = self.config.get('redis_manager', {})
        r_prefix = "vumi.transports.imimobile_ussd:%s" % self.transport_name
        session_timeout = int(self.config.get("ussd_session_timeout", 600))
        self.session_manager = yield SessionManager.from_redis_config(
            r_config, r_prefix, max_session_length=session_timeout)
Beispiel #17
0
    def setup_transport(self):
        super(ImiMobileUssdTransport, self).setup_transport()

        # configure session manager
        r_config = self.config.get('redis_manager', {})
        r_prefix = "vumi.transports.imimobile_ussd:%s" % self.transport_name
        session_timeout = int(self.config.get("ussd_session_timeout", 600))
        self.session_manager = yield SessionManager.from_redis_config(
            r_config, r_prefix, max_session_length=session_timeout)
Beispiel #18
0
    def setup_transport(self):
        config = self.get_static_config()
        self.client_factory = Factory.forProtocol(self.protocol_class)
        self.client_factory.vumi_transport = self

        prefix = "%s:ussd_codes" % (config.transport_name,)
        self.session_manager = yield SessionManager.from_redis_config(
            config.redis_manager, prefix, config.ussd_session_lifetime)
        self.client_service = self.get_service(
            config.twisted_endpoint, self.client_factory)
 def setup_application(self):
     """Application specific setup"""
     self.app_config = self.get_static_config()
     self.server = TwilioAPIServer(self, self.app_config.api_version)
     path = os.path.join(
         self.app_config.web_path, self.app_config.api_version)
     self.webserver = self.start_web_resources([
         (self.server.app.resource(), path)],
         self.app_config.web_port)
     redis = yield TxRedisManager.from_config(self.app_config.redis_manager)
     self.session_manager = SessionManager(
         redis, self.app_config.redis_timeout)
     self.session_lookup = SessionIDLookup(
         redis, self.app_config.redis_timeout,
         self.app_config.session_lookup_namespace)
Beispiel #20
0
    def setup_transport(self):
        config = self.get_static_config()
        self.user_termination_response = config.user_termination_response

        r_prefix = "vumi.transports.mtn_nigeria:%s" % self.transport_name
        self.session_manager = yield SessionManager.from_redis_config(
            config.redis_manager, r_prefix, config.session_timeout_period)

        self.factory = MtnNigeriaUssdClientFactory(
            vumi_transport=self,
            username=config.username,
            password=config.password,
            application_id=config.application_id,
            enquire_link_interval=config.enquire_link_interval,
            timeout_period=config.timeout_period)
        self.client_connector = reactor.connectTCP(config.server_hostname,
                                                   config.server_port,
                                                   self.factory)
        log.msg('Connecting')
Beispiel #21
0
    def setup_transport(self):
        config = self.get_static_config()
        self.user_termination_response = config.user_termination_response

        r_prefix = "vumi.transports.mtn_nigeria:%s" % self.transport_name
        self.session_manager = yield SessionManager.from_redis_config(
            config.redis_manager, r_prefix,
            config.session_timeout_period)

        self.factory = MtnNigeriaUssdClientFactory(
            vumi_transport=self,
            username=config.username,
            password=config.password,
            application_id=config.application_id,
            enquire_link_interval=config.enquire_link_interval,
            timeout_period=config.timeout_period)
        self.client_connector = reactor.connectTCP(
            config.server_hostname, config.server_port, self.factory)
        log.msg('Connecting')
Beispiel #22
0
    def setup_transport(self):
        log.msg('Starting the OperaInboundTransport config: %s' %
                self.transport_name)
        r_prefix = "%(transport_name)s@%(url)s" % self.config
        self.session_manager = yield SessionManager.from_redis_config(
            self.r_config, r_prefix, self.message_id_lifetime)

        self.proxy = xmlrpc.Proxy(self.opera_url)
        self.default_values = {
            'Service': self.opera_service,
            'Password': self.opera_password,
            'Channel': self.opera_channel,
        }

        # start receipt web resource
        self.web_resource = yield self.start_web_resources([
            (OperaReceiptResource(
                self.handle_raw_incoming_receipt), self.web_receipt_path),
            (OperaReceiveResource(
                self.publish_message), self.web_receive_path),
            (OperaHealthResource(), 'health'),
        ], self.web_port)
Beispiel #23
0
    def setup_transport(self):
        """
        Transport specific setup - it sets up a connection.
        """
        self._requests = {}
        self._requests_deferreds = {}
        self.callLater = reactor.callLater

        config = self.get_static_config()
        self.endpoint = config.twisted_endpoint
        self.timeout = config.timeout

        r_prefix = "vumi.transports.mtn_rwanda:%s" % self.transport_name
        self.session_manager = yield SessionManager.from_redis_config(
            config.redis_manager, r_prefix,
            config.session_timeout_period)

        self.factory = build_web_site({
            config.health_path: HttpRpcHealthResource(self),
            config.web_path: MTNRwandaXMLRPCResource(self),
        })

        self.xmlrpc_server = yield self.endpoint.listen(self.factory)
Beispiel #24
0
    def setup_transport(self):
        """
        Transport specific setup - it sets up a connection.
        """
        self._requests = {}
        self._requests_deferreds = {}
        self.callLater = reactor.callLater

        config = self.get_static_config()
        self.endpoint = config.twisted_endpoint
        self.timeout = config.timeout

        r_prefix = "vumi.transports.mtn_rwanda:%s" % self.transport_name
        self.session_manager = yield SessionManager.from_redis_config(
            config.redis_manager, r_prefix, config.session_timeout_period)

        self.factory = build_web_site({
            config.health_path:
            HttpRpcHealthResource(self),
            config.web_path:
            MTNRwandaXMLRPCResource(self),
        })

        self.xmlrpc_server = yield self.endpoint.listen(self.factory)
Beispiel #25
0
class DeliverShortMessageProcessor(default.DeliverShortMessageProcessor):

    CONFIG_CLASS = DeliverShortMessageProcessorConfig

    # NOTE: these keys are hexidecimal because of python-smpp encoding
    #       quirkiness
    ussd_service_op_map = {
        '01': 'new',
        '12': 'continue',
        '81': 'close',  # user abort
    }

    def __init__(self, transport, config):
        super(DeliverShortMessageProcessor, self).__init__(transport, config)
        self.transport = transport
        self.log = transport.log
        self.redis = transport.redis
        self.config = self.CONFIG_CLASS(config, static=True)
        self.session_manager = SessionManager(
            self.redis, max_session_length=self.config.max_session_length)

    @inlineCallbacks
    def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts):
        service_op = pdu_opts['ussd_service_op']
        mica_session_identifier = pdu_opts['user_message_reference']
        vumi_session_identifier = make_vumi_session_identifier(
            pdu_params['source_addr'], mica_session_identifier)

        session_event = self.ussd_service_op_map.get(service_op)

        if session_event == 'new':
            # PSSR request. Let's assume it means a new session.
            ussd_code = pdu_params['short_message']
            content = None

            yield self.session_manager.create_session(
                vumi_session_identifier, ussd_code=ussd_code)

        elif session_event == 'close':
            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = None

            yield self.session_manager.clear_session(vumi_session_identifier)

        else:
            if session_event != 'continue':
                self.log.warning((
                    'Received unknown %r ussd_service_op, assuming continue.')
                    % (service_op,))
                session_event = 'continue'

            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = self.dcs_decode(
                pdu_params['short_message'], pdu_params['data_coding'])

        # This is stashed on the message and available when replying
        # with a `submit_sm`
        session_info = {
            'ussd_service_op': service_op,
            'session_identifier': mica_session_identifier,
        }

        result = yield self.handle_short_message_content(
            source_addr=pdu_params['source_addr'],
            destination_addr=ussd_code,
            short_message=content,
            message_type='ussd',
            session_event=session_event,
            session_info=session_info)
        returnValue(result)
Beispiel #26
0
class SubmitShortMessageProcessor(default.SubmitShortMessageProcessor):

    CONFIG_CLASS = SubmitShortMessageProcessorConfig

    # NOTE: these values are hexidecimal because of python-smpp encoding
    #       quirkiness
    ussd_service_op_map = {
        'continue': '02',
        'close': '17',  # end
    }

    def __init__(self, transport, config):
        super(SubmitShortMessageProcessor, self).__init__(transport, config)
        self.transport = transport
        self.redis = transport.redis
        self.config = self.CONFIG_CLASS(config, static=True)
        self.session_manager = SessionManager(
            self.redis, max_session_length=self.config.max_session_length)

    @inlineCallbacks
    def handle_outbound_message(self, message, service):
        to_addr = message['to_addr']
        from_addr = message['from_addr']
        text = message['content']
        if text is None:
            text = u""
        vumi_message_id = message['message_id']

        session_event = message['session_event']
        transport_type = message['transport_type']
        optional_parameters = {}

        if transport_type == 'ussd':
            continue_session = (
                session_event != TransportUserMessage.SESSION_CLOSE)
            session_info = message['transport_metadata'].get(
                'session_info', {})
            mica_session_identifier = session_info.get(
                'session_identifier', '')
            vumi_session_identifier = make_vumi_session_identifier(
                to_addr, mica_session_identifier)

            service_op = self.ussd_service_op_map[('continue'
                                                   if continue_session
                                                   else 'close')]
            optional_parameters.update({
                'ussd_service_op': service_op,
                'user_message_reference': (
                    str(mica_session_identifier).zfill(2)),
            })

            if not continue_session:
                yield self.session_manager.clear_session(
                    vumi_session_identifier)

        resp = yield self.send_short_message(
            service,
            vumi_message_id,
            to_addr.encode('ascii'),
            text.encode(self.config.submit_sm_encoding),
            data_coding=self.config.submit_sm_data_coding,
            source_addr=from_addr.encode('ascii'),
            optional_parameters=optional_parameters)

        returnValue(resp)
Beispiel #27
0
class ContentKeywordRouter(SimpleDispatchRouter):
    """Router that dispatches based on the first word of the message
    content. In the context of SMSes the first word is sometimes called
    the 'keyword'.

    :param dict keyword_mappings:
        Mapping from application transport names to simple keywords.
        This is purely a convenience for constructing simple routing
        rules. The rules generated from this option are appened to
        the of rules supplied via the *rules* option.

    :param list rules:
        A list of routing rules. A routing rule is a dictionary. It
        must have `app` and `keyword` keys and may contain `to_addr`
        and `prefix` keys. If a message's first word matches a given
        keyword, the message is sent to the application listening on
        the transport name given by the value of `app`. If a 'to_addr'
        key is supplied, the message `to_addr` must also match the
        value of the 'to_addr' key. If a 'prefix' is supplied, the
        message `from_addr` must *start with* the value of the
        'prefix' key.

    :param str fallback_application:
        Optional application transport name to forward inbound messages
        that match no rule to. If omitted, unrouted inbound messages
        are just logged.

    :param dict transport_mappings:
        Mapping from message `from_addr` values to transports names.
        If a message's from_addr matches a given from_addr, the
        message is sent to the associated transport.

    :param int expire_routing_memory:
        Time in seconds before outbound message's ids are expired from
        the redis routing store. Outbound message ids are stored along
        with the transport_name the message came in on and are used to
        route events such as acknowledgements and delivery reports
        back to the application that sent the outgoing
        message. Default is seven days.
    """

    DEFAULT_ROUTING_TIMEOUT = 60 * 60 * 24 * 7  # 7 days

    def setup_routing(self):
        self.r_config = self.config.get('redis_manager', {})
        self.r_prefix = self.config['dispatcher_name']

        self.rules = []
        for rule in self.config.get('rules', []):
            if 'keyword' not in rule or 'app' not in rule:
                raise ConfigError("Rule definition %r must contain values for"
                                  " both 'app' and 'keyword'" % rule)
            rule = rule.copy()
            rule['keyword'] = rule['keyword'].lower()
            self.rules.append(rule)
        keyword_mappings = self.config.get('keyword_mappings', {})
        for transport_name, keyword in keyword_mappings.items():
            self.rules.append({'app': transport_name,
                               'keyword': keyword.lower()})
        self.fallback_application = self.config.get('fallback_application')
        self.transport_mappings = self.config['transport_mappings']
        self.expire_routing_timeout = int(self.config.get(
            'expire_routing_memory', self.DEFAULT_ROUTING_TIMEOUT))

        # FIXME: The following is a hack to deal with sync-only setup.
        self._redis_d = TxRedisManager.from_config(self.r_config)
        self._redis_d.addCallback(lambda m: m.sub_manager(self.r_prefix))
        self._redis_d.addCallback(self._setup_redis)

    def _setup_redis(self, redis):
        self.redis = redis
        self.session_manager = SessionManager(
            self.redis, self.expire_routing_timeout)

    def get_message_key(self, message):
        return 'message:%s' % (message,)

    def publish_transport(self, name, msg):
        self.dispatcher.publish_outbound_message(name, msg)

    def publish_exposed_inbound(self, name, msg):
        self.dispatcher.publish_inbound_message(name, msg)

    def publish_exposed_event(self, name, msg):
        self.dispatcher.publish_inbound_event(name, msg)

    def is_msg_matching_routing_rules(self, keyword, msg, rule):
        return all([keyword == rule['keyword'],
                    (not 'to_addr' in rule) or
                    (msg['to_addr'] == rule['to_addr']),
                    (not 'prefix' in rule) or
                    (msg['from_addr'].startswith(rule['prefix']))])

    def dispatch_inbound_message(self, msg):
        keyword = get_first_word(msg['content']).lower()
        matched = False
        for rule in self.rules:
            if self.is_msg_matching_routing_rules(keyword, msg, rule):
                matched = True
                # copy message so that the middleware doesn't see a particular
                # message instance multiple times
                self.publish_exposed_inbound(rule['app'], msg.copy())
        if not matched:
            if self.fallback_application is not None:
                self.publish_exposed_inbound(self.fallback_application, msg)
            else:
                log.error(DispatcherError(
                    'Message could not be routed: %r' % (msg,)))

    @inlineCallbacks
    def dispatch_inbound_event(self, msg):
        yield self._redis_d  # Horrible hack to ensure we have it setup.
        message_key = self.get_message_key(msg['user_message_id'])
        session = yield self.session_manager.load_session(message_key)
        name = session.get('name')
        if not name:
            log.error(DispatcherError(
                "No transport_name for return route found in Redis"
                " while dispatching transport event for message %s"
                % (msg['user_message_id'],)))
        try:
            self.publish_exposed_event(name, msg)
        except:
            log.error(DispatcherError("No publishing route for %s" % (name,)))

    @inlineCallbacks
    def dispatch_outbound_message(self, msg):
        yield self._redis_d  # Horrible hack to ensure we have it setup.
        transport_name = self.transport_mappings.get(msg['from_addr'])
        if transport_name is not None:
            self.publish_transport(transport_name, msg)
            message_key = self.get_message_key(msg['message_id'])
            yield self.session_manager.create_session(
                message_key, name=msg['transport_name'])
        else:
            log.error(DispatcherError(
                "No transport for %s" % (msg['from_addr'],)))
Beispiel #28
0
 def _setup_redis(self, redis):
     self.redis = redis
     self.session_manager = SessionManager(
         self.redis, self.expire_routing_timeout)
Beispiel #29
0
 def setup_transport(self):
     super(SafaricomTransport, self).setup_transport()
     self.session_manager = yield SessionManager.from_redis_config(
         self.redis_config, self.r_prefix, self.r_session_timeout)
Beispiel #30
0
class TestSessionManager(VumiTestCase):
    @inlineCallbacks
    def setUp(self):
        self.persistence_helper = self.add_helper(PersistenceHelper())
        self.manager = yield self.persistence_helper.get_redis_manager()
        yield self.manager._purge_all()  # Just in case
        self.sm = SessionManager(self.manager)
        self.add_cleanup(self.sm.stop)

    @inlineCallbacks
    def test_active_sessions(self):
        def get_sessions():
            return self.sm.active_sessions().addCallback(lambda s: sorted(s))

        def ids():
            return get_sessions().addCallback(lambda s: [x[0] for x in s])

        self.assertEqual((yield ids()), [])
        yield self.sm.create_session("u1")
        self.assertEqual((yield ids()), ["u1"])
        # 10 seconds later
        yield self.sm.create_session("u2", created_at=time.time() + 10)
        self.assertEqual((yield ids()), ["u1", "u2"])

        s1, s2 = yield get_sessions()
        self.assertTrue(s1[1]['created_at'] < s2[1]['created_at'])

    @inlineCallbacks
    def test_schedule_session_expiry(self):
        self.sm.max_session_length = 60.0
        yield self.sm.create_session("u1")

    @inlineCallbacks
    def test_create_and_retrieve_session(self):
        session = yield self.sm.create_session("u1")
        self.assertEqual(sorted(session.keys()), ['created_at'])
        self.assertTrue(time.time() - float(session['created_at']) < 10.0)
        loaded = yield self.sm.load_session("u1")
        self.assertEqual(loaded, session)

    @inlineCallbacks
    def test_create_clears_existing_session(self):
        session = yield self.sm.create_session("u1", foo="bar")
        self.assertEqual(sorted(session.keys()), ['created_at', 'foo'])
        loaded = yield self.sm.load_session("u1")
        self.assertEqual(loaded, session)

        session = yield self.sm.create_session("u1", bar="baz")
        self.assertEqual(sorted(session.keys()), ['bar', 'created_at'])
        loaded = yield self.sm.load_session("u1")
        self.assertEqual(loaded, session)

    @inlineCallbacks
    def test_save_session(self):
        test_session = {"foo": 5, "bar": "baz"}
        yield self.sm.create_session("u1")
        yield self.sm.save_session("u1", test_session)
        session = yield self.sm.load_session("u1")
        self.assertTrue(session.pop('created_at') is not None)
        # Redis saves & returns all session values as strings
        self.assertEqual(session,
                         dict([map(str, kvs) for kvs in test_session.items()]))
Beispiel #31
0
class DeliverShortMessageProcessor(default.DeliverShortMessageProcessor):

    CONFIG_CLASS = DeliverShortMessageProcessorConfig

    # NOTE: these keys are hexidecimal because of python-smpp encoding
    #       quirkiness
    ussd_service_op_map = {
        '01': 'new',
        '12': 'continue',
        '81': 'close',  # user abort
    }

    def __init__(self, transport, config):
        super(DeliverShortMessageProcessor, self).__init__(transport, config)
        self.transport = transport
        self.redis = transport.redis
        self.config = self.CONFIG_CLASS(config, static=True)
        self.session_manager = SessionManager(
            self.redis, max_session_length=self.config.max_session_length)

    @inlineCallbacks
    def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts):
        service_op = pdu_opts['ussd_service_op']

        # 6D uses its_session_info as follows:
        #
        # * First 15 bit: dialog id (i.e. session id)
        # * Last bit: end session (1 to end, 0 to continue)

        its_session_number = int(pdu_opts['its_session_info'], 16)
        end_session = bool(its_session_number % 2)
        sixdee_session_identifier = "%04x" % (its_session_number & 0xfffe)
        vumi_session_identifier = make_vumi_session_identifier(
            pdu_params['source_addr'], sixdee_session_identifier)

        if end_session:
            session_event = 'close'
        else:
            session_event = self.ussd_service_op_map.get(service_op)

        if session_event == 'new':
            # PSSR request. Let's assume it means a new session.
            ussd_code = pdu_params['short_message']
            content = None

            yield self.session_manager.create_session(vumi_session_identifier,
                                                      ussd_code=ussd_code)

        elif session_event == 'close':
            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = None

            yield self.session_manager.clear_session(vumi_session_identifier)

        else:
            if session_event != 'continue':
                log.warning(('Received unknown %r ussd_service_op, '
                             'assuming continue.') % (service_op, ))
                session_event = 'continue'

            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = pdu_params['short_message']

        # This is stashed on the message and available when replying
        # with a `submit_sm`
        session_info = {
            'ussd_service_op': service_op,
            'session_identifier': sixdee_session_identifier,
        }

        decoded_msg = self.dcs_decode(content, pdu_params['data_coding'])

        result = yield self.handle_short_message_content(
            source_addr=pdu_params['source_addr'],
            destination_addr=ussd_code,
            short_message=decoded_msg,
            message_type='ussd',
            session_event=session_event,
            session_info=session_info)
        returnValue(result)
Beispiel #32
0
class DeliverShortMessageProcessor(default.DeliverShortMessageProcessor):

    CONFIG_CLASS = DeliverShortMessageProcessorConfig

    def __init__(self, transport, config):
        super(DeliverShortMessageProcessor, self).__init__(transport, config)
        self.transport = transport
        self.redis = transport.redis
        self.config = self.CONFIG_CLASS(config, static=True)
        self.session_manager = SessionManager(
            self.redis, max_session_length=self.config.max_session_length)

    @inlineCallbacks
    def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts):
        service_op = pdu_opts['ussd_service_op']
        mica_session_identifier = pdu_opts['user_message_reference']
        vumi_session_identifier = make_vumi_session_identifier(
            pdu_params['source_addr'], mica_session_identifier)

        session_event = 'close'
        if service_op == '01':
            # PSSR request. Let's assume it means a new session.
            session_event = 'new'
            ussd_code = pdu_params['short_message']
            content = None

            yield self.session_manager.create_session(
                vumi_session_identifier, ussd_code=ussd_code)

        elif service_op == '17':
            # PSSR response. This means session end.
            session_event = 'close'

            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = None

            yield self.session_manager.clear_session(vumi_session_identifier)

        else:
            session_event = 'continue'

            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = pdu_params['short_message']

        # This is stashed on the message and available when replying
        # with a `submit_sm`
        session_info = {
            'session_identifier': mica_session_identifier,
        }

        decoded_msg = self.dcs_decode(content,
                                      pdu_params['data_coding'])

        result = yield self.handle_short_message_content(
            source_addr=pdu_params['source_addr'],
            destination_addr=ussd_code,
            short_message=decoded_msg,
            message_type='ussd',
            session_event=session_event,
            session_info=session_info)
        returnValue(result)
Beispiel #33
0
 def setup_transport(self):
     super(SafaricomTransport, self).setup_transport()
     self.session_manager = yield SessionManager.from_redis_config(
         self.redis_config, self.r_prefix, self.r_session_timeout)
Beispiel #34
0
 def session_manager(self, config):
     key_prefix = ':'.join((self.worker_name, config.router.key))
     redis = self.redis.sub_manager(key_prefix)
     return SessionManager(redis, max_session_length=config.session_expiry)
Beispiel #35
0
class ContentKeywordRouter(SimpleDispatchRouter):
    """Router that dispatches based on the first word of the message
    content. In the context of SMSes the first word is sometimes called
    the 'keyword'.

    :param dict keyword_mappings:
        Mapping from application transport names to simple keywords.
        This is purely a convenience for constructing simple routing
        rules. The rules generated from this option are appened to
        the of rules supplied via the *rules* option.

    :param list rules:
        A list of routing rules. A routing rule is a dictionary. It
        must have `app` and `keyword` keys and may contain `to_addr`
        and `prefix` keys. If a message's first word matches a given
        keyword, the message is sent to the application listening on
        the transport name given by the value of `app`. If a 'to_addr'
        key is supplied, the message `to_addr` must also match the
        value of the 'to_addr' key. If a 'prefix' is supplied, the
        message `from_addr` must *start with* the value of the
        'prefix' key.

    :param str fallback_application:
        Optional application transport name to forward inbound messages
        that match no rule to. If omitted, unrouted inbound messages
        are just logged.

    :param dict transport_mappings:
        Mapping from message `from_addr` values to transports names.
        If a message's from_addr matches a given from_addr, the
        message is sent to the associated transport.

    :param int expire_routing_memory:
        Time in seconds before outbound message's ids are expired from
        the redis routing store. Outbound message ids are stored along
        with the transport_name the message came in on and are used to
        route events such as acknowledgements and delivery reports
        back to the application that sent the outgoing
        message. Default is seven days.
    """

    DEFAULT_ROUTING_TIMEOUT = 60 * 60 * 24 * 7  # 7 days

    def setup_routing(self):
        self.r_config = self.config.get('redis_manager', {})
        self.r_prefix = self.config['dispatcher_name']

        self.rules = []
        for rule in self.config.get('rules', []):
            if 'keyword' not in rule or 'app' not in rule:
                raise ConfigError("Rule definition %r must contain values for"
                                  " both 'app' and 'keyword'" % rule)
            rule = rule.copy()
            rule['keyword'] = rule['keyword'].lower()
            self.rules.append(rule)
        keyword_mappings = self.config.get('keyword_mappings', {})
        for transport_name, keyword in keyword_mappings.items():
            self.rules.append({
                'app': transport_name,
                'keyword': keyword.lower()
            })
        self.fallback_application = self.config.get('fallback_application')
        self.transport_mappings = self.config['transport_mappings']
        self.expire_routing_timeout = int(
            self.config.get('expire_routing_memory',
                            self.DEFAULT_ROUTING_TIMEOUT))

        # FIXME: The following is a hack to deal with sync-only setup.
        self._redis_d = TxRedisManager.from_config(self.r_config)
        self._redis_d.addCallback(lambda m: m.sub_manager(self.r_prefix))
        self._redis_d.addCallback(self._setup_redis)

    def _setup_redis(self, redis):
        self.redis = redis
        self.session_manager = SessionManager(self.redis,
                                              self.expire_routing_timeout)

    def get_message_key(self, message):
        return 'message:%s' % (message, )

    def publish_transport(self, name, msg):
        self.dispatcher.publish_outbound_message(name, msg)

    def publish_exposed_inbound(self, name, msg):
        self.dispatcher.publish_inbound_message(name, msg)

    def publish_exposed_event(self, name, msg):
        self.dispatcher.publish_inbound_event(name, msg)

    def is_msg_matching_routing_rules(self, keyword, msg, rule):
        return all([
            keyword == rule['keyword'], (not 'to_addr' in rule)
            or (msg['to_addr'] == rule['to_addr']), (not 'prefix' in rule)
            or (msg['from_addr'].startswith(rule['prefix']))
        ])

    def dispatch_inbound_message(self, msg):
        keyword = get_first_word(msg['content']).lower()
        matched = False
        for rule in self.rules:
            if self.is_msg_matching_routing_rules(keyword, msg, rule):
                matched = True
                # copy message so that the middleware doesn't see a particular
                # message instance multiple times
                self.publish_exposed_inbound(rule['app'], msg.copy())
        if not matched:
            if self.fallback_application is not None:
                self.publish_exposed_inbound(self.fallback_application, msg)
            else:
                log.error(
                    DispatcherError('Message could not be routed: %r' %
                                    (msg, )))

    @inlineCallbacks
    def dispatch_inbound_event(self, msg):
        yield self._redis_d  # Horrible hack to ensure we have it setup.
        message_key = self.get_message_key(msg['user_message_id'])
        session = yield self.session_manager.load_session(message_key)
        name = session.get('name')
        if not name:
            log.error(
                DispatcherError(
                    "No transport_name for return route found in Redis"
                    " while dispatching transport event for message %s" %
                    (msg['user_message_id'], )))
        try:
            self.publish_exposed_event(name, msg)
        except:
            log.error(DispatcherError("No publishing route for %s" % (name, )))

    @inlineCallbacks
    def dispatch_outbound_message(self, msg):
        yield self._redis_d  # Horrible hack to ensure we have it setup.
        transport_name = self.transport_mappings.get(msg['from_addr'])
        if transport_name is not None:
            self.publish_transport(transport_name, msg)
            message_key = self.get_message_key(msg['message_id'])
            yield self.session_manager.create_session(
                message_key, name=msg['transport_name'])
        else:
            log.error(
                DispatcherError("No transport for %s" % (msg['from_addr'], )))
class TwilioAPIWorker(ApplicationWorker):
    """Emulates the Twilio API to use vumi as if it was Twilio"""
    CONFIG_CLASS = TwilioAPIConfig

    @inlineCallbacks
    def setup_application(self):
        """Application specific setup"""
        self.app_config = self.get_static_config()
        self.server = TwilioAPIServer(self, self.app_config.api_version)
        path = os.path.join(
            self.app_config.web_path, self.app_config.api_version)
        self.webserver = self.start_web_resources([
            (self.server.app.resource(), path)],
            self.app_config.web_port)
        redis = yield TxRedisManager.from_config(self.app_config.redis_manager)
        self.session_manager = SessionManager(
            redis, self.app_config.redis_timeout)
        self.session_lookup = SessionIDLookup(
            redis, self.app_config.redis_timeout,
            self.app_config.session_lookup_namespace)

    @inlineCallbacks
    def teardown_application(self):
        """Clean-up of setup done in `setup_application`"""
        yield self.webserver.loseConnection()
        yield self.session_manager.stop()

    def _http_request(self, url='', method='GET', data={}):
        return treq.request(method, url, persistent=False, data=data)

    def _request_data_from_session(self, session):
        return {
            'CallSid': session['CallId'],
            'AccountSid': session['AccountSid'],
            'From': session['From'],
            'To': session['To'],
            'CallStatus': session['Status'],
            'ApiVersion': self.app_config.api_version,
            'Direction': session['Direction'],
        }

    @inlineCallbacks
    def _get_twiml_from_client(self, session, data=None):
        if data is None:
            data = self._request_data_from_session(session)
        twiml_raw = yield self._http_request(
            session['Url'], session['Method'], data)
        if twiml_raw.code < 200 or twiml_raw.code >= 300:
            twiml_raw = yield self._http_request(
                session['FallbackUrl'], session['FallbackMethod'], data)
        twiml_raw = yield twiml_raw.content()
        twiml_parser = TwiMLParser(session['Url'])
        returnValue(twiml_parser.parse(twiml_raw))

    @inlineCallbacks
    def _handle_connected_call(
            self, session_id, session, status='in-progress', twiml=None):
        # TODO: Support sending ForwardedFrom parameter
        # TODO: Support sending CallerName parameter
        # TODO: Support sending geographic data parameters
        session['Status'] = status
        self.session_manager.save_session(session_id, session)
        if twiml is None:
            twiml = yield self._get_twiml_from_client(session)
        for verb in twiml:
            if verb.name == "Play":
                # TODO: Support loop and digit attributes
                yield self._send_message(verb.nouns[0], session)
            elif verb.name == "Hangup":
                yield self._send_message(
                    None, session, TransportUserMessage.SESSION_CLOSE)
                yield self.session_manager.clear_session(session_id)
                break
            elif verb.name == "Gather":
                # TODO: Support timeout and numDigits attributes
                msgs = []
                for subverb in verb.nouns:
                    # TODO: Support Say and Pause subverbs
                    if subverb.name == "Play":
                        msgs.append({'speech_url': subverb.nouns[0]})
                session['Gather_Action'] = verb.attributes['action']
                session['Gather_Method'] = verb.attributes['method']
                yield self.session_manager.save_session(session_id, session)
                if len(msgs) == 0:
                    msgs.append({'speech_url': None})
                msgs[-1]['wait_for'] = verb.attributes['finishOnKey']
                for msg in msgs:
                    yield self._send_message(
                        msg['speech_url'], session,
                        wait_for=msg.get('wait_for'))
                break

    def _send_message(self, url, session, session_event=None, wait_for=None):
        helper_metadata = {'voice': {}}
        if url is not None:
            helper_metadata['voice']['speech_url'] = url
        if wait_for is not None:
            helper_metadata['voice']['wait_for'] = wait_for

        return self.send_to(
            session['To'], None,
            from_addr=session['From'],
            session_event=session_event,
            to_addr_type=TransportUserMessage.AT_MSISDN,
            from_addr_type=TransportUserMessage.AT_MSISDN,
            helper_metadata=helper_metadata)

    @inlineCallbacks
    def consume_user_message(self, message):
        # At the moment there is no way to determine whether or not a message
        # is the result of a wait_for or just a single digit, so if the Gather
        # data exists inside the current session data, then we assume that it
        # is the result of a Gather
        # TODO: Fix this
        session = yield self.session_manager.load_session(message['from_addr'])
        if session.get('Gather_Action') and session.get('Gather_Method'):
            data = self._request_data_from_session(session)
            data['Digits'] = message['content']
            twiml = yield self._get_twiml_from_client({
                'Url': session['Gather_Action'],
                'Method': session['Gather_Method'],
                'Fallback_Url': None,
                'Fallback_Method': None, },
                data=data)
            yield self._handle_connected_call(
                message['from_addr'], session, twiml=twiml)

    @inlineCallbacks
    def consume_ack(self, event):
        message_id = event['user_message_id']
        session_id = yield self.session_lookup.get_address(message_id)
        yield self.session_lookup.delete_id(message_id)
        session = yield self.session_manager.load_session(session_id)

        if session['Status'] == 'queued':
            yield self._handle_connected_call(session_id, session)

    @inlineCallbacks
    def consume_nack(self, event):
        message_id = event['user_message_id']
        session_id = yield self.session_lookup.get_address(message_id)
        yield self.session_lookup.delete_id(message_id)
        session = yield self.session_manager.load_session(session_id)

        if session['Status'] == 'queued':
            yield self._handle_connected_call(
                session_id, session, status='failed')

    @inlineCallbacks
    def new_session(self, message):
        yield self.session_lookup.set_id(
            message['message_id'], message['from_addr'])
        config = yield self.get_config(message)
        session = {
            'CallId': self.server._get_sid(),
            'AccountSid': self.server._get_sid(),
            'From': message['from_addr'],
            'To': message['to_addr'],
            'Status': 'in-progress',
            'Direction': 'inbound',
            'Url': config.client_path,
            'Method': config.client_method,
            'StatusCallback': config.status_callback_path,
            'StatusCallbackMethod': config.status_callback_method,
        }
        yield self.session_manager.create_session(
            message['from_addr'], **session)

        twiml = yield self._get_twiml_from_client(session)
        for verb in twiml:
            if verb.name == "Play":
                yield self.reply_to(message, None, helper_metadata={
                    'voice': {
                        'speech_url': verb.nouns[0],
                        }
                    })
            elif verb.name == "Hangup":
                yield self.reply_to(
                    message, None,
                    session_event=TransportUserMessage.SESSION_CLOSE)
                yield self.session_manager.clear_session(message['from_addr'])
                break
            elif verb.name == "Gather":
                # TODO: Support timeout and numDigits attributes
                msgs = []
                for subverb in verb.nouns:
                    # TODO: Support Say and Pause subverbs
                    if subverb.name == "Play":
                        msgs.append({'speech_url': subverb.nouns[0]})
                session['Gather_Action'] = verb.attributes['action']
                session['Gather_Method'] = verb.attributes['method']
                yield self.session_manager.save_session(
                    message['from_addr'], session)
                if len(msgs) == 0:
                    msgs.append({'speech_url': None})
                msgs[-1]['wait_for'] = verb.attributes['finishOnKey']
                for msg in msgs:
                    yield self.reply_to(message, None, helper_metadata={
                        'voice': {
                            'speech_url': msg.get('speech_url'),
                            'wait_for': msg.get('wait_for'),
                        }})
                break

    @inlineCallbacks
    def close_session(self, message):
        # TODO: Implement call duration parameters
        # TODO: Implement recording parameters
        session = yield self.session_manager.load_session(message['from_addr'])
        yield self.session_manager.clear_session(message['from_addr'])
        url = session.get('StatusCallback')

        if url and url != 'None':
            session['Status'] = 'completed'
            data = self._request_data_from_session(session)
            yield self._http_request(
                session['StatusCallback'], session['StatusCallbackMethod'],
                data)
Beispiel #37
0
 def setUp(self):
     self.persistence_helper = self.add_helper(PersistenceHelper())
     self.manager = yield self.persistence_helper.get_redis_manager()
     yield self.manager._purge_all()  # Just in case
     self.sm = SessionManager(self.manager)
     self.add_cleanup(self.sm.stop)
Beispiel #38
0
 def _setup_redis(self, redis):
     self.redis = redis
     self.session_manager = SessionManager(self.redis,
                                           self.expire_routing_timeout)
Beispiel #39
0
class DeliverShortMessageProcessor(default.DeliverShortMessageProcessor):

    CONFIG_CLASS = DeliverShortMessageProcessorConfig

    # NOTE: these keys are hexidecimal because of python-smpp encoding
    #       quirkiness
    ussd_service_op_map = {
        '01': 'new',
        '12': 'continue',
        '81': 'close',  # user abort
    }

    def __init__(self, transport, config):
        super(DeliverShortMessageProcessor, self).__init__(transport, config)
        self.transport = transport
        self.log = transport.log
        self.redis = transport.redis
        self.config = self.CONFIG_CLASS(config, static=True)
        self.session_manager = SessionManager(
            self.redis, max_session_length=self.config.max_session_length)

    @inlineCallbacks
    def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts):
        service_op = pdu_opts['ussd_service_op']
        mica_session_identifier = pdu_opts['user_message_reference']
        vumi_session_identifier = make_vumi_session_identifier(
            pdu_params['source_addr'], mica_session_identifier)

        session_event = self.ussd_service_op_map.get(service_op)

        if session_event == 'new':
            # PSSR request. Let's assume it means a new session.
            ussd_code = pdu_params['short_message']
            content = None

            yield self.session_manager.create_session(vumi_session_identifier,
                                                      ussd_code=ussd_code)

        elif session_event == 'close':
            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = None

            yield self.session_manager.clear_session(vumi_session_identifier)

        else:
            if session_event != 'continue':
                self.log.warning(
                    ('Received unknown %r ussd_service_op, assuming continue.')
                    % (service_op, ))
                session_event = 'continue'

            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = self.dcs_decode(pdu_params['short_message'],
                                      pdu_params['data_coding'])

        # This is stashed on the message and available when replying
        # with a `submit_sm`
        session_info = {
            'ussd_service_op': service_op,
            'session_identifier': mica_session_identifier,
        }

        result = yield self.handle_short_message_content(
            source_addr=pdu_params['source_addr'],
            destination_addr=ussd_code,
            short_message=content,
            message_type='ussd',
            session_event=session_event,
            session_info=session_info)
        returnValue(result)
Beispiel #40
0
class SubmitShortMessageProcessor(default.SubmitShortMessageProcessor):

    CONFIG_CLASS = SubmitShortMessageProcessorConfig

    # NOTE: these values are hexidecimal because of python-smpp encoding
    #       quirkiness
    ussd_service_op_map = {
        'continue': '02',
        'close': '17',  # end
    }

    def __init__(self, transport, config):
        super(SubmitShortMessageProcessor, self).__init__(transport, config)
        self.transport = transport
        self.redis = transport.redis
        self.config = self.CONFIG_CLASS(config, static=True)
        self.session_manager = SessionManager(
            self.redis, max_session_length=self.config.max_session_length)

    @inlineCallbacks
    def handle_outbound_message(self, message, protocol):
        to_addr = message['to_addr']
        from_addr = message['from_addr']
        text = message['content']
        if text is None:
            text = u""
        vumi_message_id = message['message_id']

        session_event = message['session_event']
        transport_type = message['transport_type']
        optional_parameters = {}

        if transport_type == 'ussd':
            continue_session = (session_event !=
                                TransportUserMessage.SESSION_CLOSE)
            session_info = message['transport_metadata'].get(
                'session_info', {})
            sixdee_session_identifier = session_info.get(
                'session_identifier', '')
            vumi_session_identifier = make_vumi_session_identifier(
                to_addr, sixdee_session_identifier)

            its_session_info = (int(sixdee_session_identifier, 16)
                                | int(not continue_session))

            service_op = self.ussd_service_op_map[(
                'continue' if continue_session else 'close')]
            optional_parameters.update({
                'ussd_service_op':
                service_op,
                'its_session_info':
                "%04x" % (its_session_info, )
            })

            if not continue_session:
                yield self.session_manager.clear_session(
                    vumi_session_identifier)

        if self.config.send_long_messages:
            resp = yield protocol.submit_sm_long(
                vumi_message_id,
                to_addr.encode('ascii'),
                long_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )

        elif self.config.send_multipart_sar:
            resp = yield protocol.submit_csm_sar(
                vumi_message_id,
                to_addr.encode('ascii'),
                short_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )

        elif self.config.send_multipart_udh:
            resp = yield protocol.submit_csm_udh(
                vumi_message_id,
                to_addr.encode('ascii'),
                short_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )
        else:
            resp = yield protocol.submit_sm(
                vumi_message_id,
                to_addr.encode('ascii'),
                short_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )

        returnValue(resp)
Beispiel #41
0
class SubmitShortMessageProcessor(default.SubmitShortMessageProcessor):

    CONFIG_CLASS = SubmitShortMessageProcessorConfig

    # NOTE: these values are hexidecimal because of python-smpp encoding
    #       quirkiness
    ussd_service_op_map = {
        'continue': '02',
        'close': '17',  # end
    }

    def __init__(self, transport, config):
        super(SubmitShortMessageProcessor, self).__init__(transport, config)
        self.transport = transport
        self.redis = transport.redis
        self.config = self.CONFIG_CLASS(config, static=True)
        self.session_manager = SessionManager(
            self.redis, max_session_length=self.config.max_session_length)

    @inlineCallbacks
    def handle_outbound_message(self, message, protocol):
        to_addr = message['to_addr']
        from_addr = message['from_addr']
        text = message['content']
        if text is None:
            text = u""
        vumi_message_id = message['message_id']

        session_event = message['session_event']
        transport_type = message['transport_type']
        optional_parameters = {}

        if transport_type == 'ussd':
            continue_session = (
                session_event != TransportUserMessage.SESSION_CLOSE)
            session_info = message['transport_metadata'].get(
                'session_info', {})
            sixdee_session_identifier = session_info.get(
                'session_identifier', '')
            vumi_session_identifier = make_vumi_session_identifier(
                to_addr, sixdee_session_identifier)

            its_session_info = (
                int(sixdee_session_identifier, 16) |
                int(not continue_session))

            service_op = self.ussd_service_op_map[('continue'
                                                   if continue_session
                                                   else 'close')]
            optional_parameters.update({
                'ussd_service_op': service_op,
                'its_session_info': "%04x" % (its_session_info,)
            })

            if not continue_session:
                yield self.session_manager.clear_session(
                    vumi_session_identifier)

        if self.config.send_long_messages:
            resp = yield protocol.submit_sm_long(
                vumi_message_id,
                to_addr.encode('ascii'),
                long_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )

        elif self.config.send_multipart_sar:
            resp = yield protocol.submit_csm_sar(
                vumi_message_id,
                to_addr.encode('ascii'),
                short_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )

        elif self.config.send_multipart_udh:
            resp = yield protocol.submit_csm_udh(
                vumi_message_id,
                to_addr.encode('ascii'),
                short_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )
        else:
            resp = yield protocol.submit_sm(
                vumi_message_id,
                to_addr.encode('ascii'),
                short_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )

        returnValue(resp)
Beispiel #42
0
class SubmitShortMessageProcessor(default.SubmitShortMessageProcessor):

    CONFIG_CLASS = SubmitShortMessageProcessorConfig

    def __init__(self, transport, config):
        super(SubmitShortMessageProcessor, self).__init__(transport, config)
        self.transport = transport
        self.redis = transport.redis
        self.config = self.CONFIG_CLASS(config, static=True)
        self.session_manager = SessionManager(
            self.redis, max_session_length=self.config.max_session_length)

    @inlineCallbacks
    def handle_outbound_message(self, message, protocol):
        to_addr = message['to_addr']
        from_addr = message['from_addr']
        text = message['content']
        vumi_message_id = message['message_id']

        session_event = message['session_event']
        transport_type = message['transport_type']
        optional_parameters = {}

        if transport_type == 'ussd':
            continue_session = (
                session_event != TransportUserMessage.SESSION_CLOSE)
            session_info = message['transport_metadata'].get(
                'session_info', {})
            mica_session_identifier = session_info.get(
                'session_identifier', '')
            vumi_session_identifier = make_vumi_session_identifier(
                to_addr, mica_session_identifier)

            optional_parameters.update({
                'ussd_service_op': ('02' if continue_session else '17'),
                'user_message_reference': (
                    str(mica_session_identifier).zfill(2)),
            })

            if not continue_session:
                yield self.session_manager.clear_session(
                    vumi_session_identifier)

        if self.config.send_long_messages:
            resp = yield protocol.submit_sm_long(
                vumi_message_id,
                to_addr.encode('ascii'),
                long_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )

        elif self.config.send_multipart_sar:
            resp = yield protocol.submit_csm_sar(
                vumi_message_id,
                to_addr.encode('ascii'),
                short_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )

        elif self.config.send_multipart_udh:
            resp = yield protocol.submit_csm_udh(
                vumi_message_id,
                to_addr.encode('ascii'),
                short_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )
        else:
            resp = yield protocol.submit_sm(
                vumi_message_id,
                to_addr.encode('ascii'),
                short_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )

        returnValue(resp)
Beispiel #43
0
class DeliverShortMessageProcessor(default.DeliverShortMessageProcessor):

    CONFIG_CLASS = DeliverShortMessageProcessorConfig

    def __init__(self, transport, config):
        super(DeliverShortMessageProcessor, self).__init__(transport, config)
        self.transport = transport
        self.redis = transport.redis
        self.config = self.CONFIG_CLASS(config, static=True)
        self.session_manager = SessionManager(
            self.redis, max_session_length=self.config.max_session_length)

    @inlineCallbacks
    def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts):
        service_op = pdu_opts['ussd_service_op']
        mica_session_identifier = pdu_opts['user_message_reference']
        vumi_session_identifier = make_vumi_session_identifier(
            pdu_params['source_addr'], mica_session_identifier)

        session_event = 'close'
        if service_op == '01':
            # PSSR request. Let's assume it means a new session.
            session_event = 'new'
            ussd_code = pdu_params['short_message']
            content = None

            yield self.session_manager.create_session(vumi_session_identifier,
                                                      ussd_code=ussd_code)

        elif service_op == '17':
            # PSSR response. This means session end.
            session_event = 'close'

            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = None

            yield self.session_manager.clear_session(vumi_session_identifier)

        else:
            session_event = 'continue'

            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = pdu_params['short_message']

        # This is stashed on the message and available when replying
        # with a `submit_sm`
        session_info = {
            'session_identifier': mica_session_identifier,
        }

        decoded_msg = self.dcs_decode(content, pdu_params['data_coding'])

        result = yield self.handle_short_message_content(
            source_addr=pdu_params['source_addr'],
            destination_addr=ussd_code,
            short_message=decoded_msg,
            message_type='ussd',
            session_event=session_event,
            session_info=session_info)
        returnValue(result)
Beispiel #44
0
class DeliverShortMessageProcessor(default.DeliverShortMessageProcessor):

    CONFIG_CLASS = DeliverShortMessageProcessorConfig

    # NOTE: these keys are hexidecimal because of python-smpp encoding
    #       quirkiness
    ussd_service_op_map = {
        '01': 'new',
        '12': 'continue',
        '81': 'close',  # user abort
    }

    def __init__(self, transport, config):
        super(DeliverShortMessageProcessor, self).__init__(transport, config)
        self.transport = transport
        self.redis = transport.redis
        self.config = self.CONFIG_CLASS(config, static=True)
        self.session_manager = SessionManager(
            self.redis, max_session_length=self.config.max_session_length)

    @inlineCallbacks
    def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts):
        service_op = pdu_opts['ussd_service_op']

        # 6D uses its_session_info as follows:
        #
        # * First 15 bit: dialog id (i.e. session id)
        # * Last bit: end session (1 to end, 0 to continue)

        its_session_number = int(pdu_opts['its_session_info'], 16)
        end_session = bool(its_session_number % 2)
        sixdee_session_identifier = "%04x" % (its_session_number & 0xfffe)
        vumi_session_identifier = make_vumi_session_identifier(
            pdu_params['source_addr'], sixdee_session_identifier)

        if end_session:
            session_event = 'close'
        else:
            session_event = self.ussd_service_op_map.get(service_op)

        if session_event == 'new':
            # PSSR request. Let's assume it means a new session.
            ussd_code = pdu_params['short_message']
            content = None

            yield self.session_manager.create_session(
                vumi_session_identifier, ussd_code=ussd_code)

        elif session_event == 'close':
            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = None

            yield self.session_manager.clear_session(vumi_session_identifier)

        else:
            if session_event != 'continue':
                log.warning(('Received unknown %r ussd_service_op, '
                             'assuming continue.') % (service_op,))
                session_event = 'continue'

            session = yield self.session_manager.load_session(
                vumi_session_identifier)
            ussd_code = session['ussd_code']
            content = pdu_params['short_message']

        # This is stashed on the message and available when replying
        # with a `submit_sm`
        session_info = {
            'ussd_service_op': service_op,
            'session_identifier': sixdee_session_identifier,
        }

        decoded_msg = self.dcs_decode(content,
                                      pdu_params['data_coding'])

        result = yield self.handle_short_message_content(
            source_addr=pdu_params['source_addr'],
            destination_addr=ussd_code,
            short_message=decoded_msg,
            message_type='ussd',
            session_event=session_event,
            session_info=session_info)
        returnValue(result)
Beispiel #45
0
class SubmitShortMessageProcessor(default.SubmitShortMessageProcessor):

    CONFIG_CLASS = SubmitShortMessageProcessorConfig

    def __init__(self, transport, config):
        super(SubmitShortMessageProcessor, self).__init__(transport, config)
        self.transport = transport
        self.redis = transport.redis
        self.config = self.CONFIG_CLASS(config, static=True)
        self.session_manager = SessionManager(
            self.redis, max_session_length=self.config.max_session_length)

    @inlineCallbacks
    def handle_outbound_message(self, message, protocol):
        to_addr = message['to_addr']
        from_addr = message['from_addr']
        text = message['content']
        vumi_message_id = message['message_id']

        session_event = message['session_event']
        transport_type = message['transport_type']
        optional_parameters = {}

        if transport_type == 'ussd':
            continue_session = (session_event !=
                                TransportUserMessage.SESSION_CLOSE)
            session_info = message['transport_metadata'].get(
                'session_info', {})
            mica_session_identifier = session_info.get('session_identifier',
                                                       '')
            vumi_session_identifier = make_vumi_session_identifier(
                to_addr, mica_session_identifier)

            optional_parameters.update({
                'ussd_service_op': ('02' if continue_session else '17'),
                'user_message_reference':
                (str(mica_session_identifier).zfill(2)),
            })

            if not continue_session:
                yield self.session_manager.clear_session(
                    vumi_session_identifier)

        if self.config.send_long_messages:
            resp = yield protocol.submit_sm_long(
                vumi_message_id,
                to_addr.encode('ascii'),
                long_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )

        elif self.config.send_multipart_sar:
            resp = yield protocol.submit_csm_sar(
                vumi_message_id,
                to_addr.encode('ascii'),
                short_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )

        elif self.config.send_multipart_udh:
            resp = yield protocol.submit_csm_udh(
                vumi_message_id,
                to_addr.encode('ascii'),
                short_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )
        else:
            resp = yield protocol.submit_sm(
                vumi_message_id,
                to_addr.encode('ascii'),
                short_message=text.encode(self.config.submit_sm_encoding),
                data_coding=self.config.submit_sm_data_coding,
                source_addr=from_addr.encode('ascii'),
                optional_parameters=optional_parameters,
            )

        returnValue(resp)
Beispiel #46
0
class TestSessionManager(VumiTestCase):
    @inlineCallbacks
    def setUp(self):
        self.persistence_helper = self.add_helper(PersistenceHelper())
        self.manager = yield self.persistence_helper.get_redis_manager()
        yield self.manager._purge_all()  # Just in case
        self.sm = SessionManager(self.manager)
        self.add_cleanup(self.sm.stop)

    @inlineCallbacks
    def test_active_sessions(self):
        def get_sessions():
            return self.sm.active_sessions().addCallback(lambda s: sorted(s))

        def ids():
            return get_sessions().addCallback(lambda s: [x[0] for x in s])

        self.assertEqual((yield ids()), [])
        yield self.sm.create_session("u1")
        self.assertEqual((yield ids()), ["u1"])
        # 10 seconds later
        yield self.sm.create_session("u2", created_at=time.time() + 10)
        self.assertEqual((yield ids()), ["u1", "u2"])

        s1, s2 = yield get_sessions()
        self.assertTrue(s1[1]["created_at"] < s2[1]["created_at"])

    @inlineCallbacks
    def test_schedule_session_expiry(self):
        self.sm.max_session_length = 60.0
        yield self.sm.create_session("u1")

    @inlineCallbacks
    def test_create_and_retrieve_session(self):
        session = yield self.sm.create_session("u1")
        self.assertEqual(sorted(session.keys()), ["created_at"])
        self.assertTrue(time.time() - float(session["created_at"]) < 10.0)
        loaded = yield self.sm.load_session("u1")
        self.assertEqual(loaded, session)

    @inlineCallbacks
    def test_create_clears_existing_session(self):
        session = yield self.sm.create_session("u1", foo="bar")
        self.assertEqual(sorted(session.keys()), ["created_at", "foo"])
        loaded = yield self.sm.load_session("u1")
        self.assertEqual(loaded, session)

        session = yield self.sm.create_session("u1", bar="baz")
        self.assertEqual(sorted(session.keys()), ["bar", "created_at"])
        loaded = yield self.sm.load_session("u1")
        self.assertEqual(loaded, session)

    @inlineCallbacks
    def test_save_session(self):
        test_session = {"foo": 5, "bar": "baz"}
        yield self.sm.create_session("u1")
        yield self.sm.save_session("u1", test_session)
        session = yield self.sm.load_session("u1")
        self.assertTrue(session.pop("created_at") is not None)
        # Redis saves & returns all session values as strings
        self.assertEqual(session, dict([map(str, kvs) for kvs in test_session.items()]))
Beispiel #47
0
 def setup_transport(self):
     yield super(InfobipTransport, self).setup_transport()
     r_prefix = "infobip:%s" % (self.transport_name,)
     session_timeout = int(self.config.get("ussd_session_timeout", 600))
     self.session_manager = yield SessionManager.from_redis_config(
         self.r_config, r_prefix, session_timeout)
Beispiel #48
0
 def setUp(self):
     self.persistence_helper = self.add_helper(PersistenceHelper())
     self.manager = yield self.persistence_helper.get_redis_manager()
     yield self.manager._purge_all()  # Just in case
     self.sm = SessionManager(self.manager)
     self.add_cleanup(self.sm.stop)
Beispiel #49
0
 def get_session_manager(self, config):
     return SessionManager(
         self.get_redis(config).sub_manager('session'),
         max_session_length=config.max_session_length)
Beispiel #50
0
class SubmitShortMessageProcessor(default.SubmitShortMessageProcessor):

    CONFIG_CLASS = SubmitShortMessageProcessorConfig

    # NOTE: these values are hexidecimal because of python-smpp encoding
    #       quirkiness
    ussd_service_op_map = {
        'continue': '02',
        'close': '17',  # end
    }

    def __init__(self, transport, config):
        super(SubmitShortMessageProcessor, self).__init__(transport, config)
        self.transport = transport
        self.redis = transport.redis
        self.config = self.CONFIG_CLASS(config, static=True)
        self.session_manager = SessionManager(
            self.redis, max_session_length=self.config.max_session_length)

    @inlineCallbacks
    def handle_outbound_message(self, message, service):
        to_addr = message['to_addr']
        from_addr = message['from_addr']
        text = message['content']
        if text is None:
            text = u""
        vumi_message_id = message['message_id']

        session_event = message['session_event']
        transport_type = message['transport_type']
        optional_parameters = {}

        if transport_type == 'ussd':
            continue_session = (session_event !=
                                TransportUserMessage.SESSION_CLOSE)
            session_info = message['transport_metadata'].get(
                'session_info', {})
            mica_session_identifier = session_info.get('session_identifier',
                                                       '')
            vumi_session_identifier = make_vumi_session_identifier(
                to_addr, mica_session_identifier)

            service_op = self.ussd_service_op_map[(
                'continue' if continue_session else 'close')]
            optional_parameters.update({
                'ussd_service_op':
                service_op,
                'user_message_reference':
                (str(mica_session_identifier).zfill(2)),
            })

            if not continue_session:
                yield self.session_manager.clear_session(
                    vumi_session_identifier)

        resp = yield self.send_short_message(
            service,
            vumi_message_id,
            to_addr.encode('ascii'),
            text.encode(self.config.submit_sm_encoding),
            data_coding=self.config.submit_sm_data_coding,
            source_addr=from_addr.encode('ascii'),
            optional_parameters=optional_parameters)

        returnValue(resp)