Пример #1
0
def process_incoming_message(*args, **kwargs):
    try:
        from corehq.messaging.smsbackends.telerivet.views import TELERIVET_INBOUND_FIELD_MAP
        fields = {a: kwargs[a] for (a, b) in TELERIVET_INBOUND_FIELD_MAP}
        log = IncomingRequest(**fields)
        log.save()
    except Exception as e:
        notify_exception(None, "Could not save Telerivet log entry")
        pass

    backend = SQLTelerivetBackend.by_webhook_secret(kwargs["secret"])
    if backend is None:
        # Ignore the message if the webhook secret is not recognized
        return

    if kwargs["from_number_e164"]:
        from_number = strip_plus(kwargs["from_number_e164"])
    else:
        from_number = strip_plus(kwargs["from_number"])

    if kwargs["event"] == EVENT_INCOMING:
        if kwargs["message_type"] == MESSAGE_TYPE_SMS:
            domain_scope = backend.domain if not backend.is_global else None
            incoming_sms(from_number,
                         kwargs["content"],
                         SQLTelerivetBackend.get_api_id(),
                         domain_scope=domain_scope,
                         backend_id=backend.couch_id)
        elif kwargs["message_type"] == MESSAGE_TYPE_CALL:
            incoming_ivr(from_number, "TELERIVET-%s" % kwargs["message_id"],
                         None)
Пример #2
0
    def _update(self, bundle):
        should_save = False
        for key, value in bundle.data.items():
            if getattr(bundle.obj, key, None) != value:
                if key == 'phone_numbers':
                    bundle.obj.phone_numbers = []
                    for idx, phone_number in enumerate(bundle.data.get('phone_numbers', [])):

                        bundle.obj.add_phone_number(strip_plus(phone_number))
                        if idx == 0:
                            bundle.obj.set_default_phone_number(strip_plus(phone_number))
                        should_save = True
                elif key == 'groups':
                    bundle.obj.set_groups(bundle.data.get("groups", []))
                    should_save = True
                elif key in ['email', 'username']:
                    setattr(bundle.obj, key, value.lower())
                    should_save = True
                elif key == 'password':
                    domain = Domain.get_by_name(bundle.obj.domain)
                    if domain.strong_mobile_passwords:
                        try:
                            clean_password(bundle.data.get("password"))
                        except ValidationError as e:
                            if not hasattr(bundle.obj, 'errors'):
                                bundle.obj.errors = []
                            bundle.obj.errors.append(e.message)
                            return False
                    bundle.obj.set_password(bundle.data.get("password"))
                    should_save = True
                else:
                    setattr(bundle.obj, key, value)
                    should_save = True
        return should_save
Пример #3
0
    def _update(self, bundle):
        should_save = False
        for key, value in bundle.data.items():
            if getattr(bundle.obj, key, None) != value:
                if key == 'phone_numbers':
                    bundle.obj.phone_numbers = []
                    for idx, phone_number in enumerate(bundle.data.get('phone_numbers', [])):

                        bundle.obj.add_phone_number(strip_plus(phone_number))
                        if idx == 0:
                            bundle.obj.set_default_phone_number(strip_plus(phone_number))
                        should_save = True
                elif key == 'groups':
                    bundle.obj.set_groups(bundle.data.get("groups", []))
                    should_save = True
                elif key in ['email', 'username']:
                    setattr(bundle.obj, key, value.lower())
                    should_save = True
                elif key == 'password':
                    domain = Domain.get_by_name(bundle.obj.domain)
                    if domain.strong_mobile_passwords:
                        try:
                            clean_password(bundle.data.get("password"))
                        except ValidationError as e:
                            if not hasattr(bundle.obj, 'errors'):
                                bundle.obj.errors = []
                            bundle.obj.errors.append(e.message)
                            return False
                    bundle.obj.set_password(bundle.data.get("password"))
                    should_save = True
                else:
                    setattr(bundle.obj, key, value)
                    should_save = True
        return should_save
Пример #4
0
def process_incoming_message(*args, **kwargs):
    try:
        from corehq.apps.telerivet.views import TELERIVET_INBOUND_FIELD_MAP
        fields = {a: kwargs[a] for (a, b) in TELERIVET_INBOUND_FIELD_MAP}
        log = IncomingRequest(**fields)
        log.save()
    except Exception as e:
        notify_exception(None, "Could not save Telerivet log entry")
        pass

    backend = TelerivetBackend.by_webhook_secret(kwargs["secret"])
    if backend is None:
        # Ignore the message if the webhook secret is not recognized
        return

    if kwargs["from_number_e164"]:
        from_number = strip_plus(kwargs["from_number_e164"])
    else:
        from_number = strip_plus(kwargs["from_number"])

    if kwargs["event"] == EVENT_INCOMING:
        if kwargs["message_type"] == MESSAGE_TYPE_SMS:
            incoming_sms(from_number, kwargs["content"], TelerivetBackend.get_api_id())
        elif kwargs["message_type"] == MESSAGE_TYPE_CALL:
            incoming_ivr(from_number, None,
                "TELERIVET-%s" % kwargs["message_id"], None)
Пример #5
0
 def run_script(self, script):
     commands = self.parse_script(script)
     for command in commands:
         phone_number = command['phone_number']
         v = PhoneNumber.by_phone(phone_number)
         if command['direction'] == '>':
             incoming(phone_number, command['text'], v.backend_id)
         else:
             msg = self.get_last_outbound_sms(v.owner_doc_type, v.owner_id)
             self.assertEqual(msg.text, unicode(command['text']))
             self.assertEqual(strip_plus(msg.phone_number), strip_plus(phone_number))
             msg.delete()
Пример #6
0
 def run_script(self, script):
     commands = self.parse_script(script)
     for command in commands:
         phone_number = command['phone_number']
         v = PhoneNumber.get_two_way_number(phone_number)
         if command['direction'] == '>':
             incoming(phone_number, command['text'], v.backend_id)
         else:
             msg = self.get_last_outbound_sms(v.owner_doc_type, v.owner_id)
             self.assertEqual(msg.text, unicode(command['text']))
             self.assertEqual(strip_plus(msg.phone_number), strip_plus(phone_number))
             msg.delete()
Пример #7
0
 def _update(self, bundle):
     should_save = False
     for key, value in bundle.data.items():
         if key == 'phone_numbers' and getattr(bundle.obj, key, None) != value:
             bundle.obj.phone_numbers = []
             for idx, phone_number in enumerate(bundle.data.get('phone_numbers', [])):
                 bundle.obj.add_phone_number(strip_plus(phone_number))
                 if idx == 0:
                     bundle.obj.set_default_phone_number(strip_plus(phone_number))
                 should_save = True
         elif getattr(bundle.obj, key, None) != value:
             setattr(bundle.obj, key, value)
             should_save = True
     return should_save
Пример #8
0
    def create_session_object(cls, domain, contact, phone_number, app, form, expire_after=MAX_SESSION_LENGTH,
            reminder_intervals=None, submit_partially_completed_forms=False,
            include_case_updates_in_partial_submissions=False):

        now = utcnow()

        session = cls(
            couch_id=uuid.uuid4().hex,
            connection_id=contact.get_id,
            form_xmlns=form.xmlns,
            start_time=now,
            modified_time=now,
            completed=False,
            domain=domain,
            user_id=contact.get_id,
            app_id=app.get_id,
            session_type=XFORMS_SESSION_SMS,
            phone_number=strip_plus(phone_number),
            expire_after=expire_after,
            session_is_open=True,
            reminder_intervals=reminder_intervals or [],
            current_reminder_num=0,
            submit_partially_completed_forms=submit_partially_completed_forms,
            include_case_updates_in_partial_submissions=include_case_updates_in_partial_submissions,
        )

        session.set_current_action_due_timestamp()

        return session
Пример #9
0
    def initiate_outbound_call(self, call, logged_subevent, ivr_data=None):
        """
        Same expected return value as corehq.apps.ivr.api.initiate_outbound_call
        """
        phone_number = strip_plus(call.phone_number)

        if phone_number.startswith("91"):
            phone_number = "0%s" % phone_number[2:]
        else:
            log_error(MessagingEvent.ERROR_UNSUPPORTED_COUNTRY, call, logged_subevent)
            return True

        response = self.invoke_kookoo_outbound_api(phone_number)
        status, message = self.get_status_and_message(response)

        do_not_retry = False
        if status == "queued":
            call.error = False
            call.gateway_session_id = "KOOKOO-%s" % message
        elif status == "error":
            call.error = True
            call.error_message = message
            if message.strip().upper() in ["CALLS WILL NOT BE MADE BETWEEN 9PM TO 9AM.", "PHONE NUMBER IN DND LIST"]:
                # These are error messages that come from KooKoo and
                # are indicative of non-recoverable errors, so we
                # wouldn't benefit from retrying the call.
                do_not_retry = True
            logged_subevent.error(MessagingEvent.ERROR_GATEWAY_ERROR)
        else:
            log_error(MessagingEvent.ERROR_GATEWAY_ERROR, call, logged_subevent)

        return not call.error or do_not_retry
Пример #10
0
    def send(self, msg, *args, **kwargs):
        if self.additional_params is not None:
            params = self.additional_params.copy()
        else:
            params = {}
        
        phone_number = msg.phone_number
        if self.include_plus:
            phone_number = clean_phone_number(phone_number)
        else:
            phone_number = strip_plus(phone_number)
        
        try:
            text = msg.text.encode("iso-8859-1")
        except UnicodeEncodeError:
            text = msg.text.encode("utf-8")
        params[self.message_param] = text
        params[self.number_param] = phone_number

        url_params = urlencode(params)
        try:
            if self.method == "GET":
                response = urlopen("%s?%s" % (self.url, url_params),
                    timeout=settings.SMS_GATEWAY_TIMEOUT).read()
            else:
                response = urlopen(self.url, url_params,
                    timeout=settings.SMS_GATEWAY_TIMEOUT).read()
        except Exception as e:
            msg = "Error sending message from backend: '{}'\n\n{}".format(self.name, str(e))
            raise BackendProcessingException(msg), None, sys.exc_info()[2]
Пример #11
0
    def send(self, msg, delay=True, *args, **kwargs):
        phone_number = strip_plus(msg.phone_number)
        if not phone_number.startswith("63"):
            raise MegamobileException(
                "Only Filipino phone numbers are supported")
        phone_number = phone_number[2:]

        text = msg.text.encode("utf-8")

        pid = None
        if msg.in_reply_to:
            original_msg = SMSLog.get(msg.in_reply_to)
            pid = getattr(original_msg, "megamobile_pid", None)
        pid = pid or DEFAULT_PID
        setattr(msg, "megamobile_pid", pid)
        msg.save()

        params = urlencode({
            "pid": pid,
            "cel": phone_number,
            "msg": text,
            "src": self.source_identifier,
        })
        api_account_name = quote(self.api_account_name)
        url = "http://api.mymegamobile.com/%s?%s" % (api_account_name, params)
        response = urlopen(url, timeout=settings.SMS_GATEWAY_TIMEOUT).read()
Пример #12
0
    def send(self, msg, *args, **kwargs):
        config = self.config
        if config.additional_params:
            params = config.additional_params.copy()
        else:
            params = {}

        phone_number = msg.phone_number
        if config.include_plus:
            phone_number = clean_phone_number(phone_number)
        else:
            phone_number = strip_plus(phone_number)

        params[config.message_param] = self._encode_http_message(msg.text)
        params[config.number_param] = phone_number

        url_params = urlencode(params)
        try:
            unverified = ssl._create_unverified_context()
            if config.method == "GET":
                urlopen(
                    "%s?%s" % (config.url, url_params),
                    context=unverified,
                    timeout=settings.SMS_GATEWAY_TIMEOUT,
                ).read()
            else:
                urlopen(
                    config.url,
                    url_params,
                    context=unverified,
                    timeout=settings.SMS_GATEWAY_TIMEOUT,
                ).read()
        except Exception as e:
            msg = "Error sending message from backend: '{}'\n\n{}".format(self.pk, str(e))
            six.reraise(BackendProcessingException, BackendProcessingException(msg), sys.exc_info()[2])
Пример #13
0
    def create_session_object(cls, domain, contact, phone_number, app, form, expire_after=MAX_SESSION_LENGTH,
            reminder_intervals=None, submit_partially_completed_forms=False,
            include_case_updates_in_partial_submissions=False):

        now = utcnow()

        session = cls(
            couch_id=uuid.uuid4().hex,
            connection_id=contact.get_id,
            form_xmlns=form.xmlns,
            start_time=now,
            modified_time=now,
            completed=False,
            domain=domain,
            user_id=contact.get_id,
            app_id=app.get_id,
            session_type=XFORMS_SESSION_SMS,
            phone_number=strip_plus(phone_number),
            expire_after=expire_after,
            session_is_open=True,
            reminder_intervals=reminder_intervals or [],
            current_reminder_num=0,
            submit_partially_completed_forms=submit_partially_completed_forms,
            include_case_updates_in_partial_submissions=include_case_updates_in_partial_submissions,
        )

        session.set_current_action_due_timestamp()

        return session
Пример #14
0
def validate_row(row, domain, data_cols):
    """pre-validate the information in a particular import row: valid location,
    reporting user, and data formats
    """
    # identify location
    loc_code = row.get('outlet_code') or row.get('site_code')
    row['loc'] = get_supply_point(domain, loc_code)['case']
    if row['loc'] is None:
        set_error(row, 'ERROR location code is invalid')
        return

    # identify user
    phone = row.get('phone')
    owner = None
    if phone:
        vn = VerifiedNumber.by_phone(phone)
        if not vn:
            set_error(row, 'ERROR phone number is not verified with any user')
            return
        owner = vn.owner
        row['phone'] = strip_plus(phone)

    username = row.get('reporter')
    if username:
        user = CouchUser.get_by_username('%s@%s.commcarehq.org' %
                                         (username, domain))
        if not user:
            set_error(row, 'ERROR reporter user does not exist')
            return

    if owner:
        if user and user._id != owner._id:
            set_error(row, 'ERROR phone number does not belong to user')
            return
        user = owner
    row['user'] = user

    # validate other fields

    try:
        row['timestamp'] = datetime.strptime(row['date'],
                                             '%Y-%m-%d')  # TODO: allow time?
    except ValueError:
        set_error(row, 'ERROR invalid date format')
        return

    for k in data_cols:
        val = row[k]
        if val:
            try:
                int(val)
            except ValueError:
                set_error(
                    row,
                    'ERROR invalid data value "%s" in column "%s"' % (val, k))
                return

    if all(not row[k] for k in data_cols):
        set_error(row, 'ERROR stock report is empty')
        return
Пример #15
0
def ivr_in(request):
    """
    Handles tropo call requests
    """
    from tropo import Tropo
    if request.method == "POST":
        data = json.loads(request.body.decode('utf-8'))
        phone_number = data["session"]["from"]["id"]

        if phone_number:
            cleaned_number = strip_plus(phone_number)
            v = PhoneNumber.by_extensive_search(cleaned_number)
        else:
            v = None

        # Save the call entry
        msg = Call(
            phone_number=cleaned_number,
            direction=INCOMING,
            date=datetime.utcnow(),
            backend_api=SQLTropoBackend.get_api_id(),
        )
        if v is not None:
            msg.domain = v.domain
            msg.couch_recipient_doc_type = v.owner_doc_type
            msg.couch_recipient = v.owner_id
        msg.save()

        t = Tropo()
        t.reject()
        return HttpResponse(t.RenderJson())
    else:
        return HttpResponseBadRequest("Bad Request")
Пример #16
0
def ivr_in(request):
    """
    Handles tropo call requests
    """
    if request.method == "POST":
        data = json.loads(request.body)
        phone_number = data["session"]["from"]["id"]
        # TODO: Implement tropo as an ivr backend. In the meantime, just log the call.

        if phone_number:
            cleaned_number = strip_plus(phone_number)
            v = VerifiedNumber.by_extensive_search(cleaned_number)
        else:
            v = None

        # Save the call entry
        msg = CallLog(
            phone_number=cleaned_number,
            direction=INCOMING,
            date=datetime.utcnow(),
            backend_api=TropoBackend.get_api_id(),
        )
        if v is not None:
            msg.domain = v.domain
            msg.couch_recipient_doc_type = v.owner_doc_type
            msg.couch_recipient = v.owner_id
        msg.save()

        t = Tropo()
        t.reject()
        return HttpResponse(t.RenderJson())
    else:
        return HttpResponseBadRequest("Bad Request")
Пример #17
0
    def send(self, msg, *args, **kwargs):
        if self.additional_params is not None:
            params = self.additional_params.copy()
        else:
            params = {}

        phone_number = msg.phone_number
        if self.include_plus:
            phone_number = clean_phone_number(phone_number)
        else:
            phone_number = strip_plus(phone_number)

        try:
            text = msg.text.encode("iso-8859-1")
        except UnicodeEncodeError:
            text = msg.text.encode("utf-8")
        params[self.message_param] = text
        params[self.number_param] = phone_number

        url_params = urlencode(params)
        if self.method == "GET":
            response = urlopen("%s?%s" % (self.url, url_params),
                               timeout=settings.SMS_GATEWAY_TIMEOUT).read()
        else:
            response = urlopen(self.url,
                               url_params,
                               timeout=settings.SMS_GATEWAY_TIMEOUT).read()
Пример #18
0
    def send(self, msg, orig_phone_number=None, *args, **kwargs):
        config = self.config
        phone_number = strip_plus(msg.phone_number)
        try:
            text = msg.text.encode("iso-8859-1")
            msg_type = "PM"
        except UnicodeEncodeError:
            text = msg.text.encode("utf_16_be").encode('hex').upper()
            msg_type = "UC"
        params = {
            "username": config.username,
            "pin": config.pin,
            "mnumber": phone_number,
            "message": text,
            "signature": config.sender_id,
            "msgType": msg_type,
            "splitAlgm": "concat",
        }
        url_params = urlencode(params)
        url = 'https://smsgw.sms.gov.in/failsafe/HttpLink'
        response = urlopen("%s?%s" % (url, url_params),
                           timeout=settings.SMS_GATEWAY_TIMEOUT).read()

        response_code = self.get_response_code(response)
        if response_code != '000':
            self.handle_error(response_code, msg)
Пример #19
0
def ivr_in(request):
    """
    Handles tropo call requests
    """
    if request.method == "POST":
        data = json.loads(request.body)
        phone_number = data["session"]["from"]["id"]
        # TODO: Implement tropo as an ivr backend. In the meantime, just log the call.

        if phone_number:
            cleaned_number = strip_plus(phone_number)
            v = VerifiedNumber.by_extensive_search(cleaned_number)
        else:
            v = None

        # Save the call entry
        msg = CallLog(
            phone_number=cleaned_number,
            direction=INCOMING,
            date=datetime.utcnow(),
            backend_api=SQLTropoBackend.get_api_id(),
        )
        if v is not None:
            msg.domain = v.domain
            msg.couch_recipient_doc_type = v.owner_doc_type
            msg.couch_recipient = v.owner_id
        msg.save()

        t = Tropo()
        t.reject()
        return HttpResponse(t.RenderJson())
    else:
        return HttpResponseBadRequest("Bad Request")
Пример #20
0
    def send(self, msg, orig_phone_number=None, *args, **kwargs):
        config = self.config
        phone_number = strip_plus(msg.phone_number)

        if not self.destination_number_is_valid(phone_number):
            msg.set_system_error(SMS.ERROR_INVALID_DESTINATION_NUMBER)
            return

        try:
            text = msg.text.encode("iso-8859-1")
            msg_type = "PM"
        except UnicodeEncodeError:
            text = msg.text.encode("utf_16_be").encode('hex').upper()
            msg_type = "UC"
        params = {
            "username": config.username,
            "pin": config.pin,
            "mnumber": phone_number,
            "message": text,
            "signature": config.sender_id,
            "msgType": msg_type,
            "splitAlgm": "concat",
        }
        url_params = urlencode(params)
        url = 'https://smsgw.sms.gov.in/failsafe/HttpLink'
        response = urlopen("%s?%s" % (url, url_params),
                           timeout=settings.SMS_GATEWAY_TIMEOUT).read()

        response_code = self.get_response_code(response)
        if response_code != '000':
            self.handle_error(response_code, msg)
Пример #21
0
    def send_sms(self, msg, delay=True, *args, **kwargs):
        phone_number = strip_plus(msg.phone_number)
        if not phone_number.startswith("63"):
            raise MegamobileException("Only Filipino phone numbers are supported")
        phone_number = phone_number[2:]

        text = msg.text.encode("utf-8")

        pid = None
        if msg.in_reply_to:
            original_msg = SMSLog.get(msg.in_reply_to)
            pid = getattr(original_msg, "megamobile_pid", None)
        pid = pid or DEFAULT_PID
        setattr(msg, "megamobile_pid", pid)
        msg.save()

        params = urlencode({
            "pid" : pid,
            "cel" : phone_number,
            "msg" : text,
            "src" : self.source_identifier,
        })
        api_account_name = quote(self.api_account_name)
        url = "http://api.mymegamobile.com/%s?%s" % (api_account_name, params)
        response = urlopen(url, timeout=settings.SMS_GATEWAY_TIMEOUT).read()
Пример #22
0
    def send(self, msg, *args, **kwargs):
        config = self.config
        if config.additional_params:
            params = config.additional_params.copy()
        else:
            params = {}

        phone_number = msg.phone_number
        if config.include_plus:
            phone_number = clean_phone_number(phone_number)
        else:
            phone_number = strip_plus(phone_number)

        try:
            text = msg.text.encode("iso-8859-1")
        except UnicodeEncodeError:
            text = msg.text.encode("utf-8")
        params[config.message_param] = text
        params[config.number_param] = phone_number

        url_params = urlencode(params)
        try:
            if config.method == "GET":
                response = urlopen("%s?%s" % (config.url, url_params),
                    timeout=settings.SMS_GATEWAY_TIMEOUT).read()
            else:
                response = urlopen(config.url, url_params,
                    timeout=settings.SMS_GATEWAY_TIMEOUT).read()
        except Exception as e:
            msg = "Error sending message from backend: '{}'\n\n{}".format(self.pk, str(e))
            six.reraise(BackendProcessingException(msg), None, sys.exc_info()[2])
Пример #23
0
 def test_sms_registration_no_user(self):
     # Test with no username
     no_username_phone_number = "+99912345678"
     incoming(no_username_phone_number, 'JOIN {} WORKER'.format(self.domain), self.backend.hq_api_id)
     self.assertIsNotNone(CommCareUser.get_by_username(
         format_username(strip_plus(no_username_phone_number), self.domain)
     ))
Пример #24
0
 def send(self, msg, *args, **kwargs):
     if self.additional_params is not None:
         params = self.additional_params.copy()
     else:
         params = {}
     
     phone_number = msg.phone_number
     if self.include_plus:
         phone_number = clean_phone_number(phone_number)
     else:
         phone_number = strip_plus(phone_number)
     
     try:
         text = msg.text.encode("iso-8859-1")
     except UnicodeEncodeError:
         text = msg.text.encode("utf-8")
     params[self.message_param] = text
     params[self.number_param] = phone_number
     
     url_params = urlencode(params)
     if self.method == "GET":
         response = urlopen("%s?%s" % (self.url, url_params),
             timeout=settings.SMS_GATEWAY_TIMEOUT).read()
     else:
         response = urlopen(self.url, url_params,
             timeout=settings.SMS_GATEWAY_TIMEOUT).read()
Пример #25
0
    def phone_lookup(cls, view_name, phone_number, include_pending=False):
        # We use .one() here because the framework prevents duplicates
        # from being entered when a contact saves a number.
        # See CommCareMobileContactMixin.save_verified_number()
        from corehq.apps.sms.util import strip_plus

        v = cls.view(view_name, key=strip_plus(phone_number), include_docs=True).one()
        return v if (include_pending or (v and v.verified)) else None
Пример #26
0
 def _update(self, bundle):
     should_save = False
     for key, value in bundle.data.items():
         if key == 'phone_numbers' and getattr(bundle.obj, key,
                                               None) != value:
             bundle.obj.phone_numbers = []
             for idx, phone_number in enumerate(
                     bundle.data.get('phone_numbers', [])):
                 bundle.obj.add_phone_number(strip_plus(phone_number))
                 if idx == 0:
                     bundle.obj.set_default_phone_number(
                         strip_plus(phone_number))
                 should_save = True
         elif getattr(bundle.obj, key, None) != value:
             setattr(bundle.obj, key, value)
             should_save = True
     return should_save
Пример #27
0
 def get_or_create(cls, phone_number):
     """
     phone_number - should be a string of digits
     """
     phone_number = smsutil.strip_plus(phone_number)
     if not phone_number:
         return (None, False)
     return cls.objects.get_or_create(phone_number=phone_number)
Пример #28
0
def process_sms_registration(msg):
    """
    This method handles registration via sms.
    Returns True if a contact was registered, False if not.
    
    To have a case register itself, do the following:

        1) Select "Enable Case Registration Via SMS" in project settings, and fill in the
        associated Case Registration settings.

        2) Text in "join <domain>", where <domain> is the domain to join. If the sending
        number does not exist in the system, a case will be registered tied to that number.
        The "join" keyword can be any keyword in REGISTRATION_KEYWORDS. This is meant to
        support multiple translations.
    
    To have a mobile worker register itself, do the following:

        NOTE: This is not yet implemented and may change slightly.

        1) Select "Enable Mobile Worker Registration via SMS" in project settings.

        2) Text in "join <domain> worker", where <domain> is the domain to join. If the
        sending number does not exist in the system, a PendingCommCareUser object will be
        created, tied to that number.
        The "join" and "worker" keywords can be any keyword in REGISTRATION_KEYWORDS and
        REGISTRATION_MOBILE_WORKER_KEYWORDS, respectively. This is meant to support multiple 
        translations.

        3) A domain admin will have to approve the addition of the mobile worker before
        a CommCareUser can actually be created.
    """
    registration_processed = False
    text_words = msg.text.upper().split()
    keyword1 = text_words[0] if len(text_words) > 0 else ""
    keyword2 = text_words[1].lower() if len(text_words) > 1 else ""
    keyword3 = text_words[2] if len(text_words) > 2 else ""
    if keyword1 in REGISTRATION_KEYWORDS and keyword2 != "":
        domain = Domain.get_by_name(keyword2, strict=True)
        if domain is not None:
            if domain_has_privilege(domain, privileges.INBOUND_SMS):
                if keyword3 in REGISTRATION_MOBILE_WORKER_KEYWORDS and domain.sms_mobile_worker_registration_enabled:
                    #TODO: Register a PendingMobileWorker object that must be approved by a domain admin
                    pass
                elif domain.sms_case_registration_enabled:
                    register_sms_contact(
                        domain=domain.name,
                        case_type=domain.sms_case_registration_type,
                        case_name="unknown",
                        user_id=domain.sms_case_registration_user_id,
                        contact_phone_number=strip_plus(msg.phone_number),
                        contact_phone_number_is_verified="1",
                        owner_id=domain.sms_case_registration_owner_id,
                    )
                    registration_processed = True
            msg.domain = domain.name
            msg.save()

    return registration_processed
Пример #29
0
 def clean_phone_numbers(self):
     value = self.cleaned_data.get('phone_numbers', '')
     phone_list = [strip_plus(s.strip()) for s in value.split(',')]
     phone_list = [phone for phone in phone_list if phone]
     if len(phone_list) == 0:
         raise ValidationError(_("This field is required."))
     for phone_number in phone_list:
         validate_phone_number(phone_number)
     return list(set(phone_list))
Пример #30
0
 def clean_phone_numbers(self):
     value = self.cleaned_data.get('phone_numbers', '')
     phone_list = [strip_plus(s.strip()) for s in value.split(',')]
     phone_list = [phone for phone in phone_list if phone]
     if len(phone_list) == 0:
         raise ValidationError(_("This field is required."))
     for phone_number in phone_list:
         validate_phone_number(phone_number)
     return list(set(phone_list))
Пример #31
0
def process_sms_registration(msg):
    """
    This method handles registration via sms.
    Returns True if a contact was registered, False if not.
    
    To have a case register itself, do the following:

        1) Select "Enable Case Registration Via SMS" in project settings, and fill in the
        associated Case Registration settings.

        2) Text in "join <domain>", where <domain> is the domain to join. If the sending
        number does not exist in the system, a case will be registered tied to that number.
        The "join" keyword can be any keyword in REGISTRATION_KEYWORDS. This is meant to
        support multiple translations.
    
    To have a mobile worker register itself, do the following:

        NOTE: This is not yet implemented and may change slightly.

        1) Select "Enable Mobile Worker Registration via SMS" in project settings.

        2) Text in "join <domain> worker", where <domain> is the domain to join. If the
        sending number does not exist in the system, a PendingCommCareUser object will be
        created, tied to that number.
        The "join" and "worker" keywords can be any keyword in REGISTRATION_KEYWORDS and
        REGISTRATION_MOBILE_WORKER_KEYWORDS, respectively. This is meant to support multiple 
        translations.

        3) A domain admin will have to approve the addition of the mobile worker before
        a CommCareUser can actually be created.
    """
    registration_processed = False
    text_words = msg.text.upper().split()
    keyword1 = text_words[0] if len(text_words) > 0 else ""
    keyword2 = text_words[1].lower() if len(text_words) > 1 else ""
    keyword3 = text_words[2] if len(text_words) > 2 else ""
    if keyword1 in REGISTRATION_KEYWORDS and keyword2 != "":
        domain = Domain.get_by_name(keyword2, strict=True)
        if domain is not None:
            if keyword3 in REGISTRATION_MOBILE_WORKER_KEYWORDS and domain.sms_mobile_worker_registration_enabled:
                #TODO: Register a PendingMobileWorker object that must be approved by a domain admin
                pass
            elif domain.sms_case_registration_enabled:
                register_sms_contact(
                    domain=domain.name,
                    case_type=domain.sms_case_registration_type,
                    case_name="unknown",
                    user_id=domain.sms_case_registration_user_id,
                    contact_phone_number=strip_plus(msg.phone_number),
                    contact_phone_number_is_verified="1",
                    owner_id=domain.sms_case_registration_owner_id,
                )
                msg.domain = domain.name
                msg.save()
                registration_processed = True

    return registration_processed
Пример #32
0
 def phone_lookup(cls, view_name, phone_number, include_pending=False):
     # We use .one() here because the framework prevents duplicates
     # from being entered when a contact saves a number.
     # See CommCareMobileContactMixin.save_verified_number()
     from corehq.apps.sms.util import strip_plus
     v = cls.view(view_name,
                  key=strip_plus(phone_number),
                  include_docs=True).one()
     return v if (include_pending or (v and v.verified)) else None
Пример #33
0
def validate_row(row, domain, data_cols):
    """pre-validate the information in a particular import row: valid location,
    reporting user, and data formats
    """
    # identify location
    loc_code = row.get('outlet_code') or row.get('site_code')
    row['loc'] = get_supply_point(domain, loc_code)['case']
    if row['loc'] is None:
        set_error(row, 'ERROR location code is invalid')
        return

    # identify user
    phone = row.get('phone')
    owner = None
    if phone:
        vn = VerifiedNumber.by_phone(phone)
        if not vn:
            set_error(row, 'ERROR phone number is not verified with any user')
            return
        owner = vn.owner
        row['phone'] = strip_plus(phone)

    username = row.get('reporter')
    if username:
        user = CouchUser.get_by_username('%s@%s.commcarehq.org' % (username, domain))
        if not user:
            set_error(row, 'ERROR reporter user does not exist')
            return

    if owner:
        if user and user._id != owner._id:
            set_error(row, 'ERROR phone number does not belong to user')
            return
        user = owner
    row['user'] = user

    # validate other fields

    try:
        row['timestamp'] = datetime.strptime(row['date'], '%Y-%m-%d') # TODO: allow time?
    except ValueError:
        set_error(row, 'ERROR invalid date format')
        return

    for k in data_cols:
        val = row[k]
        if val:
            try:
                int(val)
            except ValueError:
                set_error(row, 'ERROR invalid data value "%s" in column "%s"' % (val, k))
                return

    if all(not row[k] for k in data_cols):
        set_error(row, 'ERROR stock report is empty')
        return
Пример #34
0
    def get_phone_number(phone_number):
        """
        Only send to Indian phone numbers. Also remove the country code before
        making the request.
        """
        phone_number = strip_plus(phone_number)
        if re.match(r'^(91)[6-9]\d{9}$', phone_number):
            return phone_number[2:]

        raise InvalidDestinationNumber()
Пример #35
0
    def get_phone_number(phone_number):
        """
        Only send to Indian phone numbers. Also remove the country code before
        making the request.
        """
        phone_number = strip_plus(phone_number)
        if phone_number.startswith('91') and len(phone_number) > 2:
            return phone_number[2:]

        raise InvalidDestinationNumber()
Пример #36
0
 def handle_error(self, response, msg):
     phone = strip_plus(msg.phone_number)
     if not (phone.startswith(GHANA_COUNTRY_CODE) and len(phone) == GHANA_PHONE_LENGTH):
         msg.set_system_error(SMS.ERROR_INVALID_DESTINATION_NUMBER)
         return
     data = self.get_additional_data(response)
     raise SMSGHException("Error with the SMSGH backend. "
         "Response Code: %s, Subcode: %s. See "
         "http://developers.smsgh.com/documentations/sendmessage#handlingerrors "
         "for details. " % (response.status_code, data.get('Status')))
Пример #37
0
    def get_phone_number(phone_number):
        """
        Only send to Indian phone numbers. Also remove the country code before
        making the request.
        """
        phone_number = strip_plus(phone_number)
        if phone_number.startswith('91') and len(phone_number) > 2:
            return phone_number[2:]

        raise InvalidDestinationNumber()
Пример #38
0
def get_ivr_backend(recipient, verified_number=None, unverified_number=None):
    if verified_number and verified_number.ivr_backend_id:
        return MobileBackend.get(verified_number.ivr_backend_id)
    else:
        phone_number = verified_number.phone_number if verified_number else unverified_number
        phone_number = strip_plus(str(phone_number))
        prefixes = settings.IVR_BACKEND_MAP.keys()
        prefixes = sorted(prefixes, key=lambda x: len(x), reverse=True)
        for prefix in prefixes:
            if phone_number.startswith(prefix):
                return MobileBackend.get(settings.IVR_BACKEND_MAP[prefix])
    return None
Пример #39
0
def _update_default_phone_number(user, phone_number, user_change_logger):
    old_phone_numbers = set(user.phone_numbers)
    new_phone_numbers = set(user.phone_numbers)
    if not isinstance(phone_number, str):
        raise InvalidFormatException('default_phone_number', 'string')
    formatted_phone_number = strip_plus(phone_number)
    new_phone_numbers.add(formatted_phone_number)
    user.set_default_phone_number(formatted_phone_number)

    if user_change_logger:
        _log_phone_number_change(new_phone_numbers, old_phone_numbers,
                                 user_change_logger)
Пример #40
0
    def get_params(self, msg_obj):
        config = self.config
        message = msg_obj.text.encode('utf_16_be').encode('hex').upper()
        message_type = LONG_UNICODE_MSG_TYPE

        return {
            'usr': config.username,
            'pass': config.password,
            'msisdn': strip_plus(msg_obj.phone_number),
            'sid': config.sender_id,
            'mt': message_type,
            'msg': message,
        }
Пример #41
0
def get_ivr_backend(recipient, verified_number=None, unverified_number=None):
    if verified_number and verified_number.ivr_backend_id:
        return MobileBackend.get(verified_number.ivr_backend_id)
    else:
        phone_number = (verified_number.phone_number
                        if verified_number else unverified_number)
        phone_number = strip_plus(str(phone_number))
        prefixes = settings.IVR_BACKEND_MAP.keys()
        prefixes = sorted(prefixes, key=lambda x: len(x), reverse=True)
        for prefix in prefixes:
            if phone_number.startswith(prefix):
                return MobileBackend.get(settings.IVR_BACKEND_MAP[prefix])
    return None
Пример #42
0
    def _update(self, bundle):
        should_save = False
        for key, value in bundle.data.items():
            if getattr(bundle.obj, key, None) != value:
                if key == 'phone_numbers':
                    bundle.obj.phone_numbers = []
                    for idx, phone_number in enumerate(bundle.data.get('phone_numbers', [])):

                        bundle.obj.add_phone_number(strip_plus(phone_number))
                        if idx == 0:
                            bundle.obj.set_default_phone_number(strip_plus(phone_number))
                        should_save = True
                elif key == 'groups':
                    bundle.obj.set_groups(bundle.data.get("groups", []))
                    should_save = True
                elif key in ['email', 'username']:
                    setattr(bundle.obj, key, value.lower())
                    should_save = True
                else:
                    setattr(bundle.obj, key, value)
                    should_save = True
        return should_save
Пример #43
0
 def test_unicode(self):
     queued_sms = QueuedSMS(
         phone_number=TEST_PHONE_NUMBER,
         text=TEST_UNICODE_MESSAGE,
     )
     self.assertEqual(
         self.backend.get_params(queued_sms), {
             'usr': TEST_USERNAME,
             'pass': TEST_PASSWORD,
             'msisdn': strip_plus(TEST_PHONE_NUMBER),
             'sid': TEST_SENDER_ID,
             'mt': LONG_UNICODE_MSG_TYPE,
             'msg': '0928092E0938094D09240947',
         })
Пример #44
0
def _update_phone_numbers(user, phone_numbers, user_change_logger):
    old_phone_numbers = set(user.phone_numbers)
    new_phone_numbers = set()
    user.phone_numbers = []
    for idx, phone_number in enumerate(phone_numbers):
        formatted_phone_number = strip_plus(phone_number)
        new_phone_numbers.add(formatted_phone_number)
        user.add_phone_number(formatted_phone_number)
        if idx == 0:
            user.set_default_phone_number(formatted_phone_number)

    if user_change_logger:
        _log_phone_number_change(new_phone_numbers, old_phone_numbers,
                                 user_change_logger)
Пример #45
0
 def test_ascii(self):
     queued_sms = QueuedSMS(
         phone_number=TEST_PHONE_NUMBER,
         text=TEST_TEXT_MESSAGE,
     )
     self.assertEqual(
         self.backend.get_params(queued_sms), {
             'usr': TEST_USERNAME,
             'pass': TEST_PASSWORD,
             'msisdn': strip_plus(TEST_PHONE_NUMBER),
             'sid': TEST_SENDER_ID,
             'mt': LONG_TEXT_MSG_TYPE,
             'msg': TEST_TEXT_MESSAGE,
         })
Пример #46
0
    def _update(self, bundle):
        should_save = False
        for key, value in bundle.data.items():
            if getattr(bundle.obj, key, None) != value:
                if key == 'phone_numbers':
                    bundle.obj.phone_numbers = []
                    for idx, phone_number in enumerate(
                            bundle.data.get('phone_numbers', [])):

                        bundle.obj.add_phone_number(strip_plus(phone_number))
                        if idx == 0:
                            bundle.obj.set_default_phone_number(
                                strip_plus(phone_number))
                        should_save = True
                elif key == 'groups':
                    bundle.obj.set_groups(bundle.data.get("groups", []))
                    should_save = True
                elif key in ['email', 'username']:
                    setattr(bundle.obj, key, value.lower())
                    should_save = True
                else:
                    setattr(bundle.obj, key, value)
                    should_save = True
        return should_save
Пример #47
0
    def test_params(self):
        self.queued_sms.text = TEST_TEXT_MESSAGE
        params = self.vertex_backend.populate_params(self.queued_sms)
        self.assertEqual(params['username'], TEST_USERNAME)
        self.assertEqual(params['pass'], TEST_PASSWORD)
        self.assertEqual(params['senderid'], TEST_SENDER_ID)
        self.assertEqual(params['response'], 'Y')
        self.assertEqual(params['dest_mobileno'], strip_plus(TEST_PHONE_NUMBER))
        self.assertEqual(params['msgtype'], TEXT_MSG_TYPE)
        self.assertEqual(params['message'], TEST_TEXT_MESSAGE)

        self.queued_sms.text = TEST_UNICODE_MESSAGE
        params = self.vertex_backend.populate_params(self.queued_sms)
        self.assertEqual(params['message'], TEST_UNICODE_MESSAGE.encode('utf-8'))
        self.assertEqual(params['msgtype'], UNICODE_MSG_TYPE)
Пример #48
0
    def get_verified_number(self, phone=None):
        """
        Retrieves this contact's verified number entry by (self.doc_type, self._id).

        return  the VerifiedNumber entry
        """
        from corehq.apps.sms.util import strip_plus
        verified = self.get_verified_numbers(True)
        if not phone:
            # for backwards compatibility with code that assumes only one verified phone #
            if len(verified) > 0:
                return sorted(verified.iteritems())[0][1]
            else:
                return None

        return verified.get(strip_plus(phone))
Пример #49
0
 def test_ascii(self):
     queued_sms = QueuedSMS(
         phone_number=TEST_PHONE_NUMBER,
         text=TEST_TEXT_MESSAGE,
     )
     self.assertEqual(
         self.backend.get_params(queued_sms),
         {
             'usr': TEST_USERNAME,
             'pass': TEST_PASSWORD,
             'msisdn': strip_plus(TEST_PHONE_NUMBER),
             'sid': TEST_SENDER_ID,
             'mt': LONG_TEXT_MSG_TYPE,
             'msg': TEST_TEXT_MESSAGE,
         }
     )
Пример #50
0
def log_call(phone_number, gateway_session_id, backend_module=None):
    cleaned_number = strip_plus(phone_number)
    v = VerifiedNumber.by_extensive_search(cleaned_number)

    call = CallLog(
        phone_number=cleaned_number,
        direction=INCOMING,
        date=datetime.utcnow(),
        backend_api=backend_module.API_ID if backend_module else None,
        gateway_session_id=gateway_session_id,
    )
    if v:
        call.domain = v.domain
        call.couch_recipient_doc_type = v.owner_doc_type
        call.couch_recipient = v.owner_id
    call.save()
Пример #51
0
    def get_verified_number(self, phone=None):
        """
        Retrieves this contact's verified number entry by (self.doc_type, self._id).

        return  the VerifiedNumber entry
        """
        from corehq.apps.sms.util import strip_plus
        verified = self.get_verified_numbers(True)
        if not phone:
            # for backwards compatibility with code that assumes only one verified phone #
            if len(verified) > 0:
                return sorted(verified.iteritems())[0][1]
            else:
                return None

        return verified.get(strip_plus(phone))
Пример #52
0
def log_call(phone_number, gateway_session_id, backend_module=None):
    cleaned_number = strip_plus(phone_number)
    v = VerifiedNumber.by_extensive_search(cleaned_number)

    call = CallLog(
        phone_number=cleaned_number,
        direction=INCOMING,
        date=datetime.utcnow(),
        backend_api=backend_module.API_ID if backend_module else None,
        gateway_session_id=gateway_session_id,
    )
    if v:
        call.domain = v.domain
        call.couch_recipient_doc_type = v.owner_doc_type
        call.couch_recipient = v.owner_id
    call.save()
Пример #53
0
 def test_unicode(self):
     queued_sms = QueuedSMS(
         phone_number=TEST_PHONE_NUMBER,
         text=TEST_UNICODE_MESSAGE,
     )
     self.assertEqual(
         self.backend.get_params(queued_sms),
         {
             'usr': TEST_USERNAME,
             'pass': TEST_PASSWORD,
             'msisdn': strip_plus(TEST_PHONE_NUMBER),
             'sid': TEST_SENDER_ID,
             'mt': LONG_UNICODE_MSG_TYPE,
             'msg': '0928092E0938094D09240947',
         }
     )
Пример #54
0
def log_call(phone_number, gateway_session_id, backend=None):
    cleaned_number = strip_plus(phone_number)
    v = PhoneNumber.by_extensive_search(cleaned_number)

    call = Call(
        phone_number=cleaned_number,
        direction=INCOMING,
        date=datetime.utcnow(),
        backend_api=backend.get_api_id() if backend else None,
        backend_id=backend.couch_id if backend else None,
        gateway_session_id=gateway_session_id,
    )
    if v:
        call.domain = v.domain
        call.couch_recipient_doc_type = v.owner_doc_type
        call.couch_recipient = v.owner_id
    call.save()
Пример #55
0
def log_call(phone_number, gateway_session_id, backend=None):
    cleaned_number = strip_plus(phone_number)
    v = PhoneNumber.by_extensive_search(cleaned_number)

    call = Call(
        phone_number=cleaned_number,
        direction=INCOMING,
        date=datetime.utcnow(),
        backend_api=backend.get_api_id() if backend else None,
        backend_id=backend.couch_id if backend else None,
        gateway_session_id=gateway_session_id,
    )
    if v:
        call.domain = v.domain
        call.couch_recipient_doc_type = v.owner_doc_type
        call.couch_recipient = v.owner_id
    call.save()
Пример #56
0
def process_incoming_message(*args, **kwargs):
    backend = TelerivetBackend.by_webhook_secret(kwargs["secret"])
    if backend is None:
        # Ignore the message if the webhook secret is not recognized
        return

    from_number = strip_plus(kwargs["from_number"])
    if backend.country_code:
        if not from_number.startswith(backend.country_code):
            from_number = "%s%s" % (backend.country_code, from_number)

    if kwargs["event"] == EVENT_INCOMING:
        if kwargs["message_type"] == MESSAGE_TYPE_SMS:
            incoming_sms(from_number, kwargs["content"], TelerivetBackend.get_api_id())
        elif kwargs["message_type"] == MESSAGE_TYPE_CALL:
            incoming_ivr(from_number, None,
                "TELERIVET-%s" % kwargs["message_id"], None)
Пример #57
0
def initiate_outbound_call(call_log_entry, logged_subevent, ivr_data=None, *args, **kwargs):
    """
    Expected kwargs:
        api_key

    Same expected return value as corehq.apps.ivr.api.initiate_outbound_call
    """
    phone_number = strip_plus(call_log_entry.phone_number)

    if phone_number.startswith('91'):
        phone_number = '0%s' % phone_number[2:]
    else:
        log_error(MessagingEvent.ERROR_UNSUPPORTED_COUNTRY,
            call_log_entry, logged_subevent)
        return True

    response = invoke_kookoo_outbound_api(phone_number, kwargs['api_key'], kwargs.get('is_test', False))
    status, message = get_status_and_message(response)

    do_not_retry = False
    if status == 'queued':
        call_log_entry.error = False
        call_log_entry.gateway_session_id = 'KOOKOO-%s' % message

        if ivr_data:
            set_first_ivr_response(call_log_entry, call_log_entry.gateway_session_id,
                ivr_data, get_http_response_string)
    elif status == 'error':
        call_log_entry.error = True
        call_log_entry.error_message = message
        if (message.strip().upper() in [
            'CALLS WILL NOT BE MADE BETWEEN 9PM TO 9AM.',
            'PHONE NUMBER IN DND LIST',
        ]):
            # These are error messages that come from KooKoo and
            # are indicative of non-recoverable errors, so we
            # wouldn't benefit from retrying the call.
            do_not_retry = True
        logged_subevent.error(MessagingEvent.ERROR_GATEWAY_ERROR)
    else:
        log_error(MessagingEvent.ERROR_GATEWAY_ERROR,
            call_log_entry, logged_subevent)

    call_log_entry.save()
    return not call_log_entry.error or do_not_retry
Пример #58
0
def apply_leniency(contact_phone_number):
    """
    The documentation says that contact_phone_number should be
    in international format and consist of only digits. However,
    we can apply some leniency to avoid common mistakes.
    Returns None if an unsupported data type is passed in.
    """
    from corehq.apps.sms.util import strip_plus
    # Decimal preserves trailing zeroes, so it's ok 
    if isinstance(contact_phone_number, (int, long, Decimal)):
        contact_phone_number = str(contact_phone_number)
    if isinstance(contact_phone_number, basestring):
        chars = re.compile(r"(\s|-|\.)+")
        contact_phone_number = chars.sub("", contact_phone_number)
        contact_phone_number = strip_plus(contact_phone_number)
    else:
        contact_phone_number = None
    return contact_phone_number
Пример #59
0
    def get_params(self, msg_obj):
        config = self.config
        try:
            message = msg_obj.text
            message.encode('ascii')
            message_type = LONG_TEXT_MSG_TYPE
        except UnicodeEncodeError:
            message = codecs.encode(codecs.encode(msg_obj.text, 'utf_16_be'), 'hex').decode('utf-8').upper()
            message_type = LONG_UNICODE_MSG_TYPE

        return {
            'usr': config.username,
            'pass': config.password,
            'msisdn': strip_plus(msg_obj.phone_number),
            'sid': config.sender_id,
            'mt': message_type,
            'msg': message,
        }
Пример #60
0
    def get_json_payload(self, msg_obj):
        config = self.config
        text, content_type = self.get_text_and_content_type(msg_obj)

        # 'vp' is validity period, and we set it to the maximum value (1 day)
        return {
            'ver': '1.0',
            'key': self.get_auth_key(),
            'messages': [
                {
                    'dest': [strip_plus(msg_obj.phone_number)],
                    'send': config.sender_id,
                    'text': text,
                    'type': content_type,
                    'vp': '1440',
                },
            ],
        }