def testSync(self):
        extra_number = PhoneNumber.objects.create(
            domain=self.domain,
            owner_doc_type='X',
            owner_id='owner1',
            phone_number='999123',
            verified=True,
            pending_verification=False,
            is_two_way=True
        )

        user = self.mobile_worker1
        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1)

        user.phone_numbers = ['9990001']
        user.save()
        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 2)
        self.assertPhoneEntries(user, ['9990001'])

        before = user.get_phone_entries()['9990001']

        user.phone_numbers = ['9990001', '9990002']
        user.save()
        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 3)
        self.assertPhoneEntries(user, ['9990001', '9990002'])

        after = user.get_phone_entries()['9990001']
        self.assertEqual(before.pk, after.pk)

        user.phone_numbers = ['9990002']
        user.save()
        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 2)
        self.assertPhoneEntries(user, ['9990002'])

        self.assertEqual(PhoneNumber.get_two_way_number('999123'), extra_number)
    def test_sms(self):
        from corehq.apps.sms.models import PhoneNumber, MessagingEvent, MessagingSubEvent
        expected_object_counts = Counter({PhoneNumber: 1, MessagingEvent: 1, MessagingSubEvent: 1})

        phone_number = PhoneNumber(
            domain=self.domain_name,
            owner_doc_type='CommCareCase',
            owner_id='fake-owner-id1',
            phone_number='99912341234',
            backend_id=None,
            ivr_backend_id=None,
            verified=True,
            is_two_way=True,
            pending_verification=False,
            contact_last_modified=datetime.utcnow()
        )
        phone_number.save()
        event = MessagingEvent.objects.create(
            domain=self.domain_name,
            date=datetime.utcnow(),
            source=MessagingEvent.SOURCE_REMINDER,
            content_type=MessagingEvent.CONTENT_SMS,
            status=MessagingEvent.STATUS_COMPLETED
        )
        MessagingSubEvent.objects.create(
            parent=event,
            date=datetime.utcnow(),
            recipient_type=MessagingEvent.RECIPIENT_CASE,
            content_type=MessagingEvent.CONTENT_SMS,
            status=MessagingEvent.STATUS_COMPLETED
        )

        self._dump_and_load(expected_object_counts)
    def test_backend(self):
        backend1 = SQLTestSMSBackend.objects.create(
            hq_api_id=SQLTestSMSBackend.get_api_id(),
            is_global=False,
            domain=self.domain,
            name='BACKEND1'
        )

        backend2 = SQLTestSMSBackend.objects.create(
            hq_api_id=SQLTestSMSBackend.get_api_id(),
            is_global=False,
            domain=self.domain,
            name='BACKEND2'
        )

        SQLMobileBackendMapping.set_default_domain_backend(self.domain, backend1)

        number = PhoneNumber(domain=self.domain, phone_number='+999123')
        self.assertEqual(number.backend, backend1)

        number.backend_id = backend2.name
        self.assertEqual(number.backend, backend2)

        number.backend_id = '  '
        self.assertEqual(number.backend, backend1)
    def test_by_owner_id(self):
        number = PhoneNumber.objects.create(
            domain=self.domain,
            owner_doc_type='X',
            owner_id='owner1',
            phone_number='999123',
            verified=True,
            pending_verification=False,
            is_two_way=True
        )

        [lookup] = PhoneNumber.by_owner_id('owner1')
        self.assertEqual(lookup, number)

        # test cache clear
        number.owner_id = 'owner2'
        number.save()
        self.assertEqual(PhoneNumber.by_owner_id('owner1'), [])
        [lookup] = PhoneNumber.by_owner_id('owner2')
        self.assertEqual(lookup, number)

        number.verified = False
        number.is_two_way = False
        number.save()
        [lookup] = PhoneNumber.by_owner_id('owner2')
        self.assertFalse(lookup.verified)
        self.assertFalse(lookup.is_two_way)
Exemple #5
0
    def testCouchSyncToSQL(self):
        self.assertEqual(self.getCouchCount(), 0)
        self.assertEqual(self.getSQLCount(), 0)

        # Test Create
        couch_obj = VerifiedNumber()
        self.setRandomCouchObjectValues(couch_obj)
        couch_obj.save()

        sleep(1)
        self.assertEqual(self.getCouchCount(), 1)
        self.assertEqual(self.getSQLCount(), 1)

        sql_obj = PhoneNumber.objects.get(couch_id=couch_obj._id)
        self.checkFieldValues(couch_obj, sql_obj, PhoneNumber._migration_get_fields())
        self.assertTrue(VerifiedNumber.get_db().get_rev(couch_obj._id).startswith('1-'))

        # Test Update
        self.setRandomCouchObjectValues(couch_obj)
        couch_obj.save()

        sleep(1)
        self.assertEqual(self.getCouchCount(), 1)
        self.assertEqual(self.getSQLCount(), 1)
        sql_obj = PhoneNumber.objects.get(couch_id=couch_obj._id)
        self.checkFieldValues(couch_obj, sql_obj, PhoneNumber._migration_get_fields())
        self.assertTrue(VerifiedNumber.get_db().get_rev(couch_obj._id).startswith('2-'))

        # Test Delete
        couch_id = couch_obj._id
        couch_obj.delete()
        with self.assertRaises(ResourceNotFound):
            VerifiedNumber.get(couch_id)
        self.assertEqual(self.getCouchCount(), 0)
        self.assertEqual(self.getSQLCount(), 0)
    def assertMatch(self, match, phone_search, suffix_search, owner_id_search):
        lookedup = PhoneNumber.get_two_way_number(phone_search)
        self.assertPhoneNumbersEqual(match, lookedup)

        lookedup = PhoneNumber.get_two_way_number_by_suffix(suffix_search)
        self.assertPhoneNumbersEqual(match, lookedup)

        [lookedup] = PhoneNumber.by_owner_id(owner_id_search)
        self.assertPhoneNumbersEqual(match, lookedup)
Exemple #7
0
def process_incoming(msg, delay=True):
    v = VerifiedNumber.by_phone(msg.phone_number, include_pending=True)

    if v is not None and v.verified:
        msg.couch_recipient_doc_type = v.owner_doc_type
        msg.couch_recipient = v.owner_id
        msg.domain = v.domain
        contact = v.owner
        if isinstance(contact, CommCareUser) and hasattr(contact, 'location_id'):
            msg.location_id = contact.location_id
        msg.save()

    if msg.domain_scope:
        # only process messages for phones known to be associated with this domain
        if v is None or v.domain != msg.domain_scope:
            raise DomainScopeValidationError(
                'Attempted to simulate incoming sms from phone number not ' \
                'verified with this domain'
            )

    can_receive_sms = PhoneNumber.can_receive_sms(msg.phone_number)
    opt_in_keywords, opt_out_keywords = get_opt_keywords(msg)
    if is_opt_message(msg.text, opt_out_keywords) and can_receive_sms:
        if PhoneNumber.opt_out_sms(msg.phone_number):
            metadata = MessageMetadata(ignore_opt_out=True)
            text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0],))
            if v:
                send_sms_to_verified_number(v, text, metadata=metadata)
            else:
                send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata)
    elif is_opt_message(msg.text, opt_in_keywords) and not can_receive_sms:
        if PhoneNumber.opt_in_sms(msg.phone_number):
            text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0],))
            if v:
                send_sms_to_verified_number(v, text)
            else:
                send_sms(msg.domain, None, msg.phone_number, text)
    elif v is not None and v.verified:
        if domain_has_privilege(msg.domain, privileges.INBOUND_SMS):
            for h in settings.SMS_HANDLERS:
                try:
                    handler = to_function(h)
                except:
                    notify_exception(None, message=('error loading sms handler: %s' % h))
                    continue

                try:
                    was_handled = handler(v, msg.text, msg=msg)
                except Exception, e:
                    log_sms_exception(msg)
                    was_handled = False

                if was_handled:
                    break
    def testGetOrCreate(self):
        before = self.mobile_worker1.get_or_create_phone_entry('999123')
        self.assertEqual(before.owner_doc_type, 'CommCareUser')
        self.assertEqual(before.owner_id, self.mobile_worker1.get_id)
        self.assertEqual(before.phone_number, '999123')
        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1)

        after = self.mobile_worker1.get_or_create_phone_entry('999123')
        self.assertEqual(before.pk, after.pk)
        self.assertEqual(after.owner_doc_type, 'CommCareUser')
        self.assertEqual(after.owner_id, self.mobile_worker1.get_id)
        self.assertEqual(after.phone_number, '999123')
        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1)
    def testRetire(self):
        self.mobile_worker1.phone_numbers = ['9990001']
        self.mobile_worker1.save()
        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1)
        self.assertPhoneEntries(self.mobile_worker1, ['9990001'])

        self.mobile_worker2.phone_numbers = ['9990002']
        self.mobile_worker2.save()
        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 2)
        self.assertPhoneEntries(self.mobile_worker2, ['9990002'])

        self.mobile_worker1.retire()
        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1)
        self.assertPhoneEntries(self.mobile_worker2, ['9990002'])
Exemple #10
0
def get_inbound_phone_entry(msg):
    if msg.backend_id:
        backend = SQLMobileBackend.load(msg.backend_id, is_couch_id=True)
        if not backend.is_global and toggles.INBOUND_SMS_LENIENCY.enabled(backend.domain):
            p = PhoneNumber.get_two_way_number_with_domain_scope(msg.phone_number, backend.domains_with_access)
            return (
                p,
                p is not None
            )

    return (
        PhoneNumber.get_reserved_number(msg.phone_number),
        False
    )
Exemple #11
0
    def save_verified_number(self, domain, phone_number, verified, backend_id=None, ivr_backend_id=None, only_one_number_allowed=False):
        """
        Saves the given phone number as this contact's verified phone number.

        backend_id - the name of an SMSBackend to use when sending SMS to
            this number; if specified, this will override any project or
            global settings for which backend will be used to send sms to
            this number

        return  The PhoneNumber
        raises  InvalidFormatException if the phone number format is invalid
        raises  PhoneNumberInUseException if the phone number is already in use by another contact
        """
        from corehq.apps.sms.models import PhoneNumber

        phone_number = apply_leniency(phone_number)
        self.verify_unique_number(phone_number)
        if only_one_number_allowed:
            v = self.get_verified_number()
        else:
            v = self.get_verified_number(phone_number)
        if v is None:
            v = PhoneNumber(
                owner_doc_type=self.doc_type,
                owner_id=self.get_id
            )
        v.domain = domain
        v.phone_number = phone_number
        v.verified = verified
        v.backend_id = backend_id
        v.ivr_backend_id = ivr_backend_id
        v.save()
    def testDelete(self):
        self.mobile_worker1.get_or_create_phone_entry('999123')
        self.mobile_worker1.get_or_create_phone_entry('999124')
        self.mobile_worker1.get_or_create_phone_entry('999125')
        self.mobile_worker2.get_or_create_phone_entry('999126')
        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 4)

        self.mobile_worker1.delete_phone_entry('999124')
        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 3)

        entries = self.mobile_worker1.get_phone_entries()
        self.assertEqual(set(entries.keys()), set(['999123', '999125']))

        entries = self.mobile_worker2.get_phone_entries()
        self.assertEqual(set(entries.keys()), set(['999126']))
Exemple #13
0
def initiate_sms_verification_workflow(contact, phone_number):
    # For now this is only applicable to mobile workers
    assert isinstance(contact, CommCareUser)

    logged_event = MessagingEvent.get_current_verification_event(
        contact.domain, contact.get_id, phone_number)

    with CriticalSection(['verifying-phone-number-%s' % phone_number]):
        vn = PhoneNumber.by_phone(phone_number, include_pending=True)
        if vn:
            if vn.owner_id != contact.get_id:
                return VERIFICATION__ALREADY_IN_USE
            if vn.verified:
                return VERIFICATION__ALREADY_VERIFIED
            else:
                result = VERIFICATION__RESENT_PENDING
        else:
            contact.save_verified_number(contact.domain, phone_number, False)
            result = VERIFICATION__WORKFLOW_STARTED
            # Always create a new event when the workflow starts
            if logged_event:
                logged_event.status = MessagingEvent.STATUS_NOT_COMPLETED
                logged_event.save()
            logged_event = MessagingEvent.create_verification_event(contact.domain, contact)

        if not logged_event:
            logged_event = MessagingEvent.create_verification_event(contact.domain, contact)

        send_verification(contact.domain, contact, phone_number, logged_event)
        return result
Exemple #14
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 = 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")
Exemple #15
0
    def get_two_way_entry_or_phone_number(cls, recipient, try_usercase=True, domain_for_toggles=None):
        """
        If recipient has a two-way number, returns it as a PhoneNumber entry.
        If recipient does not have a two-way number but has a phone number configured,
        returns the one-way phone number as a string.

        If try_usercase is True and recipient is a CommCareUser who doesn't have a
        two-way or one-way phone number, then it will try to get the two-way or
        one-way number from the user's user case if one exists.
        """
        if settings.USE_PHONE_ENTRIES:
            phone_entry = get_two_way_number_for_recipient(recipient)
            if phone_entry:
                return phone_entry

        phone_number = get_one_way_number_for_recipient(recipient)

        if toggles.INBOUND_SMS_LENIENCY.enabled(domain_for_toggles) and \
                toggles.ONE_PHONE_NUMBER_MULTIPLE_CONTACTS.enabled(domain_for_toggles):
            phone_entry = PhoneNumber.get_phone_number_for_owner(recipient.get_id, phone_number)
            if phone_entry:
                return phone_entry

        # Avoid processing phone numbers that are obviously fake (len <= 3) to
        # save on processing time
        if phone_number and len(phone_number) > 3:
            return phone_number

        if try_usercase and isinstance(recipient, CommCareUser) and recipient.memoized_usercase:
            return cls.get_two_way_entry_or_phone_number(recipient.memoized_usercase,
                                                         domain_for_toggles=domain_for_toggles)

        return None
Exemple #16
0
    def test_opt_out_and_opt_in(self):
        self.assertEqual(PhoneBlacklist.objects.count(), 0)

        incoming('99912345678', 'join opt-test', 'GVI')
        v = PhoneNumber.get_two_way_number('99912345678')
        self.assertIsNotNone(v)

        incoming('99912345678', 'stop', 'GVI')
        self.assertEqual(PhoneBlacklist.objects.count(), 1)
        phone_number = PhoneBlacklist.objects.get(phone_number='99912345678')
        self.assertFalse(phone_number.send_sms)
        self.assertEqual(phone_number.domain, self.domain)
        self.assertIsNotNone(phone_number.last_sms_opt_out_timestamp)
        self.assertIsNone(phone_number.last_sms_opt_in_timestamp)

        sms = self.get_last_sms('+99912345678')
        self.assertEqual(sms.direction, 'O')
        self.assertEqual(sms.text, get_message(MSG_OPTED_OUT, context=('START',)))

        incoming('99912345678', 'start', 'GVI')
        self.assertEqual(PhoneBlacklist.objects.count(), 1)
        phone_number = PhoneBlacklist.objects.get(phone_number='99912345678')
        self.assertTrue(phone_number.send_sms)
        self.assertEqual(phone_number.domain, self.domain)
        self.assertIsNotNone(phone_number.last_sms_opt_out_timestamp)
        self.assertIsNotNone(phone_number.last_sms_opt_in_timestamp)

        sms = self.get_last_sms('+99912345678')
        self.assertEqual(sms.direction, 'O')
        self.assertEqual(sms.text, get_message(MSG_OPTED_IN, context=('STOP',)))
    def testUserSyncNoChange(self):
        before = self.mobile_worker1.get_or_create_phone_entry('999123')
        before.set_two_way()
        before.set_verified()
        before.save()
        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1)

        self.mobile_worker1.phone_numbers = ['999123']
        self.mobile_worker1.save()
        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 1)

        after = self.mobile_worker1.get_phone_entries()['999123']
        self.assertEqual(before.pk, after.pk)
        self.assertTrue(after.is_two_way)
        self.assertTrue(after.verified)
        self.assertFalse(after.pending_verification)
Exemple #18
0
    def test_sending_to_opted_out_number(self):
        self.assertEqual(PhoneBlacklist.objects.count(), 0)

        incoming('99912345678', 'join opt-test', 'GVI')
        v = PhoneNumber.get_two_way_number('99912345678')
        self.assertIsNotNone(v)

        send_sms_to_verified_number(v, 'hello')
        sms = self.get_last_sms('+99912345678')
        self.assertEqual(sms.direction, 'O')
        self.assertEqual(sms.text, 'hello')

        incoming('99912345678', 'stop', 'GVI')
        self.assertEqual(PhoneBlacklist.objects.count(), 1)
        phone_number = PhoneBlacklist.objects.get(phone_number='99912345678')
        self.assertFalse(phone_number.send_sms)

        send_sms_to_verified_number(v, 'hello')
        sms = self.get_last_sms('+99912345678')
        self.assertEqual(sms.direction, 'O')
        self.assertEqual(sms.text, 'hello')
        self.assertTrue(sms.error)
        self.assertEqual(sms.system_error_message, SMS.ERROR_PHONE_NUMBER_OPTED_OUT)

        incoming('99912345678', 'start', 'GVI')
        self.assertEqual(PhoneBlacklist.objects.count(), 1)
        phone_number = PhoneBlacklist.objects.get(phone_number='99912345678')
        self.assertTrue(phone_number.send_sms)

        send_sms_to_verified_number(v, 'hello')
        sms = self.get_last_sms('+99912345678')
        self.assertEqual(sms.direction, 'O')
        self.assertEqual(sms.text, 'hello')
        self.assertFalse(sms.error)
        self.assertIsNone(sms.system_error_message)
Exemple #19
0
def handle_due_survey_action(domain, contact_id, session_id):
    with critical_section_for_smsforms_sessions(contact_id):
        session = SQLXFormsSession.by_session_id(session_id)
        if (not session or not session.session_is_open
                or session.current_action_due > utcnow()):
            return

        if session.current_action_is_a_reminder:
            # Resend the current question in the open survey to the contact
            p = PhoneNumber.get_phone_number_for_owner(session.connection_id,
                                                       session.phone_number)
            if p:
                metadata = MessageMetadata(
                    workflow=session.workflow,
                    xforms_session_couch_id=session._id,
                )
                resp = current_question(session.session_id)
                send_sms_to_verified_number(
                    p,
                    resp.event.text_prompt,
                    metadata,
                    logged_subevent=session.related_subevent)

            session.move_to_next_action()
            session.save()
        else:
            # Close the session
            session.close()
            session.save()
Exemple #20
0
def process_incoming(msg):
    v = PhoneNumber.by_phone(msg.phone_number, include_pending=True)

    if v:
        msg.couch_recipient_doc_type = v.owner_doc_type
        msg.couch_recipient = v.owner_id
        msg.domain = v.domain
        msg.location_id = get_location_id_by_verified_number(v)
        msg.save()
    elif msg.domain_scope:
        msg.domain = msg.domain_scope
        msg.save()

    can_receive_sms = PhoneBlacklist.can_receive_sms(msg.phone_number)
    opt_in_keywords, opt_out_keywords = get_opt_keywords(msg)
    domain = v.domain if v else None

    if is_opt_message(msg.text, opt_out_keywords) and can_receive_sms:
        if PhoneBlacklist.opt_out_sms(msg.phone_number, domain=domain):
            metadata = MessageMetadata(ignore_opt_out=True)
            text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0],))
            if v:
                send_sms_to_verified_number(v, text, metadata=metadata)
            else:
                send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata)
    elif is_opt_message(msg.text, opt_in_keywords) and not can_receive_sms:
        if PhoneBlacklist.opt_in_sms(msg.phone_number, domain=domain):
            text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0],))
            if v:
                send_sms_to_verified_number(v, text)
            else:
                send_sms(msg.domain, None, msg.phone_number, text)
    else:
        handled = False
        is_verified = v is not None and v.verified

        if msg.domain and domain_has_privilege(msg.domain, privileges.INBOUND_SMS):
            handled = load_and_call(settings.CUSTOM_SMS_HANDLERS, v, msg.text, msg)

            if not handled and is_verified and is_contact_active(v.domain, v.owner_doc_type, v.owner_id):
                handled = load_and_call(settings.SMS_HANDLERS, v, msg.text, msg)

        if not handled and not is_verified:
            handled = process_pre_registration(msg)

            if not handled:
                handled = process_sms_registration(msg)

            if not handled:
                import verify
                verify.process_verification(v, msg)

    # If the sms queue is enabled, then the billable gets created in remove_from_queue()
    if (
        not settings.SMS_QUEUE_ENABLED and
        msg.domain and
        domain_has_privilege(msg.domain, privileges.INBOUND_SMS)
    ):
        create_billable_for_sms(msg)
Exemple #21
0
 def test_case_owner(self):
     with create_test_case(self.domain, 'participant', 'test') as case:
         number = PhoneNumber(domain=self.domain,
                              owner_doc_type='CommCareCase',
                              owner_id=case.case_id)
         owner = number.owner
         self.assertTrue(is_commcarecase(owner))
         self.assertEqual(owner.case_id, case.case_id)
    def test_user_owner(self):
        mobile_user = CommCareUser.create(self.domain, 'abc', 'def')
        number = PhoneNumber(owner_doc_type='CommCareUser',
                             owner_id=mobile_user.get_id)
        owner = number.owner
        self.assertTrue(isinstance(owner, CommCareUser))
        self.assertEqual(owner.get_id, mobile_user.get_id)

        web_user = WebUser.create(self.domain, 'ghi', 'jkl')
        number = PhoneNumber(owner_doc_type='WebUser',
                             owner_id=web_user.get_id)
        owner = number.owner
        self.assertTrue(isinstance(owner, WebUser))
        self.assertEqual(owner.get_id, web_user.get_id)

        number = PhoneNumber(owner_doc_type='X')
        self.assertIsNone(number.owner)
    def test_other_registration_from_invite(self):
        self.domain_obj.sms_mobile_worker_registration_enabled = True
        self.domain_obj.enable_registration_welcome_sms_for_mobile_worker = True
        self.domain_obj.save()

        user_data = {'abc': 'def'}

        # Initiate Registration Workflow
        SelfRegistrationInvitation.initiate_workflow(
            self.domain,
            [SelfRegistrationUserInfo('999123', user_data)],
            app_id=self.app_id,
        )

        self.assertRegistrationInvitation(
            phone_number='999123',
            app_id=self.app_id,
            phone_type=None,
            android_only=False,
            require_email=False,
            custom_user_data=user_data,
            status=SelfRegistrationInvitation.STATUS_PENDING,
        )

        self.assertLastOutgoingSMS(
            '+999123', [_MESSAGES[MSG_MOBILE_WORKER_INVITATION_START]])

        # Choose phone type 'other'
        incoming('+999123', '2', self.backend.hq_api_id)

        self.assertRegistrationInvitation(
            phone_number='999123',
            app_id=self.app_id,
            phone_type=SelfRegistrationInvitation.PHONE_TYPE_OTHER,
            android_only=False,
            require_email=False,
            custom_user_data=user_data,
            status=SelfRegistrationInvitation.STATUS_PENDING,
        )

        self.assertLastOutgoingSMS(
            '+999123',
            [_MESSAGES[MSG_MOBILE_WORKER_JAVA_INVITATION].format(self.domain)])

        # Register over SMS
        incoming('+999123', 'JOIN {} WORKER test'.format(self.domain),
                 self.backend.hq_api_id)
        user = CommCareUser.get_by_username(
            format_username('test', self.domain))
        self.assertIsNotNone(user)
        self.assertEqual(user.user_data, user_data)
        self.assertEqual(PhoneNumber.by_phone('999123').owner_id, user.get_id)

        self.assertLastOutgoingSMS(
            '+999123', [_MESSAGES[MSG_REGISTRATION_WELCOME_MOBILE_WORKER]])

        self.assertRegistrationInvitation(
            status=SelfRegistrationInvitation.STATUS_REGISTERED, )
Exemple #24
0
    def test_suffix_lookup(self):
        number1 = PhoneNumber.objects.create(domain=self.domain,
                                             owner_doc_type='X',
                                             owner_id='X',
                                             phone_number='999123',
                                             verified=True,
                                             pending_verification=False,
                                             is_two_way=True)

        number2 = PhoneNumber.objects.create(domain=self.domain,
                                             owner_doc_type='X',
                                             owner_id='X',
                                             phone_number='999223',
                                             verified=True,
                                             pending_verification=False,
                                             is_two_way=True)

        self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('1 23'),
                         number1)
        self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('2 23'),
                         number2)
        self.assertIsNone(PhoneNumber.get_two_way_number_by_suffix('23'))

        # test update
        number1.phone_number = '999124'
        number1.save()
        number2.phone_number = '999224'
        number2.save()
        self.assertIsNone(PhoneNumber.get_two_way_number_by_suffix('1 23'))
        self.assertIsNone(PhoneNumber.get_two_way_number_by_suffix('2 23'))
        self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('124'),
                         number1)
        self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('224'),
                         number2)
    def test_suffix_lookup(self):
        number1 = PhoneNumber.objects.create(
            domain=self.domain,
            owner_doc_type='X',
            owner_id='X',
            phone_number='999123',
            verified=True,
            pending_verification=False,
            is_two_way=True
        )

        number2 = PhoneNumber.objects.create(
            domain=self.domain,
            owner_doc_type='X',
            owner_id='X',
            phone_number='999223',
            verified=True,
            pending_verification=False,
            is_two_way=True
        )

        self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('1 23'), number1)
        self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('2 23'), number2)
        self.assertIsNone(PhoneNumber.get_two_way_number_by_suffix('23'))

        # test update
        number1.phone_number = '999124'
        number1.save()
        number2.phone_number = '999224'
        number2.save()
        self.assertIsNone(PhoneNumber.get_two_way_number_by_suffix('1 23'))
        self.assertIsNone(PhoneNumber.get_two_way_number_by_suffix('2 23'))
        self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('124'), number1)
        self.assertEqual(PhoneNumber.get_two_way_number_by_suffix('224'), number2)
Exemple #26
0
def send_message_via_backend(msg, backend=None, orig_phone_number=None):
    """send sms using a specific backend

    msg - outbound message object
    backend - MobileBackend object to use for sending; if None, use
      msg.outbound_backend
    orig_phone_number - the originating phone number to use when sending; this
      is sent in if the backend supports load balancing
    """
    try:
        msg.text = clean_text(msg.text)
    except Exception:
        logging.exception("Could not clean text for sms dated '%s' in domain '%s'" % (msg.date, msg.domain))
    try:
        if not domain_has_privilege(msg.domain, privileges.OUTBOUND_SMS):
            raise Exception(
                ("Domain '%s' does not have permission to send SMS."
                 "  Please investigate why this function was called.") % msg.domain
            )

        phone_obj = PhoneNumber.get_by_phone_number_or_none(msg.phone_number)
        if phone_obj and not phone_obj.send_sms:
            if msg.ignore_opt_out and phone_obj.can_opt_in:
                # If ignore_opt_out is True on the message, then we'll still
                # send it. However, if we're not letting the phone number
                # opt back in and it's in an opted-out state, we will not
                # send anything to it no matter the state of the ignore_opt_out
                # flag.
                pass
            else:
                msg.set_system_error(SMS.ERROR_PHONE_NUMBER_OPTED_OUT)
                return False

        if not backend:
            backend = msg.outbound_backend
            # note: this will handle "verified" contacts that are still pending
            # verification, thus the backend is None. it's best to only call
            # send_sms_to_verified_number on truly verified contacts, though

        if not msg.backend_id:
            msg.backend_id = backend._id

        if backend.domain_is_authorized(msg.domain):
            backend.send(msg, orig_phone_number=orig_phone_number)
        else:
            raise BackendAuthorizationException("Domain '%s' is not authorized to use backend '%s'" % (msg.domain, backend._id))

        try:
            msg.backend_api = backend.__class__.get_api_id()
        except Exception:
            pass
        msg.save()
        create_billable_for_sms(msg)
        return True
    except Exception:
        log_sms_exception(msg)
        return False
Exemple #27
0
def process_incoming(msg):
    v = PhoneNumber.by_phone(msg.phone_number, include_pending=True)

    if v is not None and v.verified:
        msg.couch_recipient_doc_type = v.owner_doc_type
        msg.couch_recipient = v.owner_id
        msg.domain = v.domain
        msg.location_id = get_location_id_by_verified_number(v)
        msg.save()

    if msg.domain_scope:
        # only process messages for phones known to be associated with this domain
        if v is None or v.domain != msg.domain_scope:
            raise DomainScopeValidationError(
                'Attempted to simulate incoming sms from phone number not ' \
                'verified with this domain'
            )

    can_receive_sms = PhoneBlacklist.can_receive_sms(msg.phone_number)
    opt_in_keywords, opt_out_keywords = get_opt_keywords(msg)
    domain = v.domain if v else None

    if is_opt_message(msg.text, opt_out_keywords) and can_receive_sms:
        if PhoneBlacklist.opt_out_sms(msg.phone_number, domain=domain):
            metadata = MessageMetadata(ignore_opt_out=True)
            text = get_message(MSG_OPTED_OUT, v, context=(opt_in_keywords[0],))
            if v:
                send_sms_to_verified_number(v, text, metadata=metadata)
            else:
                send_sms(msg.domain, None, msg.phone_number, text, metadata=metadata)
    elif is_opt_message(msg.text, opt_in_keywords) and not can_receive_sms:
        if PhoneBlacklist.opt_in_sms(msg.phone_number, domain=domain):
            text = get_message(MSG_OPTED_IN, v, context=(opt_out_keywords[0],))
            if v:
                send_sms_to_verified_number(v, text)
            else:
                send_sms(msg.domain, None, msg.phone_number, text)
    elif v is not None and v.verified:
        if (
            domain_has_privilege(msg.domain, privileges.INBOUND_SMS) and
            is_contact_active(v.domain, v.owner_doc_type, v.owner_id)
        ):
            for h in settings.SMS_HANDLERS:
                try:
                    handler = to_function(h)
                except:
                    notify_exception(None, message=('error loading sms handler: %s' % h))
                    continue

                try:
                    was_handled = handler(v, msg.text, msg=msg)
                except Exception, e:
                    log_sms_exception(msg)
                    was_handled = False

                if was_handled:
                    break
    def test_by_domain(self):
        number1 = PhoneNumber.objects.create(
            domain=self.domain,
            owner_doc_type='X',
            owner_id='X',
            phone_number='999123',
            verified=True,
            pending_verification=False,
            is_two_way=True
        )

        number2 = PhoneNumber.objects.create(
            domain=self.domain,
            owner_doc_type='X',
            owner_id='X',
            phone_number='999124',
            verified=False,
            pending_verification=False,
            is_two_way=False
        )

        number3 = PhoneNumber.objects.create(
            domain=self.domain + 'X',
            owner_doc_type='X',
            owner_id='X',
            phone_number='999124',
            verified=True,
            pending_verification=False,
            is_two_way=True
        )
        self.addCleanup(number3.delete)

        self.assertEqual(
            set(PhoneNumber.by_domain(self.domain)),
            set([number1, number2])
        )

        self.assertEqual(
            set(PhoneNumber.by_domain(self.domain, ids_only=True)),
            set([number1.couch_id, number2.couch_id])
        )

        self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)
    def test_by_domain(self):
        number1 = PhoneNumber.objects.create(
            domain=self.domain,
            owner_doc_type='X',
            owner_id='X',
            phone_number='999123',
            verified=True,
            pending_verification=False,
            is_two_way=True
        )

        number2 = PhoneNumber.objects.create(
            domain=self.domain,
            owner_doc_type='X',
            owner_id='X',
            phone_number='999124',
            verified=False,
            pending_verification=False,
            is_two_way=False
        )

        number3 = PhoneNumber.objects.create(
            domain=self.domain + 'X',
            owner_doc_type='X',
            owner_id='X',
            phone_number='999124',
            verified=True,
            pending_verification=False,
            is_two_way=True
        )
        self.addCleanup(number3.delete)

        self.assertEqual(
            set(PhoneNumber.by_domain(self.domain)),
            set([number1, number2])
        )

        self.assertEqual(
            set(PhoneNumber.by_domain(self.domain, ids_only=True)),
            set([number1.couch_id, number2.couch_id])
        )

        self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)
Exemple #30
0
def get_inbound_phone_entry(phone_number, backend_id=None):
    if backend_id:
        backend = SQLMobileBackend.load(backend_id, is_couch_id=True)
        if toggles.INBOUND_SMS_LENIENCY.enabled(backend.domain):
            p = None
            if toggles.ONE_PHONE_NUMBER_MULTIPLE_CONTACTS.enabled(backend.domain):
                running_session_info = XFormsSessionSynchronization.get_running_session_info_for_channel(
                    SMSChannel(backend_id=backend_id, phone_number=phone_number)
                )
                contact_id = running_session_info.contact_id
                if contact_id:
                    p = PhoneNumber.get_phone_number_for_owner(contact_id, phone_number)
                if p is not None:
                    return (
                        p,
                        True
                    )
                elif running_session_info.session_id:
                    # This would be very unusual, as it would mean the supposedly running form session
                    # is linked to a phone number, contact pair that doesn't exist in the PhoneNumber table
                    notify_error(
                        "Contact from running session has no match in PhoneNumber table. "
                        "Only known way for this to happen is if you "
                        "unregister a phone number for a contact "
                        "while they are in an active session.",
                        details={
                            'running_session_info': running_session_info
                        }
                    )

            # NOTE: I don't think the backend could ever be global here since global backends
            # don't have a 'domain' and so the toggles would never be activated
            if not backend.is_global:
                p = PhoneNumber.get_two_way_number_with_domain_scope(phone_number, backend.domains_with_access)
                return (
                    p,
                    p is not None
                )

    return (
        PhoneNumber.get_reserved_number(phone_number),
        False
    )
    def test_android_only_registration_from_invite(self):
        self.domain_obj.sms_mobile_worker_registration_enabled = True
        self.domain_obj.enable_registration_welcome_sms_for_mobile_worker = True
        self.domain_obj.save()

        # Initiate Registration Workflow
        with patch('corehq.apps.sms.models.SelfRegistrationInvitation.odk_url') as mock_odk_url, \
                patch.object(SelfRegistrationInvitation, 'get_user_registration_url', return_value=DUMMY_REGISTRATION_URL), \
                patch.object(SelfRegistrationInvitation, 'get_app_info_url', return_value=DUMMY_APP_INFO_URL):
            mock_odk_url.__get__ = Mock(return_value=DUMMY_APP_ODK_URL)
            SelfRegistrationInvitation.initiate_workflow(
                self.domain,
                [SelfRegistrationUserInfo('999123')],
                app_id=self.app_id,
                android_only=True,
            )

        self.assertRegistrationInvitation(
            phone_number='999123',
            app_id=self.app_id,
            phone_type=SelfRegistrationInvitation.PHONE_TYPE_ANDROID,
            android_only=True,
            require_email=False,
            custom_user_data={},
            status=SelfRegistrationInvitation.STATUS_PENDING,
        )

        self.assertLastOutgoingSMS('+999123', [
            _MESSAGES[MSG_MOBILE_WORKER_ANDROID_INVITATION].format(
                DUMMY_REGISTRATION_URL),
            '[commcare app - do not delete] {}'.format(DUMMY_APP_INFO_URL_B64),
        ])

        invite = self._get_sms_registration_invitation()
        c = Client()
        response = c.post(
            '/a/{}/settings/users/commcare/register/{}/'.format(
                self.domain, invite.token), {
                    'username': '******',
                    'password': '******',
                    'password2': 'abc',
                    'email': '*****@*****.**',
                })
        self.assertEqual(response.status_code, 200)

        user = CommCareUser.get_by_username(
            format_username('new_user', self.domain))
        self.assertIsNotNone(user)
        self.assertEqual(user.user_data, self.default_user_data)
        self.assertEqual(user.email, '*****@*****.**')
        self.assertEqual(
            PhoneNumber.get_two_way_number('999123').owner_id, user.get_id)

        self.assertRegistrationInvitation(
            status=SelfRegistrationInvitation.STATUS_REGISTERED, )
Exemple #32
0
 def _fmt_status(self, number):
     if number.verified:
         return "Verified"
     elif number.pending_verification:
         return "Verification Pending"
     elif (
             not (number.is_two_way or number.pending_verification) and
             PhoneNumber.get_reserved_number(number.phone_number)
          ):
         return "Already In Use"
     return "Not Verified"
Exemple #33
0
def _sync_case_phone_number(contact_case):
    phone_info = contact_case.get_phone_info()

    lock_keys = ['sync-case-phone-number-for-%s' % contact_case.case_id]
    if phone_info.phone_number:
        lock_keys.append('verifying-phone-number-%s' % phone_info.phone_number)

    with CriticalSection(lock_keys, timeout=5 * 60):
        phone_number = contact_case.get_verified_number()
        if (
            phone_number and
            phone_number.contact_last_modified and
            phone_number.contact_last_modified >= contact_case.server_modified_on
        ):
            return
        if phone_info.requires_entry:
            try:
                contact_case.verify_unique_number(phone_info.phone_number)
            except (InvalidFormatException, PhoneNumberInUseException):
                if phone_number:
                    phone_number.delete()
                return

            if not phone_number:
                phone_number = PhoneNumber(
                    domain=contact_case.domain,
                    owner_doc_type=contact_case.doc_type,
                    owner_id=contact_case.case_id,
                )
            elif _phone_number_is_same(phone_number, phone_info):
                return

            phone_number.phone_number = phone_info.phone_number
            phone_number.backend_id = phone_info.sms_backend_id
            phone_number.ivr_backend_id = phone_info.ivr_backend_id
            phone_number.verified = True
            phone_number.contact_last_modified = contact_case.server_modified_on
            phone_number.save()
        else:
            if phone_number:
                phone_number.delete()
Exemple #34
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()
Exemple #35
0
    def test_other_registration_from_invite(self):
        self.domain_obj.sms_mobile_worker_registration_enabled = True
        self.domain_obj.enable_registration_welcome_sms_for_mobile_worker = True
        self.domain_obj.save()

        user_data = {'abc': 'def'}

        # Initiate Registration Workflow
        SelfRegistrationInvitation.initiate_workflow(
            self.domain,
            [SelfRegistrationUserInfo('999123', user_data)],
            app_id=self.app_id,
        )

        self.assertRegistrationInvitation(
            phone_number='999123',
            app_id=self.app_id,
            phone_type=None,
            android_only=False,
            require_email=False,
            custom_user_data=user_data,
            status=SelfRegistrationInvitation.STATUS_PENDING,
        )

        self.assertLastOutgoingSMS('+999123', [_MESSAGES[MSG_MOBILE_WORKER_INVITATION_START]])

        # Choose phone type 'other'
        incoming('+999123', '2', self.backend.hq_api_id)

        self.assertRegistrationInvitation(
            phone_number='999123',
            app_id=self.app_id,
            phone_type=SelfRegistrationInvitation.PHONE_TYPE_OTHER,
            android_only=False,
            require_email=False,
            custom_user_data=user_data,
            status=SelfRegistrationInvitation.STATUS_PENDING,
        )

        self.assertLastOutgoingSMS('+999123', [_MESSAGES[MSG_MOBILE_WORKER_JAVA_INVITATION].format(self.domain)])

        # Register over SMS
        incoming('+999123', 'JOIN {} WORKER test'.format(self.domain), self.backend.hq_api_id)
        user = CommCareUser.get_by_username(format_username('test', self.domain))
        self.assertIsNotNone(user)
        self.assertEqual(user.user_data, user_data)
        self.assertEqual(PhoneNumber.by_phone('999123').owner_id, user.get_id)

        self.assertLastOutgoingSMS('+999123', [_MESSAGES[MSG_REGISTRATION_WELCOME_MOBILE_WORKER]])

        self.assertRegistrationInvitation(
            status=SelfRegistrationInvitation.STATUS_REGISTERED,
        )
    def test_by_owner_id(self):
        number = PhoneNumber.objects.create(domain=self.domain,
                                            owner_doc_type='X',
                                            owner_id='owner1',
                                            phone_number='999123',
                                            verified=True)

        [lookup] = PhoneNumber.by_owner_id('owner1')
        self.assertEqual(lookup, number)

        # test cache clear
        number.owner_id = 'owner2'
        number.save()
        self.assertEqual(PhoneNumber.by_owner_id('owner1').count(), 0)
        [lookup] = PhoneNumber.by_owner_id('owner2')
        self.assertEqual(lookup, number)

        number.verified = False
        number.save()
        [lookup] = PhoneNumber.by_owner_id('owner2')
        self.assertFalse(lookup.verified)
    def test_invalid_phone_format(self):
        extra_number = PhoneNumber.objects.create(
            domain=self.domain,
            owner_doc_type='X',
            owner_id='X',
            phone_number='999123',
            verified=True,
            pending_verification=False,
            is_two_way=True
        )
        self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1)

        with create_test_case(self.domain, 'participant', 'test1', drop_signals=False) as case:
            case = self.set_case_property(case, 'contact_phone_number', '99987658765')
            case = self.set_case_property(case, 'contact_phone_number_is_verified', '1')
            self.assertIsNotNone(self.get_case_phone_number(case))
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            case = self.set_case_property(case, 'contact_phone_number', 'xyz')
            self.assertIsNone(self.get_case_phone_number(case))
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1)
 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()
    def test_case_soft_delete(self):
        extra_number = PhoneNumber.objects.create(
            domain=self.domain,
            owner_doc_type='X',
            owner_id='X',
            phone_number='999123',
            verified=True,
            pending_verification=False,
            is_two_way=True
        )
        self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1)

        with create_test_case(self.domain, 'participant', 'test1', drop_signals=False) as case:
            case = self.set_case_property(case, 'contact_phone_number', '99987658765')
            case = self.set_case_property(case, 'contact_phone_number_is_verified', '1')
            self.assertIsNotNone(self.get_case_phone_number(case))
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            tag_cases_as_deleted_and_remove_indices(self.domain, [case.case_id], '123', datetime.utcnow())
            self.assertIsNone(self.get_case_phone_number(case))
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1)
    def test_case_phone_number_updates(self):
        extra_number = PhoneNumber.objects.create(
            domain=self.domain,
            owner_doc_type='X',
            owner_id='X',
            phone_number='999123',
            verified=True,
            pending_verification=False,
            is_two_way=True
        )
        self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1)

        with create_test_case(self.domain, 'participant', 'test1', drop_signals=False) as case:
            self.assertIsNone(self.get_case_phone_number(case))

            case = self.set_case_property(case, 'contact_phone_number', '99987658765')
            self.assertPhoneNumberDetails(case, '99987658765', None, None, False, False, False)
            pk = self.get_case_phone_number(case).pk
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            case = self.set_case_property(case, 'contact_phone_number_is_verified', '1')
            self.assertPhoneNumberDetails(case, '99987658765', None, None, True, False, True, pk=pk)
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            case = self.set_case_property(case, 'contact_phone_number', '99987698769')
            self.assertPhoneNumberDetails(case, '99987698769', None, None, True, False, True)
            pk = self.get_case_phone_number(case).pk
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            case = self.set_case_property(case, 'contact_backend_id', 'sms-backend')
            self.assertPhoneNumberDetails(case, '99987698769', 'sms-backend', None, True, False, True, pk=pk)
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            case = self.set_case_property(case, 'contact_ivr_backend_id', 'ivr-backend')
            self.assertPhoneNumberDetails(case, '99987698769', 'sms-backend', 'ivr-backend', True, False, True,
                pk=pk)
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            # If phone entry is ahead of the case in terms of contact_last_modified, no update should happen
            v = self.get_case_phone_number(case)
            v.contact_last_modified += timedelta(days=1)
            v.save()

            with patch('corehq.apps.sms.models.PhoneNumber.save') as mock_save:
                case = self.set_case_property(case, 'contact_phone_number', '99912341234')
                self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)
                mock_save.assert_not_called()

        self.assertEqual(PhoneNumber.get_two_way_number('999123'), extra_number)
Exemple #41
0
    def verify_unique_number(self, phone_number):
        """
        Verifies that the given phone number is not already in use by any other contacts.

        return  void
        raises  InvalidFormatException if the phone number format is invalid
        raises  PhoneNumberInUseException if the phone number is already in use by another contact
        """
        from corehq.apps.sms.models import PhoneNumber
        self.validate_number_format(phone_number)
        v = PhoneNumber.by_phone(phone_number, include_pending=True)
        if v is not None and (v.owner_doc_type != self.doc_type or v.owner_id != self.get_id):
            raise PhoneNumberInUseException("Phone number is already in use.")
    def test_delete_phone_numbers_for_owners(self):
        with self.create_case_contact('9990001') as case1, \
                self.create_case_contact('9990002') as case2, \
                self.create_case_contact('9990003') as case3:

            self.assertEqual(len(PhoneNumber.by_owner_id(case1.case_id)), 1)
            self.assertEqual(len(PhoneNumber.by_owner_id(case2.case_id)), 1)
            self.assertEqual(len(PhoneNumber.by_owner_id(case3.case_id)), 1)
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 3)

            delete_phone_numbers_for_owners([case2.case_id, case3.case_id])
            self.assertEqual(len(PhoneNumber.by_owner_id(case1.case_id)), 1)
            self.assertEqual(len(PhoneNumber.by_owner_id(case2.case_id)), 0)
            self.assertEqual(len(PhoneNumber.by_owner_id(case3.case_id)), 0)
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1)
Exemple #43
0
    def test_delete_phone_numbers_for_owners(self):
        with self.create_case_contact('9990001') as case1, \
                self.create_case_contact('9990002') as case2, \
                self.create_case_contact('9990003') as case3:

            self.assertEqual(len(PhoneNumber.by_owner_id(case1.case_id)), 1)
            self.assertEqual(len(PhoneNumber.by_owner_id(case2.case_id)), 1)
            self.assertEqual(len(PhoneNumber.by_owner_id(case3.case_id)), 1)
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 3)

            delete_phone_numbers_for_owners([case2.case_id, case3.case_id])
            self.assertEqual(len(PhoneNumber.by_owner_id(case1.case_id)), 1)
            self.assertEqual(len(PhoneNumber.by_owner_id(case2.case_id)), 0)
            self.assertEqual(len(PhoneNumber.by_owner_id(case3.case_id)), 0)
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1)
Exemple #44
0
    def test_android_only_registration_from_invite(self):
        self.domain_obj.sms_mobile_worker_registration_enabled = True
        self.domain_obj.enable_registration_welcome_sms_for_mobile_worker = True
        self.domain_obj.save()

        # Initiate Registration Workflow
        with patch('corehq.apps.sms.models.SelfRegistrationInvitation.odk_url') as mock_odk_url, \
                patch.object(SelfRegistrationInvitation, 'get_user_registration_url', return_value=DUMMY_REGISTRATION_URL), \
                patch.object(SelfRegistrationInvitation, 'get_app_info_url', return_value=DUMMY_APP_INFO_URL):
            mock_odk_url.__get__ = Mock(return_value=DUMMY_APP_ODK_URL)
            SelfRegistrationInvitation.initiate_workflow(
                self.domain,
                [SelfRegistrationUserInfo('999123')],
                app_id=self.app_id,
                android_only=True,
            )

        self.assertRegistrationInvitation(
            phone_number='999123',
            app_id=self.app_id,
            phone_type=SelfRegistrationInvitation.PHONE_TYPE_ANDROID,
            android_only=True,
            require_email=False,
            custom_user_data={},
            status=SelfRegistrationInvitation.STATUS_PENDING,
        )

        self.assertLastOutgoingSMS('+999123', [
            _MESSAGES[MSG_MOBILE_WORKER_ANDROID_INVITATION].format(DUMMY_REGISTRATION_URL),
            '[commcare app - do not delete] {}'.format(DUMMY_APP_INFO_URL_B64),
        ])

        invite = self._get_sms_registration_invitation()
        c = Client()
        response = c.post('/a/{}/settings/users/commcare/register/{}/'.format(self.domain, invite.token), {
            'username': '******',
            'password': '******',
            'password2': 'abc',
            'email': '*****@*****.**',
        })
        self.assertEqual(response.status_code, 200)

        user = CommCareUser.get_by_username(format_username('new_user', self.domain))
        self.assertIsNotNone(user)
        self.assertEqual(user.user_data, {})
        self.assertEqual(user.email, '*****@*****.**')
        self.assertEqual(PhoneNumber.by_phone('999123').owner_id, user.get_id)

        self.assertRegistrationInvitation(
            status=SelfRegistrationInvitation.STATUS_REGISTERED,
        )
Exemple #45
0
    def verify_unique_number(self, phone_number):
        """
        Verifies that the given phone number is not already in use by any other contacts.

        return  void
        raises  InvalidFormatException if the phone number format is invalid
        raises  PhoneNumberInUseException if the phone number is already in use by another contact
        """
        from corehq.apps.sms.models import PhoneNumber
        self.validate_number_format(phone_number)
        v = PhoneNumber.by_phone(phone_number, include_pending=True)
        if v is not None and (v.owner_doc_type != self.doc_type
                              or v.owner_id != self.get_id):
            raise PhoneNumberInUseException("Phone number is already in use.")
    def test_invalid_phone_format(self):
        extra_number = PhoneNumber.objects.create(
            domain=self.domain,
            owner_doc_type='X',
            owner_id='X',
            phone_number='999123',
            verified=True,
            pending_verification=False,
            is_two_way=True
        )
        self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1)

        with create_test_case(self.domain, 'participant', 'test1', drop_signals=False) as case:
            case = self.set_case_property(case, 'contact_phone_number', '99987658765')
            case = self.set_case_property(case, 'contact_phone_number_is_verified', '1')
            self.assertIsNotNone(self.get_case_phone_number(case))
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            case = self.set_case_property(case, 'contact_phone_number', 'xyz')
            self.assertIsNone(self.get_case_phone_number(case))
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1)

        self.assertEqual(PhoneNumber.get_two_way_number('999123'), extra_number)
    def test_phone_number_already_in_use(self):
        self.assertEqual(PhoneNumber.count_by_domain(self.domain), 0)

        with create_test_case(self.domain, 'participant', 'test1', drop_signals=False) as case1, \
                create_test_case(self.domain, 'participant', 'test2', drop_signals=False) as case2:
            case1 = self.set_case_property(case1, 'contact_phone_number', '99987658765')
            case1 = self.set_case_property(case1, 'contact_phone_number_is_verified', '1')

            case2 = self.set_case_property(case2, 'contact_phone_number', '99987698769')
            case2 = self.set_case_property(case2, 'contact_phone_number_is_verified', '1')

            self.assertIsNotNone(self.get_case_phone_number(case1))
            self.assertIsNotNone(self.get_case_phone_number(case2))
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            case2 = self.set_case_property(case2, 'contact_phone_number', '99987658765')

            self.assertIsNotNone(self.get_case_phone_number(case1))
            self.assertIsNotNone(self.get_case_phone_number(case2))
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            self.assertPhoneNumberDetails(case1, '99987658765', None, None, True, False, True)
            self.assertPhoneNumberDetails(case2, '99987658765', None, None, False, False, False)
    def test_phone_number_already_in_use(self):
        self.assertEqual(PhoneNumber.count_by_domain(self.domain), 0)

        with create_test_case(self.domain, 'participant', 'test1', drop_signals=False) as case1, \
                create_test_case(self.domain, 'participant', 'test2', drop_signals=False) as case2:
            case1 = self.set_case_property(case1, 'contact_phone_number', '99987658765')
            case1 = self.set_case_property(case1, 'contact_phone_number_is_verified', '1')

            case2 = self.set_case_property(case2, 'contact_phone_number', '99987698769')
            case2 = self.set_case_property(case2, 'contact_phone_number_is_verified', '1')

            self.assertIsNotNone(self.get_case_phone_number(case1))
            self.assertIsNotNone(self.get_case_phone_number(case2))
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            case2 = self.set_case_property(case2, 'contact_phone_number', '99987658765')

            self.assertIsNotNone(self.get_case_phone_number(case1))
            self.assertIsNotNone(self.get_case_phone_number(case2))
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            self.assertPhoneNumberDetails(case1, '99987658765', None, None, True, False, True)
            self.assertPhoneNumberDetails(case2, '99987658765', None, None, False, False, False)
Exemple #49
0
def handle_due_survey_action(domain, contact_id, session_id):
    with critical_section_for_smsforms_sessions(contact_id):
        session = SQLXFormsSession.by_session_id(session_id)
        if (not session or not session.session_is_open
                or session.current_action_due > utcnow()):
            return

        if toggles.ONE_PHONE_NUMBER_MULTIPLE_CONTACTS.enabled(domain):
            if not XFormsSessionSynchronization.claim_channel_for_session(
                    session):
                from .management.commands import handle_survey_actions
                # Unless we release this lock, handle_survey_actions will be unable to requeue this task
                # for the default duration of 1h, which we don't want
                handle_survey_actions.Command.get_enqueue_lock(
                    session_id, session.current_action_due).release()
                return

        if session_is_stale(session):
            # If a session is having some unrecoverable errors that aren't benefitting from
            # being retried, those errors should show up in sentry log and the fix should
            # be dealt with. In terms of the current session itself, we just close it out
            # to allow new sessions to start.
            session.mark_completed(False)
            session.save()
            return

        if session.current_action_is_a_reminder:
            # Resend the current question in the open survey to the contact
            p = PhoneNumber.get_phone_number_for_owner(session.connection_id,
                                                       session.phone_number)
            if p:
                metadata = MessageMetadata(
                    workflow=session.workflow,
                    xforms_session_couch_id=session._id,
                )
                resp = FormplayerInterface(session.session_id,
                                           domain).current_question()
                send_sms_to_verified_number(
                    p,
                    resp.event.text_prompt,
                    metadata,
                    logged_subevent=session.related_subevent)

            session.move_to_next_action()
            session.save()
        else:
            # Close the session
            session.close()
            session.save()
    def test_backend(self):
        backend1 = SQLTestSMSBackend.objects.create(
            hq_api_id=SQLTestSMSBackend.get_api_id(),
            is_global=False,
            domain=self.domain,
            name='BACKEND1')

        backend2 = SQLTestSMSBackend.objects.create(
            hq_api_id=SQLTestSMSBackend.get_api_id(),
            is_global=False,
            domain=self.domain,
            name='BACKEND2')

        SQLMobileBackendMapping.set_default_domain_backend(
            self.domain, backend1)

        number = PhoneNumber(domain=self.domain, phone_number='+999123')
        self.assertEqual(number.backend, backend1)

        number.backend_id = backend2.name
        self.assertEqual(number.backend, backend2)

        number.backend_id = '  '
        self.assertEqual(number.backend, backend1)
    def testGetPhoneEntries(self):
        number1 = self.mobile_worker1.get_or_create_phone_entry('999123')
        number2 = self.mobile_worker1.get_or_create_phone_entry('999124')
        self.mobile_worker1.get_or_create_phone_entry('999125')
        number4 = self.mobile_worker2.get_or_create_phone_entry('999126')

        number1.set_two_way()
        number2.set_pending_verification()
        number4.set_two_way()

        self.assertEqual(PhoneNumber.by_domain(self.domain).count(), 4)
        entries = self.mobile_worker1.get_phone_entries()
        self.assertEqual(set(entries.keys()), set(['999123', '999124', '999125']))

        entries = self.mobile_worker1.get_two_way_numbers()
        self.assertEqual(set(entries.keys()), set(['999123']))
    def test_filter_pending(self):
        v1 = PhoneNumber(verified=True)
        v2 = PhoneNumber(verified=False)

        self.assertIsNone(
            PhoneNumber._filter_pending(None, include_pending=True))
        self.assertIsNone(
            PhoneNumber._filter_pending(None, include_pending=False))

        self.assertEqual(
            v1, PhoneNumber._filter_pending(v1, include_pending=False))
        self.assertIsNone(
            PhoneNumber._filter_pending(v2, include_pending=False))

        self.assertEqual(v1,
                         PhoneNumber._filter_pending(v1, include_pending=True))
        self.assertEqual(v2,
                         PhoneNumber._filter_pending(v2, include_pending=True))
Exemple #53
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()
    def test_case_phone_number_updates(self):
        extra_number = PhoneNumber.objects.create(
            domain=self.domain,
            owner_doc_type='X',
            owner_id='X',
            phone_number='999123',
            verified=True,
            pending_verification=False,
            is_two_way=True
        )
        self.assertEqual(PhoneNumber.count_by_domain(self.domain), 1)

        with create_test_case(self.domain, 'participant', 'test1', drop_signals=False) as case:
            self.assertIsNone(self.get_case_phone_number(case))

            case = self.set_case_property(case, 'contact_phone_number', '99987658765')
            self.assertPhoneNumberDetails(case, '99987658765', None, None, False, False, False)
            pk = self.get_case_phone_number(case).pk
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            case = self.set_case_property(case, 'contact_phone_number_is_verified', '1')
            self.assertPhoneNumberDetails(case, '99987658765', None, None, True, False, True, pk=pk)
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            case = self.set_case_property(case, 'contact_phone_number', '99987698769')
            self.assertPhoneNumberDetails(case, '99987698769', None, None, True, False, True)
            pk = self.get_case_phone_number(case).pk
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            case = self.set_case_property(case, 'contact_backend_id', 'sms-backend')
            self.assertPhoneNumberDetails(case, '99987698769', 'sms-backend', None, True, False, True, pk=pk)
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            case = self.set_case_property(case, 'contact_ivr_backend_id', 'ivr-backend')
            self.assertPhoneNumberDetails(case, '99987698769', 'sms-backend', 'ivr-backend', True, False, True,
                pk=pk)
            self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)

            # If phone entry is ahead of the case in terms of contact_last_modified, no update should happen
            v = self.get_case_phone_number(case)
            v.contact_last_modified += timedelta(days=1)
            v.save()

            with patch('corehq.apps.sms.models.PhoneNumber.save') as mock_save:
                case = self.set_case_property(case, 'contact_phone_number', '99912341234')
                self.assertEqual(PhoneNumber.count_by_domain(self.domain), 2)
                mock_save.assert_not_called()
    def test_extensive_search(self):
        number = PhoneNumber.objects.create(domain=self.domain,
                                            owner_doc_type='X',
                                            owner_id='X',
                                            phone_number='999123',
                                            verified=True)

        self.assertEqual(PhoneNumber.by_extensive_search('999123'), number)
        self.assertEqual(PhoneNumber.by_extensive_search('0999123'), number)
        self.assertEqual(PhoneNumber.by_extensive_search('00999123'), number)
        self.assertEqual(PhoneNumber.by_extensive_search('000999123'), number)
        self.assertEqual(PhoneNumber.by_extensive_search('123'), number)
        self.assertIsNone(PhoneNumber.by_extensive_search('999124'), number)
Exemple #56
0
    def save_verified_number(self,
                             domain,
                             phone_number,
                             verified,
                             backend_id=None,
                             ivr_backend_id=None,
                             only_one_number_allowed=False):
        """
        Saves the given phone number as this contact's verified phone number.

        backend_id - the name of an SMSBackend to use when sending SMS to
            this number; if specified, this will override any project or
            global settings for which backend will be used to send sms to
            this number

        return  The PhoneNumber
        raises  InvalidFormatException if the phone number format is invalid
        raises  PhoneNumberInUseException if the phone number is already in use by another contact
        """
        from corehq.apps.sms.models import PhoneNumber

        phone_number = apply_leniency(phone_number)
        self.verify_unique_number(phone_number)
        if only_one_number_allowed:
            v = self.get_verified_number()
        else:
            v = self.get_verified_number(phone_number)
        if v is None:
            v = PhoneNumber(owner_doc_type=self.doc_type, owner_id=self.get_id)
        v.domain = domain
        v.phone_number = phone_number
        v.verified = verified
        v.backend_id = backend_id
        v.ivr_backend_id = ivr_backend_id
        v.save()
        return v
Exemple #57
0
def handle_due_survey_action(domain, contact_id, session_id):
    with critical_section_for_smsforms_sessions(contact_id):
        session = SQLXFormsSession.by_session_id(session_id)
        if (
            not session
            or not session.session_is_open
            or session.current_action_due > utcnow()
        ):
            return

        if session_is_stale(session):
            # If a session is having some unrecoverable errors that aren't benefitting from
            # being retried, those errors should show up in sentry log and the fix should
            # be dealt with. In terms of the current session itself, we just close it out
            # to allow new sessions to start.
            session.mark_completed(False)
            session.save()
            return

        if session.current_action_is_a_reminder:
            # Resend the current question in the open survey to the contact
            p = PhoneNumber.get_phone_number_for_owner(session.connection_id, session.phone_number)
            if p:
                metadata = MessageMetadata(
                    workflow=session.workflow,
                    xforms_session_couch_id=session._id,
                )
                resp = current_question(session.session_id, domain)
                send_sms_to_verified_number(
                    p,
                    resp.event.text_prompt,
                    metadata,
                    logged_subevent=session.related_subevent
                )

            session.move_to_next_action()
            session.save()
        else:
            # Close the session
            session.close()
            session.save()
Exemple #58
0
def initiate_sms_verification_workflow(contact, phone_number):
    # For now this is only applicable to mobile workers
    assert isinstance(contact, CommCareUser)

    phone_number = apply_leniency(phone_number)

    logged_event = MessagingEvent.get_current_verification_event(
        contact.domain, contact.get_id, phone_number)

    p = PhoneNumber.get_reserved_number(phone_number)
    if p:
        if p.owner_id != contact.get_id:
            return VERIFICATION__ALREADY_IN_USE
        if p.verified:
            return VERIFICATION__ALREADY_VERIFIED
        else:
            result = VERIFICATION__RESENT_PENDING
    else:
        entry = contact.get_or_create_phone_entry(phone_number)
        try:
            entry.set_pending_verification()
        except PhoneNumberInUseException:
            # On the off chance that the phone number was reserved between
            # the check above and now
            return VERIFICATION__ALREADY_IN_USE

        result = VERIFICATION__WORKFLOW_STARTED
        # Always create a new event when the workflow starts
        if logged_event:
            logged_event.status = MessagingEvent.STATUS_NOT_COMPLETED
            logged_event.save()
        logged_event = MessagingEvent.create_verification_event(
            contact.domain, contact)

    if not logged_event:
        logged_event = MessagingEvent.create_verification_event(
            contact.domain, contact)

    send_verification(contact.domain, contact, phone_number, logged_event)
    return result
Exemple #59
0
def get_channel_for_contact(contact_id, phone_number):
    """
    For a given contact_id, phone_number pair, look up the gateway to be used and return the result as a Channel

    If a PhoneNumber object does not exist for the pair,
    or attempting to determine the gateway backend results in a BadSMSConfigException
    a channel will be returned with backend_id=None
    """
    backend_id = None
    phone_number_record = PhoneNumber.get_phone_number_for_owner(contact_id, phone_number)
    if phone_number_record:
        try:
            backend = phone_number_record.backend
        except BadSMSConfigException:
            backend = None
        if backend:
            backend_id = backend.couch_id

    return SMSChannel(
        backend_id=backend_id,
        phone_number=phone_number,
    )
Exemple #60
0
    def test_sending_to_opted_out_number(self):
        self.assertEqual(PhoneBlacklist.objects.count(), 0)

        incoming('99912345678', 'join opt-test', 'GVI')
        v = PhoneNumber.get_two_way_number('99912345678')
        self.assertIsNotNone(v)

        send_sms_to_verified_number(v, 'hello')
        sms = self.get_last_sms('+99912345678')
        self.assertEqual(sms.direction, 'O')
        self.assertEqual(sms.text, 'hello')

        incoming('99912345678', 'stop', 'GVI')
        self.assertEqual(PhoneBlacklist.objects.count(), 1)
        phone_number = PhoneBlacklist.objects.get(phone_number='99912345678')
        self.assertFalse(phone_number.send_sms)

        send_sms_to_verified_number(v, 'hello')
        sms = self.get_last_sms('+99912345678')
        self.assertEqual(sms.direction, 'O')
        self.assertEqual(sms.text, 'hello')
        self.assertTrue(sms.error)
        self.assertEqual(sms.system_error_message,
                         SMS.ERROR_PHONE_NUMBER_OPTED_OUT)

        incoming('99912345678', 'start', 'GVI')
        self.assertEqual(PhoneBlacklist.objects.count(), 1)
        phone_number = PhoneBlacklist.objects.get(phone_number='99912345678')
        self.assertTrue(phone_number.send_sms)

        send_sms_to_verified_number(v, 'hello')
        sms = self.get_last_sms('+99912345678')
        self.assertEqual(sms.direction, 'O')
        self.assertEqual(sms.text, 'hello')
        self.assertFalse(sms.error)
        self.assertIsNone(sms.system_error_message)