Beispiel #1
0
def incoming(phone_number, backend_api):
    cleaned_number = phone_number
    if len(cleaned_number) > 0 and cleaned_number[0] == "+":
        cleaned_number = cleaned_number[1:]
    
    # Try to look up the verified number entry
    v = VerifiedNumber.view("sms/verified_number_by_number",
        key=cleaned_number,
        include_docs=True
    ).one()
    
    # If none was found, try to match only the last digits of numbers in the database
    if v is None:
        v = VerifiedNumber.view("sms/verified_number_by_suffix",
            key=cleaned_number,
            include_docs=True
        ).one()
    
    # Save the call entry
    msg = CallLog(
        phone_number    = cleaned_number,
        direction       = INCOMING,
        date            = datetime.utcnow(),
        backend_api     = backend_api
    )
    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()
    def handle(self, *args, **options):
        # This is ok, there's only like 50k of these and we're only querying ids
        vns = VerifiedNumber.view(
            'phone_numbers/verified_number_by_owner_id',
            include_docs=False
        ).all()

        # Convert to a dict of {owner id: count of total numbers}
        owners = {}
        for vn in vns:
            owner_id = vn['key']
            if owner_id in owners:
                owners[owner_id] += 1
            else:
                owners[owner_id] = 1

        # Convert to a list of owner ids that have more than one VerifiedNumber
        # (excluding pending numbers)
        mult_list = []
        for owner_id, count in owners.iteritems():
            if count > 1:
                owner_vns = VerifiedNumber.view(
                    'phone_numbers/verified_number_by_owner_id',
                    key=owner_id,
                    include_docs=True
                ).all()
                owner_vns = [vn for vn in owner_vns if vn.verified]
                if len(owner_vns) > 1:
                    mult_list.append(owner_id)

        # If the old methodology's preferred number doesn't match the
        # new one, report it here. Only fix it if options['fix'] is True
        for owner_id in mult_list:
            user = CouchUser.get_by_user_id(owner_id)
            if not user:
                print 'ERROR: User not found: %s' % owner_id
                continue
            if not self.phones_are_strings(user):
                print 'ERROR: Phone numbers should be strings: %s' % owner_id
                continue
            preferred_old_vn = get_verified_number_for_recipient_old(user)
            preferred_new_vn = get_verified_number_for_recipient(user)

            if preferred_old_vn._id != preferred_new_vn._id:
                print "Need to change %s %s from %s to %s" % (
                    user.domain,
                    owner_id,
                    preferred_new_vn.phone_number,
                    preferred_old_vn.phone_number,
                )
                if preferred_old_vn.phone_number not in user.phone_numbers:
                    print 'ERROR: Phone numbers are out of sync: %s' % owner_id
                    continue
                if options.get('fix', False):
                    print "  fixing..."
                    user.set_default_phone_number(preferred_old_vn.phone_number)
    def assertMatch(self, match, phone_search, suffix_search, owner_id_search):
        lookedup = VerifiedNumber.by_phone(phone_search)
        self.assertEqual(match._id, lookedup._id)
        self.assertEqual(match._rev, lookedup._rev)

        lookedup = VerifiedNumber.by_suffix(suffix_search)
        self.assertEqual(match._id, lookedup._id)
        self.assertEqual(match._rev, lookedup._rev)

        [lookedup] = VerifiedNumber.by_owner_id(owner_id_search)
        self.assertEqual(match._id, lookedup._id)
        self.assertEqual(match._rev, lookedup._rev)
Beispiel #4
0
    def handle(self, *args, **options):
        if len(args) == 0:
            raise CommandError(
                'Usage: python manage.py set_backend_ids domain [backend_id] [--test]'
            )

        domain = args[0]
        if len(args) > 1:
            backend_id = args[1]
        else:
            backend_id = None

        test_only = options['test']

        for vn in VerifiedNumber.by_domain(domain):
            if (not vn.backend_id) and (not backend_id):
                pass
            elif vn.backend_id == backend_id:
                pass
            elif test_only:
                print '%s %s, number %s has backend %s instead of %s' % \
                    (vn.owner_doc_type, vn.owner_id, vn.phone_number,
                     'None' if vn.backend_id is None else "'%s'" % vn.backend_id, backend_id)
            else:
                if vn.owner_doc_type == "CommCareCase":
                    print 'Cannot update backend_id for %s because it is a case' % vn.owner_id
                else:
                    print 'Updating backend_id from %s to %s for %s %s, number %s' % \
                        (vn.backend_id, backend_id, vn.owner_doc_type, vn.owner_id, vn.phone_number)
                    vn.backend_id = backend_id
                    vn.save()
Beispiel #5
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 = VerifiedNumber.by_phone(phone_number, include_pending=True)
        if vn:
            if vn.owner_id != contact._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
Beispiel #6
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
Beispiel #7
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")
    def handle(self, *args, **options):
        if len(args) == 0:
            raise CommandError('Usage: python manage.py set_backend_ids domain [backend_id] [--test]')

        domain = args[0]
        if len(args) > 1:
            backend_id = args[1]
        else:
            backend_id = None

        test_only = options['test']

        for vn in VerifiedNumber.by_domain(domain):
            if (not vn.backend_id) and (not backend_id):
                pass
            elif vn.backend_id == backend_id:
                pass
            elif test_only:
                print '%s %s, number %s has backend %s instead of %s' % \
                    (vn.owner_doc_type, vn.owner_id, vn.phone_number,
                     'None' if vn.backend_id is None else "'%s'" % vn.backend_id, backend_id)
            else:
                if vn.owner_doc_type == "CommCareCase":
                    print 'Cannot update backend_id for %s because it is a case' % vn.owner_id
                else:
                    print 'Updating backend_id from %s to %s for %s %s, number %s' % \
                        (vn.backend_id, backend_id, vn.owner_doc_type, vn.owner_id, vn.phone_number)
                    vn.backend_id = backend_id
                    vn.save()
Beispiel #9
0
def process_verification(phone_number, msg, backend_id=None):
    v = VerifiedNumber.by_phone(phone_number, True)
    if not v:
        return

    if not verification_response_ok(msg.text):
        return

    msg.domain = v.domain
    msg.couch_recipient_doc_type = v.owner_doc_type
    msg.couch_recipient = v.owner_id
    msg.save()

    if not domain_has_privilege(msg.domain, privileges.INBOUND_SMS):
        return

    if backend_id:
        backend = MobileBackend.load(backend_id)
    else:
        backend = MobileBackend.auto_load(phone_number, v.domain)

    # i don't know how to dynamically instantiate this object, which may be any number of doc types...
    #owner = CommCareMobileContactMixin.get(v.owner_id)
    assert v.owner_doc_type == 'CommCareUser'
    owner = CommCareUser.get(v.owner_id)

    v = owner.save_verified_number(v.domain, phone_number, True, backend.name)
    with localize(owner.language):
        send_sms_to_verified_number(v, _(CONFIRM))
Beispiel #10
0
    def post(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)

        reminder = request.POST.get('reminder')
        phone_number = context.get('phone_number')

        if reminder and phone_number:
            phone_number = clean_phone_number(phone_number)
            v = VerifiedNumber.by_phone(phone_number, include_pending=True)
            if v and v.verified:
                user = v.owner
                if reminder == 'first_soh':
                    first_soh_process_user(user, test=True)
                elif reminder == 'second_soh':
                    now = datetime.datetime.utcnow()
                    date = now - datetime.timedelta(days=5)
                    second_soh_process_user(user, date, test=True)
                elif reminder == 'third_soh':
                    third_soh_process_users_and_facilities([user], [user.location.sql_location], test=True)
                elif reminder == 'stockout':
                    stockout_process_user(user, test=True)
                elif reminder == 'rrirv':
                    rrirv_process_user(user, test=True)
                elif reminder == 'visit_website':
                    visit_website_process_user(user, test=True)
        messages.success(request, "Reminder was sent successfully")
        return self.get(request, *args, **kwargs)
Beispiel #11
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")
 def get_couch_ids(self):
     result = VerifiedNumber.view(
         'phone_numbers/verified_number_by_domain',
         include_docs=False,
         reduce=False,
     ).all()
     return [row['id'] for row in result]
Beispiel #13
0
def process_verification(phone_number, msg, backend_id=None):
    v = VerifiedNumber.by_phone(phone_number, True)
    if not v:
        return

    if not verification_response_ok(msg.text):
        return

    msg.domain = v.domain
    msg.couch_recipient_doc_type = v.owner_doc_type
    msg.couch_recipient = v.owner_id
    msg.save()

    if backend_id:
        backend = MobileBackend.load(backend_id)
    else:
        backend = MobileBackend.auto_load(phone_number, v.domain)

    # i don't know how to dynamically instantiate this object, which may be any number of doc types...
    #owner = CommCareMobileContactMixin.get(v.owner_id)
    assert v.owner_doc_type == 'CommCareUser'
    owner = CommCareUser.get(v.owner_id)

    v = owner.save_verified_number(v.domain, phone_number, True, backend.name)
    with localize(owner.language):
        send_sms_to_verified_number(v, _(CONFIRM))
Beispiel #14
0
def chat_contacts(request, domain):
    domain_obj = Domain.get_by_name(domain, strict=True)
    verified_numbers = VerifiedNumber.by_domain(domain)
    contacts = []
    for vn in verified_numbers:
        owner = vn.owner
        if owner is not None and owner.doc_type in ('CommCareCase','CommCareUser'):
            if owner.doc_type == "CommCareUser":
                url = reverse(EditCommCareUserView.urlname, args=[domain, owner._id])
                name = owner.raw_username
            else:
                url = reverse("case_details", args=[domain, owner._id])
                if domain_obj.custom_case_username:
                    name = owner.get_case_property(domain_obj.custom_case_username) or _("(unknown)")
                else:
                    name = owner.name
            contacts.append({
                "id" : owner._id,
                "doc_type" : owner.doc_type,
                "url" : url,
                "name" : name,
            })
    context = {
        "domain" : domain,
        "contacts" : contacts,
    }
    return render(request, "sms/chat_contacts.html", context)
Beispiel #15
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
        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'
            )
    create_billable_for_sms(msg, msg.backend_api, delay=delay)

    if v is not None and v.verified:
        for h in settings.SMS_HANDLERS:
            try:
                handler = to_function(h)
            except:
                logging.exception('error loading sms handler: %s' % h)
                continue

            try:
                was_handled = handler(v, msg.text, msg=msg)
            except Exception, e:
                logging.exception('unhandled error in sms handler %s for message [%s]: %s' % (h, msg._id, e))
                was_handled = False

            if was_handled:
                break
Beispiel #16
0
 def tearDownClass(cls):
     cls.user1.delete()
     cls.national_user.delete()
     cls.regional_user.delete()
     for vn in VerifiedNumber.by_domain(TEST_DOMAIN):
         vn.delete()
     super(TestAlerts, cls).tearDownClass()
Beispiel #17
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 = VerifiedNumber.by_phone(phone_number, include_pending=True)
        if vn:
            if vn.owner_id != contact._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
Beispiel #18
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
        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'
            )

    if v is not None and v.verified:
        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
Beispiel #19
0
 def get_context_data(self, **kwargs):
     context = super(BaseRemindersTester, self).get_context_data(**kwargs)
     context['phone_number'] = kwargs.get('phone_number')
     verified_number = VerifiedNumber.by_phone(context['phone_number'])
     context['phone_user'] = CommCareUser.get(
         verified_number.owner_id) if verified_number else None
     return context
Beispiel #20
0
 def get_couch_ids(self):
     result = VerifiedNumber.view(
         'phone_numbers/verified_number_by_domain',
         include_docs=False,
         reduce=False,
     ).all()
     return [row['id'] for row in result]
Beispiel #21
0
    def validate_sms_users(self):
        for sms_user in iterate_over_api_objects(self.endpoint.get_smsusers):
            description = ""
            user = CommCareUser.get_by_username(self.get_username(sms_user)[0])
            if not user:
                description = "Not exists"
                EWSMigrationProblem.objects.create(
                    domain=self.domain,
                    external_id=sms_user.id,
                    object_type='smsuser',
                    description=description
                )
                continue

            phone_numbers = {
                apply_leniency(connection.phone_number) for connection in sms_user.phone_numbers
            }

            if phone_numbers - set(user.phone_numbers):
                description += "Invalid phone numbers, "

            phone_to_backend = {
                connection.phone_number: connection.backend
                for connection in sms_user.phone_numbers
            }

            default_phone_number = [
                connection.phone_number for connection in sms_user.phone_numbers if connection.default
            ]

            default_phone_number = default_phone_number[0] if default_phone_number else None

            if default_phone_number and (apply_leniency(default_phone_number) != user.default_phone_number):
                description += "Invalid default phone number, "

            for phone_number in user.phone_numbers:
                vn = VerifiedNumber.by_phone(phone_number)
                if not vn or vn.owner_id != user.get_id:
                    description += "Phone number not verified, "
                else:
                    backend = phone_to_backend.get(phone_number)
                    if backend == 'message_tester' and vn.backend_id != 'MOBILE_BACKEND_TEST' \
                            or (backend != 'message_tester' and vn.backend_id):
                        description += "Invalid backend, "

            if description:
                migration_problem, _ = EWSMigrationProblem.objects.get_or_create(
                    domain=self.domain,
                    object_id=user.get_id,
                    object_type='smsuser'
                )
                migration_problem.external_id = sms_user.id
                migration_problem.description = description.rstrip(' ,')
                migration_problem.save()
            else:
                EWSMigrationProblem.objects.filter(
                    domain=self.domain,
                    external_id=sms_user.id,
                    object_type='smsuser'
                ).delete()
Beispiel #22
0
def edit_contact(request, domain, sample_id, case_id):
    case = CommCareCase.get(case_id)
    if case.domain != domain:
        raise Http404
    if request.method == "POST":
        form = EditContactForm(request.POST)
        if form.is_valid():
            phone_number = form.cleaned_data.get("phone_number")
            vn = VerifiedNumber.view('sms/verified_number_by_number',
                                        key=phone_number,
                                        include_docs=True,
                                    ).one()
            if vn is not None and vn.owner_id != case_id:
                form._errors["phone_number"] = form.error_class(["Phone number is already in use."])
            else:
                update_contact(domain, case_id, request.couch_user.get_id, contact_phone_number=phone_number)
                return HttpResponseRedirect(reverse("edit_sample", args=[domain, sample_id]))
    else:
        initial = {
            "phone_number" : case.get_case_property("contact_phone_number"),
        }
        form = EditContactForm(initial=initial)
    
    context = {
        "domain" : domain,
        "case" : case,
        "form" : form,
    }
    return render(request, "reminders/partial/edit_contact.html", context)
Beispiel #23
0
def ivr_in(request):
    """
    Handles tropo call requests
    """
    if request.method == "POST":
        data = json.loads(request.raw_post_data)
        phone_number = data["session"]["from"]["id"]
        ####
        
        # TODO: Implement tropo as an ivr backend. In the meantime, just log the call.
        
        cleaned_number = phone_number
        if cleaned_number is not None and len(cleaned_number) > 0 and cleaned_number[0] == "+":
            cleaned_number = cleaned_number[1:]
        
        # Try to look up the verified number entry
        v = VerifiedNumber.view("sms/verified_number_by_number",
            key=cleaned_number,
            include_docs=True
        ).one()
        
        # If none was found, try to match only the last digits of numbers in the database
        if v is None:
            v = VerifiedNumber.view("sms/verified_number_by_suffix",
                key=cleaned_number,
                include_docs=True
            ).one()
        
        # 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")
Beispiel #24
0
 def tearDownClass(cls):
     cls.user1.delete()
     cls.national_user.delete()
     cls.regional_user.delete()
     cls.backend.delete()
     cls.sms_backend_mapping.delete()
     for vn in VerifiedNumber.by_domain(TEST_DOMAIN):
         vn.delete()
Beispiel #25
0
def delete_phone_numbers_for_owners(owner_ids):
    for ids in chunked(owner_ids, 50):
        results = VerifiedNumber.get_db().view(
            'sms/verified_number_by_owner_id',
            keys=ids,
            include_docs=True
        )
        soft_delete_docs([row['doc'] for row in results], VerifiedNumber)
    def tearDownClass(cls):
        cls.sms_backend_mapping.delete()
        cls.sms_backend.delete()
        for user in WebUser.by_domain(TEST_DOMAIN):
            user.delete()

        for vn in VerifiedNumber.by_domain(TEST_DOMAIN):
            vn.delete()
Beispiel #27
0
def incoming(phone_number, text, backend_api, timestamp=None, domain_scope=None, delay=True):
    """
    entry point for incoming sms

    phone_number - originating phone number
    text - message content
    backend_api - backend ID of receiving sms backend
    timestamp - message received timestamp; defaults to now (UTC)
    domain_scope - if present, only messages from phone numbers that can be
      definitively linked to this domain will be processed; others will be
      dropped (useful to provide security when simulating incoming sms)
    """
    phone_number = clean_phone_number(phone_number)
    v = VerifiedNumber.by_phone(phone_number, include_pending=True)
    if domain_scope:
        # only process messages for phones known to be associated with this domain
        if v is None or v.domain != domain_scope:
            raise RuntimeError("attempted to simulate incoming sms from phone number not verified with this domain")

    # Log message in message log
    msg = SMSLog(
        phone_number=phone_number,
        direction=INCOMING,
        date=timestamp or datetime.utcnow(),
        text=text,
        backend_api=backend_api,
    )
    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.save()

    create_billable_for_sms(msg, backend_api, delay=delay)

    if v is not None and v.verified:
        for h in settings.SMS_HANDLERS:
            try:
                handler = to_function(h)
            except:
                logging.exception("error loading sms handler: %s" % h)
                continue

            try:
                was_handled = handler(v, text)
            except:
                logging.exception("unhandled error in sms handler %s for message [%s]" % (h, text))
                was_handled = False

            if was_handled:
                break
    else:
        if not process_sms_registration(msg):
            import verify

            verify.process_verification(phone_number, text)

    return msg
Beispiel #28
0
    def handle(self):
        words = self.args
        if len(words) < 2 or len(words) > 3:
            self.respond(REGISTER_HELP)
            return

        name = words[0]
        code = words[1]
        params = {
            "msd_code": code
        }
        if not self.user:
            domains = [config.domain for config in ILSGatewayConfig.get_all_configs()]
            for domain in domains:
                loc = self._get_facility_location(domain, code)
                if not loc:
                    continue

                splited_name = name.split(' ', 1)
                first_name = splited_name[0]
                last_name = splited_name[1] if len(splited_name) > 1 else ""
                clean_name = name.replace(' ', '.')
                username = "******" % (clean_name, domain)
                password = User.objects.make_random_password()
                user = CommCareUser.create(domain=domain, username=username, password=password,
                                           commit=False)
                user.first_name = first_name
                user.last_name = last_name

                if len(words) == 3:
                    user.user_data = {
                        'role': words[2]
                    }

                try:
                    user.set_default_phone_number(self.msg.phone_number.replace('', ''))
                    user.save_verified_number(domain, self.msg.phone_number.replace('', ''), True, self.msg.backend_api)
                except PhoneNumberInUseException as e:
                    v = VerifiedNumber.by_phone(self.msg.phone_number, include_pending=True)
                    v.delete()
                    user.save_verified_number(domain, self.msg.phone_number.replace('', ''), True, self.msg.backend_api)
                except CommCareUser.Inconsistent:
                    continue

                user.language = Languages.DEFAULT

                params.update({
                    'sdp_name': loc.name,
                    'contact_name': name
                })

                dm = user.get_domain_membership(domain)
                dm.location_id = loc._id
                user.save()
                add_location(user, loc._id)

        self.respond(REGISTRATION_CONFIRM, **params)
    def tearDown(self):
        for location in Location.by_domain(self.TEST_DOMAIN):
            location.delete()

        for user in WebUser.by_domain(self.TEST_DOMAIN):
            user.delete()

        for vn in VerifiedNumber.by_domain(self.TEST_DOMAIN):
            vn.delete()
Beispiel #30
0
 def get_soft_deleted_couch_ids(self):
     result = VerifiedNumber.view(
         'all_docs/by_doc_type',
         startkey=['VerifiedNumber-Deleted'],
         endkey=['VerifiedNumber-Deleted', {}],
         include_docs=False,
         reduce=False,
     ).all()
     return [row['id'] for row in result]
Beispiel #31
0
 def getCouchCount(self):
     result = VerifiedNumber.view(
         'phone_numbers/verified_number_by_domain',
         startkey=[self.domain],
         endkey=[self.domain, {}],
         include_docs=False,
         reduce=True
     ).all()
     return result[0]['value'] if result else 0
Beispiel #32
0
    def delete_models(self, delete_interval):
        print 'Deleting VerifiedNumbers...'
        count = iter_bulk_delete_with_doc_type_verification(
            VerifiedNumber.get_db(),
            self.get_couch_ids(),
            'VerifiedNumber',
            wait_time=delete_interval,
            max_fetch_attempts=5)
        print 'Deleted %s documents' % count

        print 'Deleting Soft-Deleted VerifiedNumbers...'
        count = iter_bulk_delete_with_doc_type_verification(
            VerifiedNumber.get_db(),
            self.get_soft_deleted_couch_ids(),
            'VerifiedNumber-Deleted',
            wait_time=delete_interval,
            max_fetch_attempts=5)
        print 'Deleted %s documents' % count
 def get_soft_deleted_couch_ids(self):
     result = VerifiedNumber.view(
         'all_docs/by_doc_type',
         startkey=['VerifiedNumber-Deleted'],
         endkey=['VerifiedNumber-Deleted', {}],
         include_docs=False,
         reduce=False,
     ).all()
     return [row['id'] for row in result]
Beispiel #34
0
def ivr_in(request):
    """
    Handles tropo call requests
    """
    if request.method == "POST":
        data = json.loads(request.raw_post_data)
        phone_number = data["session"]["from"]["id"]
        ####

        # TODO: Implement tropo as an ivr backend. In the meantime, just log the call.

        cleaned_number = phone_number
        if cleaned_number is not None and len(
                cleaned_number) > 0 and cleaned_number[0] == "+":
            cleaned_number = cleaned_number[1:]

        # Try to look up the verified number entry
        v = VerifiedNumber.view("sms/verified_number_by_number",
                                key=cleaned_number,
                                include_docs=True).one()

        # If none was found, try to match only the last digits of numbers in the database
        if v is None:
            v = VerifiedNumber.view("sms/verified_number_by_suffix",
                                    key=cleaned_number,
                                    include_docs=True).one()

        # Save the call entry
        msg = CallLog(phone_number=cleaned_number,
                      direction=INCOMING,
                      date=datetime.utcnow(),
                      backend_api=TROPO_BACKEND_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")
Beispiel #35
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
Beispiel #36
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._id]
    if phone_info.phone_number:
        lock_keys.append('verifying-phone-number-%s' % phone_info.phone_number)

    with CriticalSection(lock_keys):
        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 = VerifiedNumber(
                    domain=contact_case.domain,
                    owner_doc_type=contact_case.doc_type,
                    owner_id=contact_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()
Beispiel #37
0
    def deleteAllLogs(self):
        for obj in VerifiedNumber.view(
            'phone_numbers/verified_number_by_domain',
            startkey=[self.domain],
            endkey=[self.domain, {}],
            include_docs=True,
            reduce=False
        ).all():
            obj.delete()

        PhoneNumber.objects.filter(domain=self.domain).delete()
    def delete_models(self, delete_interval):
        print('Deleting VerifiedNumbers...')
        count = iter_bulk_delete_with_doc_type_verification(
            VerifiedNumber.get_db(),
            self.get_couch_ids(),
            'VerifiedNumber',
            wait_time=delete_interval,
            max_fetch_attempts=5
        )
        print('Deleted %s documents' % count)

        print('Deleting Soft-Deleted VerifiedNumbers...')
        count = iter_bulk_delete_with_doc_type_verification(
            VerifiedNumber.get_db(),
            self.get_soft_deleted_couch_ids(),
            'VerifiedNumber-Deleted',
            wait_time=delete_interval,
            max_fetch_attempts=5
        )
        print('Deleted %s documents' % count)
Beispiel #39
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
Beispiel #40
0
 def run_script(self, script):
     commands = self.parse_script(script)
     for command in commands:
         phone_number = command['phone_number']
         v = VerifiedNumber.by_phone(phone_number)
         if command['direction'] == '>':
             incoming(phone_number, command['text'], v.backend_id, domain_scope=v.domain)
         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 setUp(self):
        self.endpoint = MockEndpoint('http://test-api.com/', 'dummy', 'dummy')
        self.api_object = EWSApi(TEST_DOMAIN, self.endpoint)
        self.datapath = os.path.join(os.path.dirname(__file__), 'data')
        initial_bootstrap(TEST_DOMAIN)
        self.api_object.prepare_commtrack_config()
        self.api_object.create_or_edit_roles()
        for user in CommCareUser.by_domain(TEST_DOMAIN):
            user.delete()

        for verified_number in VerifiedNumber.by_domain(TEST_DOMAIN):
            verified_number.delete()
Beispiel #42
0
    def setUp(self):
        self.endpoint = MockEndpoint('http://test-api.com/', 'dummy', 'dummy')
        self.api_object = EWSApi(TEST_DOMAIN, self.endpoint)
        self.datapath = os.path.join(os.path.dirname(__file__), 'data')
        initial_bootstrap(TEST_DOMAIN)
        self.api_object.prepare_commtrack_config()
        self.api_object.create_or_edit_roles()
        for user in CommCareUser.by_domain(TEST_DOMAIN):
            user.delete()

        for verified_number in VerifiedNumber.by_domain(TEST_DOMAIN):
            verified_number.delete()
Beispiel #43
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()
Beispiel #44
0
def process_verification(phone_number, msg, backend_id=None):
    v = VerifiedNumber.by_phone(phone_number, True)
    if not v:
        return

    logged_event = MessagingEvent.get_current_verification_event(
        v.domain, v.owner_id, phone_number)

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

    subevent = logged_event.create_subevent_for_single_sms(
        v.owner_doc_type, v.owner_id)
    subevent.completed()

    msg.domain = v.domain
    msg.couch_recipient_doc_type = v.owner_doc_type
    msg.couch_recipient = v.owner_id
    msg.messaging_subevent_id = subevent.pk
    msg.save()

    if (not domain_has_privilege(msg.domain, privileges.INBOUND_SMS)
            or not verification_response_ok(msg.text)):
        return

    if backend_id:
        backend = MobileBackend.load(backend_id)
    else:
        backend = MobileBackend.auto_load(phone_number, v.domain)

    assert v.owner_doc_type == 'CommCareUser'
    owner = CommCareUser.get(v.owner_id)

    v = owner.save_verified_number(v.domain, phone_number, True, backend.name)

    logged_event.completed()

    subevent = logged_event.create_subevent_for_single_sms(
        v.owner_doc_type, v.owner_id)

    with localize(owner.language):
        send_sms_to_verified_number(
            v,
            _(CONFIRM),
            metadata=MessageMetadata(messaging_subevent_id=subevent.pk))
        subevent.completed()
Beispiel #45
0
    def post(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)

        reminder = request.POST.get('reminder')
        phone_number = context.get('phone_number')

        if reminder and phone_number:
            phone_number = clean_phone_number(phone_number)
            v = VerifiedNumber.by_phone(phone_number, include_pending=True)
            if v and v.verified:
                user = v.owner
                if not user:
                    return self.get(request, *args, **kwargs)
                reminder_function = self.reminders.get(reminder)
                reminder_function(self.domain,
                                  datetime.utcnow(),
                                  test_list=[user])
        messages.success(request, "Reminder was sent successfully")
        return self.get(request, *args, **kwargs)
Beispiel #46
0
    def post(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)

        reminder = request.POST.get('reminder')
        phone_number = context.get('phone_number')

        if reminder and phone_number:
            phone_number = clean_phone_number(phone_number)
            v = VerifiedNumber.by_phone(phone_number, include_pending=True)
            if v and v.verified:
                user = v.owner
                if not user:
                    return self.get(request, *args, **kwargs)
                reminder_function = self.reminders.get(reminder)
                if reminder_function:
                    if reminder == 'third_soh':
                        reminder_function([user], [user.sql_location],
                                          test=True)
                    else:
                        reminder_function(user, test=True)
        messages.success(request, "Reminder was sent successfully")
        return self.get(request, *args, **kwargs)
Beispiel #47
0
def incoming(phone_number,
             text,
             backend_api,
             timestamp=None,
             domain_scope=None,
             delay=True):
    """
    entry point for incoming sms

    phone_number - originating phone number
    text - message content
    backend_api - backend ID of receiving sms backend
    timestamp - message received timestamp; defaults to now (UTC)
    domain_scope - if present, only messages from phone numbers that can be
      definitively linked to this domain will be processed; others will be
      dropped (useful to provide security when simulating incoming sms)
    """
    phone_number = clean_phone_number(phone_number)
    v = VerifiedNumber.by_phone(phone_number, include_pending=True)
    if domain_scope:
        # only process messages for phones known to be associated with this domain
        if v is None or v.domain != domain_scope:
            raise RuntimeError(
                'attempted to simulate incoming sms from phone number not verified with this domain'
            )

    # Log message in message log
    msg = SMSLog(phone_number=phone_number,
                 direction=INCOMING,
                 date=timestamp or datetime.utcnow(),
                 text=text,
                 backend_api=backend_api)
    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.save()

    create_billable_for_sms(msg, backend_api, delay=delay)

    if v is not None and v.verified:
        for h in settings.SMS_HANDLERS:
            try:
                handler = to_function(h)
            except:
                logging.exception('error loading sms handler: %s' % h)
                continue

            try:
                was_handled = handler(v, text)
            except:
                logging.exception(
                    'unhandled error in sms handler %s for message [%s]' %
                    (h, text))
                was_handled = False

            if was_handled:
                break
    else:
        if not process_sms_registration(msg):
            import verify
            verify.process_verification(phone_number, text)

    return msg
Beispiel #48
0
    context = {"domain": domain, "samples": SurveySample.get_all(domain)}
    return render(request, "reminders/partial/sample_list.html", context)


@reminders_permission
def edit_contact(request, domain, sample_id, case_id):
    case = CommCareCase.get(case_id)
    if case.domain != domain:
        raise Http404
    if request.method == "POST":
        form = EditContactForm(request.POST)
        if form.is_valid():
            phone_number = form.cleaned_data.get("phone_number")
            vn = VerifiedNumber.view(
                'sms/verified_number_by_number',
                key=phone_number,
                include_docs=True,
            ).one()
            if vn is not None and vn.owner_id != case_id:
                form._errors["phone_number"] = form.error_class(
                    ["Phone number is already in use."])
            else:
                update_contact(domain,
                               case_id,
                               request.couch_user.get_id,
                               contact_phone_number=phone_number)
                return HttpResponseRedirect(
                    reverse("edit_sample", args=[domain, sample_id]))
    else:
        initial = {
            "phone_number": case.get_case_property("contact_phone_number"),
Beispiel #49
0
def add_sample(request, domain, sample_id=None):
    sample = None
    if sample_id is not None:
        sample = SurveySample.get(sample_id)

    if request.method == "POST":
        form = SurveySampleForm(request.POST, request.FILES)
        if form.is_valid():
            name = form.cleaned_data.get("name")
            sample_contacts = form.cleaned_data.get("sample_contacts")
            time_zone = form.cleaned_data.get("time_zone")
            use_contact_upload_file = form.cleaned_data.get(
                "use_contact_upload_file")
            contact_upload_file = form.cleaned_data.get("contact_upload_file")

            if sample is None:
                sample = SurveySample(domain=domain,
                                      name=name,
                                      time_zone=time_zone.zone)
            else:
                sample.name = name
                sample.time_zone = time_zone.zone

            errors = []

            phone_numbers = []
            if use_contact_upload_file == "Y":
                for contact in contact_upload_file:
                    phone_numbers.append(contact["phone_number"])
            else:
                for contact in sample_contacts:
                    phone_numbers.append(contact["phone_number"])

            existing_number_entries = VerifiedNumber.view(
                'sms/verified_number_by_number',
                keys=phone_numbers,
                include_docs=True).all()

            for entry in existing_number_entries:
                if entry.domain != domain or entry.owner_doc_type != "CommCareCase":
                    errors.append("Cannot use phone number %s" %
                                  entry.phone_number)

            if len(errors) > 0:
                if use_contact_upload_file == "Y":
                    form._errors["contact_upload_file"] = form.error_class(
                        errors)
                else:
                    form._errors["sample_contacts"] = form.error_class(errors)
            else:
                existing_numbers = [
                    v.phone_number for v in existing_number_entries
                ]
                nonexisting_numbers = list(
                    set(phone_numbers).difference(existing_numbers))

                id_range = DomainCounter.increment(domain, "survey_contact_id",
                                                   len(nonexisting_numbers))
                ids = iter(range(id_range[0], id_range[1] + 1))
                for phone_number in nonexisting_numbers:
                    register_sms_contact(
                        domain,
                        "participant",
                        str(ids.next()),
                        request.couch_user.get_id,
                        phone_number,
                        contact_phone_number_is_verified="1",
                        contact_backend_id="MOBILE_BACKEND_TROPO_US",
                        language_code="en",
                        time_zone=time_zone.zone)

                newly_registered_entries = VerifiedNumber.view(
                    'sms/verified_number_by_number',
                    keys=nonexisting_numbers,
                    include_docs=True).all()

                sample.contacts = [
                    v.owner_id for v in existing_number_entries
                ] + [v.owner_id for v in newly_registered_entries]

                sample.save()

                # Update delegation tasks for surveys using this sample
                surveys = Survey.view("reminders/sample_to_survey",
                                      key=[domain, sample._id, "CATI"],
                                      include_docs=True).all()
                for survey in surveys:
                    survey.update_delegation_tasks(request.couch_user.get_id)
                    survey.save()

                return HttpResponseRedirect(
                    reverse("sample_list", args=[domain]))
    else:
        initial = {}
        if sample is not None:
            initial["name"] = sample.name
            initial["time_zone"] = sample.time_zone
            contact_info = []
            for case_id in sample.contacts:
                case = CommCareCase.get(case_id)
                contact_info.append({
                    "id": case.name,
                    "phone_number": case.contact_phone_number,
                    "case_id": case_id
                })
            initial["sample_contacts"] = contact_info
        form = SurveySampleForm(initial=initial)
Beispiel #50
0
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers):
    if reminder.callback_try_count > 0:
        # Handle timeouts
        if handler.submit_partial_forms and (
                reminder.callback_try_count == len(
                    reminder.current_event.callback_timeout_intervals)):
            # Submit partial form completions
            for session_id in reminder.xforms_session_ids:
                submit_unfinished_form(session_id,
                                       handler.include_case_side_effects)
        else:
            # Resend current question
            for session_id in reminder.xforms_session_ids:
                session = XFormsSession.view(
                    "smsforms/sessions_by_touchforms_id",
                    startkey=[session_id],
                    endkey=[session_id, {}],
                    include_docs=True).one()
                if session.end_time is None:
                    vn = VerifiedNumber.view("sms/verified_number_by_owner_id",
                                             key=session.connection_id,
                                             include_docs=True).first()
                    if vn is not None:
                        metadata = MessageMetadata(
                            workflow=get_workflow(handler),
                            reminder_id=reminder._id,
                            xforms_session_couch_id=session._id,
                        )
                        resp = current_question(session_id)
                        send_sms_to_verified_number(vn, resp.event.text_prompt,
                                                    metadata)
        return True
    else:
        reminder.xforms_session_ids = []

        # Get the app, module, and form
        try:
            form_unique_id = reminder.current_event.form_unique_id
            form = Form.get_form(form_unique_id)
            app = form.get_app()
            module = form.get_module()
        except Exception as e:
            raise_error(reminder, ERROR_FORM)
            return False

        # Start a touchforms session for each recipient
        for recipient in recipients:

            verified_number, unverified_number = get_recipient_phone_number(
                reminder, recipient, verified_numbers)

            domain_obj = Domain.get_by_name(reminder.domain, strict=True)
            no_verified_number = verified_number is None
            cant_use_unverified_number = (
                unverified_number is None
                or not domain_obj.send_to_duplicated_case_numbers
                or form_requires_input(form))
            if no_verified_number and cant_use_unverified_number:
                if len(recipients) == 1:
                    raise_error(reminder, ERROR_NO_VERIFIED_NUMBER)
                    return False
                else:
                    continue

            # Close all currently open sessions
            XFormsSession.close_all_open_sms_sessions(reminder.domain,
                                                      recipient.get_id)

            # Start the new session
            if isinstance(
                    recipient, CommCareCase
            ) and not handler.force_surveys_to_use_triggered_case:
                case_id = recipient.get_id
            else:
                case_id = reminder.case_id
            session, responses = start_session(
                reminder.domain,
                recipient,
                app,
                module,
                form,
                case_id,
                case_for_case_submission=handler.
                force_surveys_to_use_triggered_case)
            session.survey_incentive = handler.survey_incentive
            session.workflow = get_workflow(handler)
            session.reminder_id = reminder._id
            session.save()
            reminder.xforms_session_ids.append(session.session_id)

            # Send out first message
            if len(responses) > 0:
                message = format_message_list(responses)
                metadata = MessageMetadata(
                    workflow=get_workflow(handler),
                    reminder_id=reminder._id,
                    xforms_session_couch_id=session._id,
                )
                if verified_number:
                    result = send_sms_to_verified_number(
                        verified_number, message, metadata)
                else:
                    result = send_sms(reminder.domain, recipient,
                                      unverified_number, message, metadata)

                if len(recipients) == 1:
                    return result

        return True
Beispiel #51
0
 def _reassign_number(self, user, phone_number):
     v = VerifiedNumber.by_phone(phone_number, include_pending=True)
     if v.domain in self._get_logistics_domains():
         v.delete()
         user.save_verified_number(self.domain, phone_number, True)
Beispiel #52
0
def incoming(phone_number,
             backend_module,
             gateway_session_id,
             ivr_event,
             input_data=None):
    # Look up the call if one already exists
    call_log_entry = CallLog.view("sms/call_by_session",
                                  startkey=[gateway_session_id, {}],
                                  endkey=[gateway_session_id],
                                  descending=True,
                                  include_docs=True,
                                  limit=1).one()

    answer_is_valid = False  # This will be set to True if IVR validation passes
    error_occurred = False  # This will be set to False if touchforms validation passes (i.e., no form constraints fail)

    if call_log_entry is not None and backend_module:
        if ivr_event == IVR_EVENT_NEW_CALL and call_log_entry.use_precached_first_response:
            return HttpResponse(call_log_entry.first_response)

        form = Form.get_form(call_log_entry.form_unique_id)
        app = form.get_app()
        module = form.get_module()
        recipient = call_log_entry.recipient

        if ivr_event == IVR_EVENT_NEW_CALL:
            case_id = call_log_entry.case_id
            case_for_case_submission = call_log_entry.case_for_case_submission
            session, responses = start_session(
                recipient.domain,
                recipient,
                app,
                module,
                form,
                case_id,
                yield_responses=True,
                session_type=XFORMS_SESSION_IVR,
                case_for_case_submission=case_for_case_submission)
            call_log_entry.xforms_session_id = session.session_id
        elif ivr_event == IVR_EVENT_INPUT:
            if call_log_entry.xforms_session_id is not None:
                current_q = current_question(call_log_entry.xforms_session_id)
                if validate_answer(input_data, current_q):
                    answer_is_valid = True
                    responses = _get_responses(
                        recipient.domain,
                        recipient._id,
                        input_data,
                        yield_responses=True,
                        session_id=call_log_entry.xforms_session_id)
                else:
                    call_log_entry.current_question_retry_count += 1
                    responses = [current_q]
            else:
                responses = []
        else:
            responses = []

        ivr_responses = []
        hang_up = False
        for response in responses:
            if response.is_error:
                error_occurred = True
                call_log_entry.current_question_retry_count += 1
                if response.text_prompt is None:
                    ivr_responses = []
                    break
                else:
                    ivr_responses.append(
                        format_ivr_response(response.text_prompt, app))
            elif response.event.type == "question":
                ivr_responses.append(
                    format_ivr_response(response.event.caption, app))
            elif response.event.type == "form-complete":
                hang_up = True

        if answer_is_valid and not error_occurred:
            call_log_entry.current_question_retry_count = 0

        if call_log_entry.max_question_retries is not None and call_log_entry.current_question_retry_count > call_log_entry.max_question_retries:
            # Force hang-up
            ivr_responses = []

        if len(ivr_responses) == 0:
            hang_up = True

        input_length = None

        if hang_up:
            if call_log_entry.xforms_session_id is not None:
                # Process disconnect
                session = XFormsSession.latest_by_session_id(
                    call_log_entry.xforms_session_id)
                if session.end_time is None:
                    if call_log_entry.submit_partial_form:
                        submit_unfinished_form(
                            session.session_id,
                            call_log_entry.include_case_side_effects)
                    else:
                        session.end(completed=False)
                        session.save()
        else:
            # Set input_length to let the ivr gateway know how many digits we need to collect.
            # Have to get the current question again, since the last XFormsResponse in responses
            # may not have an event if it was a response to a constraint error.
            if error_occurred:
                current_q = current_question(call_log_entry.xforms_session_id)
            else:
                current_q = responses[-1]

            input_length = get_input_length(current_q)

        call_log_entry.save()
        return HttpResponse(
            backend_module.get_http_response_string(
                gateway_session_id,
                ivr_responses,
                collect_input=(not hang_up),
                hang_up=hang_up,
                input_length=input_length))

    # If not processed, just log the call

    if call_log_entry:
        # No need to log, already exists
        return HttpResponse("")

    cleaned_number = phone_number
    if cleaned_number is not None and len(
            cleaned_number) > 0 and cleaned_number[0] == "+":
        cleaned_number = cleaned_number[1:]

    # Try to look up the verified number entry
    v = VerifiedNumber.view("sms/verified_number_by_number",
                            key=cleaned_number,
                            include_docs=True).one()

    # If none was found, try to match only the last digits of numbers in the database
    if v is None:
        v = VerifiedNumber.view("sms/verified_number_by_suffix",
                                key=cleaned_number,
                                include_docs=True).one()

    # Save the call entry
    msg = 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 is not None:
        msg.domain = v.domain
        msg.couch_recipient_doc_type = v.owner_doc_type
        msg.couch_recipient = v.owner_id
    msg.save()

    return HttpResponse("")
Beispiel #53
0
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers,
                          logged_event):
    if reminder.callback_try_count > 0:
        # Handle timeouts
        if handler.submit_partial_forms and (
                reminder.callback_try_count == len(
                    reminder.current_event.callback_timeout_intervals)):
            # Submit partial form completions
            for session_id in reminder.xforms_session_ids:
                submit_unfinished_form(session_id,
                                       handler.include_case_side_effects)
        else:
            # Resend current question
            for session_id in reminder.xforms_session_ids:
                session = get_session_by_session_id(session_id)
                if session.end_time is None:
                    vn = VerifiedNumber.view("sms/verified_number_by_owner_id",
                                             key=session.connection_id,
                                             include_docs=True).first()
                    if vn is not None:
                        metadata = MessageMetadata(
                            workflow=get_workflow(handler),
                            reminder_id=reminder._id,
                            xforms_session_couch_id=session._id,
                        )
                        resp = current_question(session_id)
                        send_sms_to_verified_number(vn, resp.event.text_prompt,
                                                    metadata)
    else:
        reminder.xforms_session_ids = []
        domain_obj = Domain.get_by_name(reminder.domain, strict=True)

        # Get the app, module, and form
        try:
            form_unique_id = reminder.current_event.form_unique_id
            form = Form.get_form(form_unique_id)
            app = form.get_app()
            module = form.get_module()
        except Exception:
            logged_event.error(MessagingEvent.ERROR_CANNOT_FIND_FORM)
            return

        # Start a touchforms session for each recipient
        for recipient in recipients:
            logged_subevent = logged_event.create_subevent(
                handler, reminder, recipient)

            verified_number, unverified_number = get_recipient_phone_number(
                reminder, recipient, verified_numbers)

            no_verified_number = verified_number is None
            cant_use_unverified_number = (
                unverified_number is None
                or not domain_obj.send_to_duplicated_case_numbers
                or form_requires_input(form))
            if no_verified_number and cant_use_unverified_number:
                logged_subevent.error(
                    MessagingEvent.ERROR_NO_TWO_WAY_PHONE_NUMBER)
                continue

            key = "start-sms-survey-for-contact-%s" % recipient.get_id
            with CriticalSection([key], timeout=60):
                # Get the case to submit the form against, if any
                if (isinstance(recipient, CommCareCase)
                        and not handler.force_surveys_to_use_triggered_case):
                    case_id = recipient.get_id
                else:
                    case_id = reminder.case_id

                if form.requires_case() and not case_id:
                    logged_subevent.error(MessagingEvent.ERROR_NO_CASE_GIVEN)
                    continue

                # Close all currently open sessions
                SQLXFormsSession.close_all_open_sms_sessions(
                    reminder.domain, recipient.get_id)

                # Start the new session
                try:
                    session, responses = start_session(
                        reminder.domain,
                        recipient,
                        app,
                        module,
                        form,
                        case_id,
                        case_for_case_submission=handler.
                        force_surveys_to_use_triggered_case)
                except TouchformsError as e:
                    human_readable_message = e.response_data.get(
                        'human_readable_message', None)

                    logged_subevent.error(
                        MessagingEvent.ERROR_TOUCHFORMS_ERROR,
                        additional_error_text=human_readable_message)

                    if touchforms_error_is_config_error(e):
                        # Don't reraise the exception because this means there are configuration
                        # issues with the form that need to be fixed
                        continue
                    else:
                        # Reraise the exception so that the framework retries it again later
                        raise
                except Exception as e:
                    logged_subevent.error(
                        MessagingEvent.ERROR_TOUCHFORMS_ERROR)
                    # Reraise the exception so that the framework retries it again later
                    raise
                session.survey_incentive = handler.survey_incentive
                session.workflow = get_workflow(handler)
                session.reminder_id = reminder._id
                session.save()

            reminder.xforms_session_ids.append(session.session_id)
            logged_subevent.xforms_session = session
            logged_subevent.save()

            # Send out first message
            if len(responses) > 0:
                message = format_message_list(responses)
                metadata = MessageMetadata(
                    workflow=get_workflow(handler),
                    reminder_id=reminder._id,
                    xforms_session_couch_id=session._id,
                )
                if verified_number:
                    send_sms_to_verified_number(verified_number, message,
                                                metadata)
                else:
                    send_sms(reminder.domain, recipient, unverified_number,
                             message, metadata)

            logged_subevent.completed()
Beispiel #54
0
    def handle(self):
        text = ' '.join(self.msg.text.split()[1:])
        is_district = False
        sp = ""
        msd_code = ""

        if text.find(self.DISTRICT_REG_DELIMITER) != -1:
            phrases = [x.strip() for x in text.split(":")]
            if len(phrases) != 2:
                self.respond(REGISTER_HELP)
                return
            name = phrases[0]
            sp = phrases[1]
            role = Roles.DISTRICT_PHARMACIST
            message = REGISTRATION_CONFIRM_DISTRICT
            params = {}
            is_district = True
        else:
            names = []
            msd_codes = []
            location_regex = '^({prefs})\d+'.format(prefs='|'.join(
                p.lower() for p in DISTRICT_PREFIXES))
            for the_string in self.args:
                if re.match(location_regex, the_string.strip().lower()):
                    msd_codes.append(the_string.strip().lower())
                else:
                    names.append(the_string)

            name = " ".join(names)
            if len(msd_codes) != 1:
                self.respond(REGISTER_HELP)
                return
            else:
                [msd_code] = msd_codes

            role = Roles.IN_CHARGE
            message = REGISTRATION_CONFIRM
            params = {"msd_code": msd_code}

        if not self.user:
            domains = [
                config.domain for config in ILSGatewayConfig.get_all_configs()
            ]
            for domain in domains:
                if is_district:
                    loc = self._get_district_location(domain, sp)
                else:
                    loc = self._get_facility_location(domain, msd_code)
                if not loc:
                    continue
                splited_name = name.split(' ', 1)
                first_name = splited_name[0]
                last_name = splited_name[1] if len(splited_name) > 1 else ""
                clean_name = name.replace(' ', '.')
                username = "******" % (clean_name, domain)
                password = User.objects.make_random_password()
                user = CommCareUser.create(domain=domain,
                                           username=username,
                                           password=password,
                                           commit=False)
                user.first_name = first_name
                user.last_name = last_name
                try:
                    user.set_default_phone_number(
                        self.msg.phone_number.replace('+', ''))
                    user.save_verified_number(
                        domain, self.msg.phone_number.replace('+', ''), True,
                        self.msg.backend_api)
                except PhoneNumberInUseException as e:
                    v = VerifiedNumber.by_phone(self.msg.phone_number,
                                                include_pending=True)
                    v.delete()
                    user.save_verified_number(
                        domain, self.msg.phone_number.replace('+', ''), True,
                        self.msg.backend_api)
                except CommCareUser.Inconsistent:
                    continue
                user.language = Languages.DEFAULT
                params.update({'sdp_name': loc.name, 'contact_name': name})

                user.user_data = {'role': role}

                dm = user.get_domain_membership(domain)
                dm.location_id = loc._id
                user.save()
                add_location(user, loc._id)
        if params:
            self.respond(message, **params)
Beispiel #55
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
Beispiel #56
0
def sync_ilsgateway_smsuser(domain, ilsgateway_smsuser):
    domain_part = "%s.commcarehq.org" % domain
    username_part = "%s%d" % (ilsgateway_smsuser.name.strip().replace(' ', '.').lower(), ilsgateway_smsuser.id)
    username = "******" % (username_part[:(128 - len(domain_part))], domain_part)
    #sanity check
    assert len(username) <= 128
    user = CouchUser.get_by_username(username)
    splitted_value = ilsgateway_smsuser.name.split(' ', 1)
    first_name = last_name = ''
    if splitted_value:
        first_name = splitted_value[0][:30]
        last_name = splitted_value[1][:30] if len(splitted_value) > 1 else ''

    user_dict = {
        'first_name': first_name,
        'last_name': last_name,
        'is_active': bool(ilsgateway_smsuser.is_active),
        'email': ilsgateway_smsuser.email,
        'user_data': {}
    }

    if ilsgateway_smsuser.role:
        user_dict['user_data']['role'] = ilsgateway_smsuser.role

    if ilsgateway_smsuser.phone_numbers:
        user_dict['phone_numbers'] = [ilsgateway_smsuser.phone_numbers[0].replace('+', '')]
        user_dict['user_data']['backend'] = ilsgateway_smsuser.backend

    sp = SupplyPointCase.view('hqcase/by_domain_external_id',
                              key=[domain, str(ilsgateway_smsuser.supply_point)],
                              reduce=False,
                              include_docs=True,
                              limit=1).first()
    location_id = sp.location_id if sp else None

    if user is None and username_part:
        try:
            password = User.objects.make_random_password()
            user = CommCareUser.create(domain=domain, username=username, password=password,
                                       email=ilsgateway_smsuser.email, commit=False)
            user.first_name = first_name
            user.last_name = last_name
            user.is_active = bool(ilsgateway_smsuser.is_active)
            user.user_data = user_dict["user_data"]
            if "phone_numbers" in user_dict:
                user.set_default_phone_number(user_dict["phone_numbers"][0])
                try:
                    user.save_verified_number(domain, user_dict["phone_numbers"][0], True, ilsgateway_smsuser.backend)
                except PhoneNumberInUseException as e:
                    v = VerifiedNumber.by_phone(user_dict["phone_numbers"][0], include_pending=True)
                    v.delete()
                    user.save_verified_number(domain, user_dict["phone_numbers"][0], True, ilsgateway_smsuser.backend)
            dm = user.get_domain_membership(domain)
            dm.location_id = location_id
            user.save()
            add_location(user, location_id)

        except Exception as e:
            logging.error(e)
    else:
        dm = user.get_domain_membership(domain)
        current_location_id = dm.location_id if dm else None
        save = False

        if current_location_id != location_id:
            dm.location_id = location_id
            add_location(user, location_id)
            save = True

        if apply_updates(user, user_dict) or save:
            user.save()
    return user
Beispiel #57
0
def fire_sms_survey_event(reminder, handler, recipients, verified_numbers):
    if handler.recipient in [RECIPIENT_CASE, RECIPIENT_SURVEY_SAMPLE]:
        if reminder.callback_try_count > 0:
            # Handle timeouts
            if handler.submit_partial_forms and (
                    reminder.callback_try_count == len(
                        reminder.current_event.callback_timeout_intervals)):
                # Submit partial form completions
                for session_id in reminder.xforms_session_ids:
                    submit_unfinished_form(session_id,
                                           handler.include_case_side_effects)
            else:
                # Resend current question
                for session_id in reminder.xforms_session_ids:
                    session = XFormsSession.view(
                        "smsforms/sessions_by_touchforms_id",
                        startkey=[session_id],
                        endkey=[session_id, {}],
                        include_docs=True).one()
                    if session.end_time is None:
                        vn = VerifiedNumber.view(
                            "sms/verified_number_by_owner_id",
                            key=session.connection_id,
                            include_docs=True).one()
                        if vn is not None:
                            resp = current_question(session_id)
                            send_sms_to_verified_number(
                                vn, resp.event.text_prompt)
            return True
        else:
            reminder.xforms_session_ids = []

            # Get the app, module, and form
            try:
                form_unique_id = reminder.current_event.form_unique_id
                form = Form.get_form(form_unique_id)
                app = form.get_app()
                module = form.get_module()
            except Exception as e:
                raise_error(reminder, ERROR_FORM)
                return False

            # Start a touchforms session for each recipient
            for recipient in recipients:
                verified_number = verified_numbers[recipient.get_id]
                if verified_number is None:
                    if len(recipients) == 1:
                        raise_error(reminder, ERROR_NO_VERIFIED_NUMBER)
                        return False
                    else:
                        raise_warning()  # ERROR_NO_VERIFIED_NUMBER
                        continue

                # Close all currently open sessions
                close_open_sessions(reminder.domain, recipient.get_id)

                # Start the new session
                session, responses = start_session(reminder.domain, recipient,
                                                   app, module, form,
                                                   recipient.get_id)
                session.survey_incentive = handler.survey_incentive
                session.save()
                reminder.xforms_session_ids.append(session.session_id)

                # Send out first message
                if len(responses) > 0:
                    message = format_message_list(responses)
                    result = send_sms_to_verified_number(
                        verified_number, message)
                    if not result:
                        raise_warning()  # Could not send SMS

                    if len(recipients) == 1:
                        return result

            return True
    else:
        # TODO: Make sure the above flow works for RECIPIENT_USER and RECIPIENT_OWNER
        return False