Ejemplo n.º 1
0
    def post(self, request, *args, **kwargs):
        from temba.msgs.models import Msg

        request_body = request.body
        request_method = request.method
        request_path = request.get_full_path()

        def log_channel(channel, description, event, is_error=False):
            return ChannelLog.objects.create(
                channel_id=channel.pk,
                is_error=is_error,
                request=event.request_body,
                response=event.response_body,
                url=event.url,
                method=event.method,
                response_status=event.status_code,
                description=description,
            )

        action = kwargs["action"].lower()
        request_uuid = kwargs["uuid"]

        data = json.loads(force_text(request_body))
        is_ussd = self.is_ussd_message(data)
        channel_data = data.get("channel_data", {})
        channel_types = ("JNU", "JN")

        # look up the channel
        channel = Channel.objects.filter(uuid=request_uuid, is_active=True, channel_type__in=channel_types).first()

        if not channel:
            return HttpResponse("Channel not found for id: %s" % request_uuid, status=400)

        auth = request.META.get("HTTP_AUTHORIZATION", "").split(" ")
        secret = channel.config.get(Channel.CONFIG_SECRET)
        if secret is not None and (len(auth) != 2 or auth[0] != "Token" or auth[1] != secret):
            return JsonResponse(dict(error="Incorrect authentication token"), status=401)

        # Junebug is sending an event
        if action == "event":
            expected_keys = ["event_type", "message_id", "timestamp"]
            if not set(expected_keys).issubset(data.keys()):
                status = 400
                response_body = "Missing one of %s in request parameters." % (", ".join(expected_keys))
                event = HttpEvent(request_method, request_path, request_body, status, response_body)
                log_channel(channel, "Failed to handle event.", event, is_error=True)
                return HttpResponse(response_body, status=status)

            message_id = data["message_id"]
            event_type = data["event_type"]

            # look up the message
            message = Msg.objects.filter(channel=channel, external_id=message_id).select_related("channel")
            if not message:
                status = 400
                response_body = "Message with external id of '%s' not found" % (message_id,)
                event = HttpEvent(request_method, request_path, request_body, status, response_body)
                log_channel(channel, "Failed to handle %s event_type." % (event_type), event)
                return HttpResponse(response_body, status=status)

            if event_type == "submitted":
                for message_obj in message:
                    message_obj.status_sent()
            if event_type == "delivery_succeeded":
                for message_obj in message:
                    message_obj.status_delivered()
            elif event_type in ["delivery_failed", "rejected"]:
                for message_obj in message:
                    message_obj.status_fail()

            response_body = {"status": self.ACK, "message_ids": [message_obj.pk for message_obj in message]}
            event = HttpEvent(request_method, request_path, request_body, 200, json.dumps(response_body))
            log_channel(channel, "Handled %s event_type." % (event_type), event)
            # Let Junebug know we're happy
            return JsonResponse(response_body)

        # Handle an inbound message
        elif action == "inbound":
            expected_keys = [
                "channel_data",
                "from",
                "channel_id",
                "timestamp",
                "content",
                "to",
                "reply_to",
                "message_id",
            ]
            if not set(expected_keys).issubset(data.keys()):
                status = 400
                response_body = "Missing one of %s in request parameters." % (", ".join(expected_keys))
                event = HttpEvent(request_method, request_path, request_body, status, response_body)
                log_channel(channel, "Failed to handle message.", event, is_error=True)
                return HttpResponse(response_body, status=status)

            if is_ussd:
                status = {"close": USSDSession.INTERRUPTED, "new": USSDSession.TRIGGERED}.get(
                    channel_data.get("session_event"), USSDSession.IN_PROGRESS
                )

                message_date = datetime.strptime(data["timestamp"], "%Y-%m-%d %H:%M:%S.%f")
                gmt_date = pytz.timezone("GMT").localize(message_date)
                # Use a session id if provided, otherwise fall back to using the `from` address as the identifier
                session_id = channel_data.get("session_id") or data["from"]

                connection = USSDSession.handle_incoming(
                    channel=channel,
                    urn=data["from"],
                    content=data["content"],
                    status=status,
                    date=gmt_date,
                    external_id=session_id,
                    message_id=data["message_id"],
                    starcode=data["to"],
                )

                if connection:
                    status = 200
                    response_body = {"status": self.ACK, "session_id": connection.pk}
                    event = HttpEvent(request_method, request_path, request_body, status, json.dumps(response_body))
                    log_channel(
                        channel, "Handled USSD message of %s session_event" % (channel_data["session_event"],), event
                    )
                    return JsonResponse(response_body, status=status)
                else:
                    status = 400
                    response_body = {"status": self.NACK, "reason": "No suitable session found for this message."}
                    event = HttpEvent(request_method, request_path, request_body, status, json.dumps(response_body))
                    log_channel(
                        channel,
                        "Failed to handle USSD message of %s session_event" % (channel_data["session_event"],),
                        event,
                    )
                    return JsonResponse(response_body, status=status)
            else:
                content = data["content"]
                message = Msg.create_incoming(channel, URN.from_tel(data["from"]), content)
                status = 200
                response_body = {"status": self.ACK, "message_id": message.pk}
                Msg.objects.filter(pk=message.id).update(external_id=data["message_id"])
                event = HttpEvent(request_method, request_path, request_body, status, json.dumps(response_body))
                ChannelLog.log_message(message, "Handled inbound message.", event)
                return JsonResponse(response_body, status=status)
Ejemplo n.º 2
0
    def test_send_ussd_continue_and_end_session(self):
        flow = self.get_flow('ussd_session_end')
        contact = self.create_contact("Joe", "+250788383383")

        try:
            settings.SEND_MESSAGES = True

            with patch('requests.post') as mock:
                mock.return_value = MockResponse(200, json.dumps({
                    'result': {
                        'message_id': '07033084-5cfd-4812-90a4-e4d24ffb6e3d',
                    }
                }))

                flow.start([], [contact])

                # our outgoing message
                msg = Msg.objects.filter(direction='O').order_by('id').last()

                self.assertEqual(msg.direction, 'O')
                self.assertTrue(msg.sent_on)
                self.assertEqual("07033084-5cfd-4812-90a4-e4d24ffb6e3d", msg.external_id)
                self.assertEqual(msg.connection.status, USSDSession.INITIATED)

                # reply and choose an option that doesn't have any destination thus needs to close the session
                USSDSession.handle_incoming(channel=self.channel, urn="+250788383383", content="4",
                                            date=timezone.now(), external_id="21345", message_id='vumi-message-id')
                # our outgoing message
                msg = Msg.objects.filter(direction='O').order_by('id').last()
                self.assertEqual(WIRED, msg.status)
                self.assertEqual(msg.direction, 'O')
                self.assertTrue(msg.sent_on)
                self.assertEqual("07033084-5cfd-4812-90a4-e4d24ffb6e3d", msg.external_id)
                self.assertEqual("vumi-message-id", msg.response_to.external_id)

                self.assertEqual(msg.connection.status, USSDSession.COMPLETED)
                self.assertTrue(isinstance(msg.connection.get_duration(), timedelta))

                self.assertEqual(2, mock.call_count)

                # first outbound (session continued)
                call = mock.call_args_list[0]
                (args, kwargs) = call
                payload = kwargs['json']
                self.assertIsNone(payload.get('reply_to'))
                self.assertEqual(payload.get('to'), "+250788383383")
                self.assertEqual(payload['channel_data'], {
                    'continue_session': True
                })

                # second outbound (session ended)
                call = mock.call_args_list[1]
                (args, kwargs) = call
                payload = kwargs['json']
                self.assertEqual(payload['reply_to'], 'vumi-message-id')
                self.assertEqual(payload.get('to'), None)
                self.assertEqual(payload['channel_data'], {
                    'continue_session': False
                })

                self.clear_cache()
        finally:
            settings.SEND_MESSAGES = False
Ejemplo n.º 3
0
    def post(self, request, *args, **kwargs):
        from temba.msgs.models import Msg

        request_body = request.body
        request_method = request.method
        request_path = request.get_full_path()

        def log_channel(channel, description, event, is_error=False):
            return ChannelLog.objects.create(
                channel_id=channel.pk,
                is_error=is_error,
                request=event.request_body,
                response=event.response_body,
                url=event.url,
                method=event.method,
                response_status=event.status_code,
                description=description,
            )

        action = kwargs["action"].lower()
        request_uuid = kwargs["uuid"]

        data = json.loads(force_text(request_body))
        is_ussd = self.is_ussd_message(data)
        channel_data = data.get("channel_data", {})
        channel_types = ("JNU", "JN")

        # look up the channel
        channel = Channel.objects.filter(
            uuid=request_uuid, is_active=True,
            channel_type__in=channel_types).first()

        if not channel:
            return HttpResponse("Channel not found for id: %s" % request_uuid,
                                status=400)

        auth = request.META.get("HTTP_AUTHORIZATION", "").split(" ")
        secret = channel.config.get(Channel.CONFIG_SECRET)
        if secret is not None and (len(auth) != 2 or auth[0] != "Token"
                                   or auth[1] != secret):
            return JsonResponse(dict(error="Incorrect authentication token"),
                                status=401)

        # Junebug is sending an event
        if action == "event":
            expected_keys = ["event_type", "message_id", "timestamp"]
            if not set(expected_keys).issubset(data.keys()):
                status = 400
                response_body = "Missing one of %s in request parameters." % (
                    ", ".join(expected_keys))
                event = HttpEvent(request_method, request_path, request_body,
                                  status, response_body)
                log_channel(channel,
                            "Failed to handle event.",
                            event,
                            is_error=True)
                return HttpResponse(response_body, status=status)

            message_id = data["message_id"]
            event_type = data["event_type"]

            # look up the message
            message = Msg.objects.filter(
                channel=channel,
                external_id=message_id).select_related("channel")
            if not message:
                status = 400
                response_body = "Message with external id of '%s' not found" % (
                    message_id, )
                event = HttpEvent(request_method, request_path, request_body,
                                  status, response_body)
                log_channel(channel,
                            "Failed to handle %s event_type." % (event_type),
                            event)
                return HttpResponse(response_body, status=status)

            if event_type == "submitted":
                for message_obj in message:
                    message_obj.status_sent()
            if event_type == "delivery_succeeded":
                for message_obj in message:
                    message_obj.status_delivered()
            elif event_type in ["delivery_failed", "rejected"]:
                for message_obj in message:
                    message_obj.status_fail()

            response_body = {
                "status": self.ACK,
                "message_ids": [message_obj.pk for message_obj in message]
            }
            event = HttpEvent(request_method, request_path, request_body, 200,
                              json.dumps(response_body))
            log_channel(channel, "Handled %s event_type." % (event_type),
                        event)
            # Let Junebug know we're happy
            return JsonResponse(response_body)

        # Handle an inbound message
        elif action == "inbound":
            expected_keys = [
                "channel_data",
                "from",
                "channel_id",
                "timestamp",
                "content",
                "to",
                "reply_to",
                "message_id",
            ]
            if not set(expected_keys).issubset(data.keys()):
                status = 400
                response_body = "Missing one of %s in request parameters." % (
                    ", ".join(expected_keys))
                event = HttpEvent(request_method, request_path, request_body,
                                  status, response_body)
                log_channel(channel,
                            "Failed to handle message.",
                            event,
                            is_error=True)
                return HttpResponse(response_body, status=status)

            if is_ussd:
                status = {
                    "close": USSDSession.INTERRUPTED,
                    "new": USSDSession.TRIGGERED
                }.get(channel_data.get("session_event"),
                      USSDSession.IN_PROGRESS)

                message_date = datetime.strptime(data["timestamp"],
                                                 "%Y-%m-%d %H:%M:%S.%f")
                gmt_date = pytz.timezone("GMT").localize(message_date)
                # Use a session id if provided, otherwise fall back to using the `from` address as the identifier
                session_id = channel_data.get("session_id") or data["from"]

                connection = USSDSession.handle_incoming(
                    channel=channel,
                    urn=data["from"],
                    content=data["content"],
                    status=status,
                    date=gmt_date,
                    external_id=session_id,
                    message_id=data["message_id"],
                    starcode=data["to"],
                )

                if connection:
                    status = 200
                    response_body = {
                        "status": self.ACK,
                        "session_id": connection.pk
                    }
                    event = HttpEvent(request_method, request_path,
                                      request_body, status,
                                      json.dumps(response_body))
                    log_channel(
                        channel, "Handled USSD message of %s session_event" %
                        (channel_data["session_event"], ), event)
                    return JsonResponse(response_body, status=status)
                else:
                    status = 400
                    response_body = {
                        "status": self.NACK,
                        "reason": "No suitable session found for this message."
                    }
                    event = HttpEvent(request_method, request_path,
                                      request_body, status,
                                      json.dumps(response_body))
                    log_channel(
                        channel,
                        "Failed to handle USSD message of %s session_event" %
                        (channel_data["session_event"], ),
                        event,
                    )
                    return JsonResponse(response_body, status=status)
            else:
                content = data["content"]
                message = Msg.create_incoming(channel,
                                              URN.from_tel(data["from"]),
                                              content)
                status = 200
                response_body = {"status": self.ACK, "message_id": message.pk}
                Msg.objects.filter(pk=message.id).update(
                    external_id=data["message_id"])
                event = HttpEvent(request_method, request_path, request_body,
                                  status, json.dumps(response_body))
                ChannelLog.log_message(message, "Handled inbound message.",
                                       event)
                return JsonResponse(response_body, status=status)
Ejemplo n.º 4
0
    def test_send_ussd_continue_and_end_session(self):
        flow = self.get_flow("ussd_session_end")
        contact = self.create_contact("Joe", "+250788383383")

        try:
            settings.SEND_MESSAGES = True

            with patch("requests.post") as mock:
                mock.return_value = MockResponse(
                    200, json.dumps({"result": {"message_id": "07033084-5cfd-4812-90a4-e4d24ffb6e3d"}})
                )

                flow.start([], [contact])

                # our outgoing message
                msg = Msg.objects.filter(direction="O").order_by("id").last()

                self.assertEqual(msg.direction, "O")
                self.assertTrue(msg.sent_on)
                self.assertEqual("07033084-5cfd-4812-90a4-e4d24ffb6e3d", msg.external_id)
                self.assertEqual(msg.connection.status, USSDSession.INITIATED)

                # reply and choose an option that doesn't have any destination thus needs to close the session
                USSDSession.handle_incoming(
                    channel=self.channel,
                    urn="+250788383383",
                    content="4",
                    date=timezone.now(),
                    external_id="21345",
                    message_id="jn-message-id",
                )
                # our outgoing message
                msg = Msg.objects.filter(direction="O").order_by("id").last()
                self.assertEqual(WIRED, msg.status)
                self.assertEqual(msg.direction, "O")
                self.assertTrue(msg.sent_on)
                self.assertEqual("07033084-5cfd-4812-90a4-e4d24ffb6e3d", msg.external_id)
                self.assertEqual("jn-message-id", msg.response_to.external_id)

                self.assertEqual(msg.connection.status, USSDSession.COMPLETED)
                self.assertTrue(isinstance(msg.connection.get_duration(), timedelta))

                self.assertEqual(2, mock.call_count)

                # first outbound (session continued)
                call = mock.call_args_list[0]
                (args, kwargs) = call
                payload = kwargs["json"]
                self.assertIsNone(payload.get("reply_to"))
                self.assertEqual(payload.get("to"), "+250788383383")
                self.assertEqual(payload["channel_data"], {"continue_session": True})

                # second outbound (session ended)
                call = mock.call_args_list[1]
                (args, kwargs) = call
                payload = kwargs["json"]
                self.assertEqual(payload["reply_to"], "jn-message-id")
                self.assertEqual(payload.get("to"), None)
                self.assertEqual(payload["channel_data"], {"continue_session": False})

                self.clear_cache()
        finally:
            settings.SEND_MESSAGES = False
Ejemplo n.º 5
0
    def post(self, request, *args, **kwargs):
        from temba.msgs.models import Msg

        request_body = request.body
        request_method = request.method
        request_path = request.get_full_path()

        def log_channel(channel, description, event, is_error=False):
            return ChannelLog.objects.create(channel_id=channel.pk,
                                             is_error=is_error,
                                             request=event.request_body,
                                             response=event.response_body,
                                             url=event.url,
                                             method=event.method,
                                             response_status=event.status_code,
                                             description=description)

        action = kwargs['action'].lower()
        request_uuid = kwargs['uuid']

        data = json.loads(force_text(request_body))
        is_ussd = self.is_ussd_message(data)
        channel_data = data.get('channel_data', {})
        channel_types = ('JNU', 'JN')

        # look up the channel
        channel = Channel.objects.filter(
            uuid=request_uuid, is_active=True,
            channel_type__in=channel_types).first()

        if not channel:
            return HttpResponse("Channel not found for id: %s" % request_uuid,
                                status=400)

        auth = request.META.get('HTTP_AUTHORIZATION', '').split(' ')
        secret = channel.config.get(Channel.CONFIG_SECRET)
        if secret is not None and (len(auth) != 2 or auth[0] != 'Token'
                                   or auth[1] != secret):
            return JsonResponse(dict(error="Incorrect authentication token"),
                                status=401)

        # Junebug is sending an event
        if action == 'event':
            expected_keys = ["event_type", "message_id", "timestamp"]
            if not set(expected_keys).issubset(data.keys()):
                status = 400
                response_body = "Missing one of %s in request parameters." % (
                    ', '.join(expected_keys))
                event = HttpEvent(request_method, request_path, request_body,
                                  status, response_body)
                log_channel(channel,
                            'Failed to handle event.',
                            event,
                            is_error=True)
                return HttpResponse(response_body, status=status)

            message_id = data['message_id']
            event_type = data["event_type"]

            # look up the message
            message = Msg.objects.filter(
                channel=channel,
                external_id=message_id).select_related('channel')
            if not message:
                status = 400
                response_body = "Message with external id of '%s' not found" % (
                    message_id, )
                event = HttpEvent(request_method, request_path, request_body,
                                  status, response_body)
                log_channel(channel,
                            'Failed to handle %s event_type.' % (event_type),
                            event)
                return HttpResponse(response_body, status=status)

            if event_type == 'submitted':
                for message_obj in message:
                    message_obj.status_sent()
            if event_type == 'delivery_succeeded':
                for message_obj in message:
                    message_obj.status_delivered()
            elif event_type in ['delivery_failed', 'rejected']:
                for message_obj in message:
                    message_obj.status_fail()

            response_body = {
                'status': self.ACK,
                'message_ids': [message_obj.pk for message_obj in message]
            }
            event = HttpEvent(request_method, request_path, request_body, 200,
                              json.dumps(response_body))
            log_channel(channel, 'Handled %s event_type.' % (event_type),
                        event)
            # Let Junebug know we're happy
            return JsonResponse(response_body)

        # Handle an inbound message
        elif action == 'inbound':
            expected_keys = [
                'channel_data',
                'from',
                'channel_id',
                'timestamp',
                'content',
                'to',
                'reply_to',
                'message_id',
            ]
            if not set(expected_keys).issubset(data.keys()):
                status = 400
                response_body = "Missing one of %s in request parameters." % (
                    ', '.join(expected_keys))
                event = HttpEvent(request_method, request_path, request_body,
                                  status, response_body)
                log_channel(channel,
                            'Failed to handle message.',
                            event,
                            is_error=True)
                return HttpResponse(response_body, status=status)

            if is_ussd:
                status = {
                    'close': USSDSession.INTERRUPTED,
                    'new': USSDSession.TRIGGERED,
                }.get(channel_data.get('session_event'),
                      USSDSession.IN_PROGRESS)

                message_date = datetime.strptime(data['timestamp'],
                                                 "%Y-%m-%d %H:%M:%S.%f")
                gmt_date = pytz.timezone('GMT').localize(message_date)
                # Use a session id if provided, otherwise fall back to using the `from` address as the identifier
                session_id = channel_data.get('session_id') or data['from']

                connection = USSDSession.handle_incoming(
                    channel=channel,
                    urn=data['from'],
                    content=data['content'],
                    status=status,
                    date=gmt_date,
                    external_id=session_id,
                    message_id=data['message_id'],
                    starcode=data['to'])

                if connection:
                    status = 200
                    response_body = {
                        'status': self.ACK,
                        'session_id': connection.pk,
                    }
                    event = HttpEvent(request_method, request_path,
                                      request_body, status,
                                      json.dumps(response_body))
                    log_channel(
                        channel, 'Handled USSD message of %s session_event' %
                        (channel_data['session_event'], ), event)
                    return JsonResponse(response_body, status=status)
                else:
                    status = 400
                    response_body = {
                        'status': self.NACK,
                        'reason': 'No suitable session found for this message.'
                    }
                    event = HttpEvent(request_method, request_path,
                                      request_body, status,
                                      json.dumps(response_body))
                    log_channel(
                        channel,
                        'Failed to handle USSD message of %s session_event' %
                        (channel_data['session_event'], ), event)
                    return JsonResponse(response_body, status=status)
            else:
                content = data['content']
                message = Msg.create_incoming(channel,
                                              URN.from_tel(data['from']),
                                              content)
                status = 200
                response_body = {
                    'status': self.ACK,
                    'message_id': message.pk,
                }
                Msg.objects.filter(pk=message.id).update(
                    external_id=data['message_id'])
                event = HttpEvent(request_method, request_path, request_body,
                                  status, json.dumps(response_body))
                ChannelLog.log_message(message, 'Handled inbound message.',
                                       event)
                return JsonResponse(response_body, status=status)