Example #1
0
    def test_end_date(self):

        daily = Archive.objects.create(
            org=self.org,
            archive_type=Archive.TYPE_FLOWRUN,
            size=10,
            hash=uuid4().hex,
            url=f"http://s3-bucket.aws.com/my/32562662.jsonl.gz",
            record_count=100,
            start_date=date(2018, 2, 1),
            period="D",
            build_time=1234,
            needs_deletion=True,
        )

        monthly = Archive.objects.create(
            org=self.org,
            archive_type=Archive.TYPE_FLOWRUN,
            size=10,
            hash=uuid4().hex,
            url=f"http://s3-bucket.aws.com/my/32562663.jsonl.gz",
            record_count=2000,
            start_date=date(2018, 1, 1),
            period="M",
            build_time=1234,
            needs_deletion=False,
        )

        self.assertEqual(date(2018, 2, 2), daily.get_end_date())
        self.assertEqual(date(2018, 2, 1), monthly.get_end_date())

        # check the start date of our db data
        self.assertEqual(date(2018, 2, 1), self.org.get_delete_date(archive_type=Archive.TYPE_FLOWRUN))
Example #2
0
    def form_valid(self, form):
        from .type import RocketChatType

        base_url = form.cleaned_data["base_url"]
        config = {
            RocketChatType.CONFIG_BASE_URL: base_url,
            RocketChatType.CONFIG_SECRET: form.cleaned_data["secret"],
            RocketChatType.CONFIG_ADMIN_AUTH_TOKEN: form.cleaned_data["admin_auth_token"],
            RocketChatType.CONFIG_ADMIN_USER_ID: form.cleaned_data["admin_user_id"],
        }

        rc_host = urlparse(base_url).netloc

        self.object = Ticketer(
            uuid=uuid4(),
            org=self.org,
            ticketer_type=RocketChatType.slug,
            config=config,
            name=rc_host,
            created_by=self.request.user,
            modified_by=self.request.user,
        )

        client = Client(config[RocketChatType.CONFIG_BASE_URL], config[RocketChatType.CONFIG_SECRET])
        webhook_url = WEBHOOK_URL_TEMPLATE.format(domain=self.object.org.get_brand_domain(), uuid=self.object.uuid)

        try:
            client.settings(webhook_url)
            self.request.session.pop(self.SESSION_KEY, None)
        except ClientError as err:
            messages.error(self.request, err.msg if err.msg else _("Configuration has failed"))
            return super().get(self.request, *self.args, **self.kwargs)

        self.object.save()
        return super().form_valid(form)
Example #3
0
    def create_archive(self,
                       archive_type,
                       period,
                       start_date,
                       records=(),
                       needs_deletion=False,
                       rollup_of=(),
                       s3=None,
                       org=None):
        archive_hash = uuid4().hex
        bucket = "s3-bucket"
        key = f"things/{archive_hash}.jsonl.gz"
        if s3:
            s3.put_jsonl(bucket, key, records)

        archive = Archive.objects.create(
            org=org or self.org,
            archive_type=archive_type,
            size=10,
            hash=archive_hash,
            url=f"http://{bucket}.aws.com/{key}",
            record_count=len(records),
            start_date=start_date,
            period=period,
            build_time=23425,
            needs_deletion=needs_deletion,
        )
        if rollup_of:
            Archive.objects.filter(id__in=[a.id for a in rollup_of]).update(
                rollup=archive)
        return archive
Example #4
0
    def __init__(self, contact, flow, start=None):
        self.org = contact.org
        self.contact = contact
        self.start = start
        self.session = None

        contact_def = {
            "uuid": str(self.contact.uuid),
            "name": self.contact.name,
            "language": self.contact.language
        }

        self.output = {
            "uuid":
            str(uuid4()),
            "type":
            Flow.GOFLOW_TYPES[flow.flow_type],
            "environment":
            self.org.as_environment_def(),
            "trigger": {
                "type": "manual",
                "flow": flow.as_export_ref(),
                "contact": contact_def,
                "triggered_on": self._now(),
            },
            "contact":
            contact_def,
            "runs": [{
                "uuid": str(uuid4()),
                "flow": flow.as_export_ref(),
                "path": [],
                "events": [],
                "results": {},
                "status": "active",
                "created_on": self._now(),
                "modified_on": self._now(),
                "exited_on": None,
            }],
            "status":
            "active",
        }

        self.current_run = self.output["runs"][0]
        self.current_node = None
        self.events = []
Example #5
0
 def create(cls, org, user, ticketer_type, name, config):
     return cls.objects.create(
         uuid=uuid4(),
         ticketer_type=ticketer_type,
         name=name,
         config=config,
         org=org,
         created_by=user,
         modified_by=user,
     )
Example #6
0
    def form_valid(self, form):
        user = self.request.user
        org = user.get_org()
        data = form.cleaned_data

        country = data.get("country")
        number = data.get("number")
        url = data.get("url")
        role = data.get("role")

        config = {
            Channel.CONFIG_SEND_URL:
            url,
            Channel.CONFIG_ACCOUNT_SID:
            data.get("account_sid", None),
            Channel.CONFIG_AUTH_TOKEN:
            data.get("account_token", str(uuid4())),
            Channel.CONFIG_CALLBACK_DOMAIN:
            org.get_brand_domain(),
            Channel.CONFIG_MAX_CONCURRENT_EVENTS:
            data.get("max_concurrent_events", None),
        }

        is_short_code = len(number) <= 6

        if not is_short_code:
            phone_number = phonenumbers.parse(number=number, region=country)
            address = f"+{str(phone_number.country_code)}{str(phone_number.national_number)}"

            name = phonenumbers.format_number(
                phonenumbers.parse(address, None),
                phonenumbers.PhoneNumberFormat.NATIONAL)
        else:
            role = Channel.ROLE_SEND + Channel.ROLE_RECEIVE
            address = number
            name = number

        self.object = Channel.create(org,
                                     user,
                                     country,
                                     "TW",
                                     name=name,
                                     address=address,
                                     config=config,
                                     role=role)

        if not data.get("account_sid", None):
            config[
                Channel.
                CONFIG_ACCOUNT_SID] = f"{self.request.branding['name'].lower()}_{self.object.pk}"

            self.object.config = config
            self.object.save(update_fields=("config", ))

        return super().form_valid(form)
Example #7
0
    def send_msg(self, text, channel=None, attachments=[]):
        msg = {
            "uuid": str(uuid4()),
            "urn": self.contact.get_urn().urn,
            "text": text,
            "channel": self._channel_ref(channel),
        }
        if attachments:
            msg["attachments"] = attachments

        self._log_event("msg_created", msg=msg)
        return self
Example #8
0
 def create_incoming_call(self,
                          flow,
                          contact,
                          status=IVRCall.STATUS_COMPLETED):
     """
     Create something that looks like an incoming IVR call handled by mailroom
     """
     call = IVRCall.objects.create(
         org=self.org,
         channel=self.channel,
         direction=IVRCall.DIRECTION_IN,
         contact=contact,
         contact_urn=contact.get_urn(),
         status=status,
         duration=15,
     )
     session = FlowSession.objects.create(uuid=uuid4(),
                                          org=contact.org,
                                          contact=contact,
                                          connection=call)
     FlowRun.objects.create(org=self.org,
                            flow=flow,
                            contact=contact,
                            connection=call,
                            session=session)
     Msg.objects.create(
         org=self.org,
         channel=self.channel,
         connection=call,
         direction="O",
         contact=contact,
         contact_urn=contact.get_urn(),
         text="Hello",
         status="S",
         sent_on=timezone.now(),
         created_on=timezone.now(),
     )
     ChannelLog.objects.create(
         channel=self.channel,
         connection=call,
         request='{"say": "Hello"}',
         response='{"status": "%s"}' %
         ("error" if status == IVRCall.STATUS_FAILED else "OK"),
         url="https://acme-calls.com/reply",
         method="POST",
         is_error=status == IVRCall.STATUS_FAILED,
         response_status=200,
         description="Looks good",
     )
     return call
Example #9
0
    def create_flow(self, definition=None, **kwargs):
        if "org" not in kwargs:
            kwargs["org"] = self.org
        if "user" not in kwargs:
            kwargs["user"] = self.user
        if "name" not in kwargs:
            kwargs["name"] = "Color Flow"

        flow = Flow.create(**kwargs)
        if not definition:
            # if definition isn't provided, generate simple single message flow
            node_uuid = str(uuid4())
            definition = {
                "version":
                "10",
                "flow_type":
                "F",
                "base_language":
                "eng",
                "entry":
                node_uuid,
                "action_sets": [{
                    "uuid":
                    node_uuid,
                    "x":
                    0,
                    "y":
                    0,
                    "actions": [{
                        "msg": {
                            "eng": "Hey everybody!"
                        },
                        "media": {},
                        "send_all": False,
                        "type": "reply"
                    }],
                    "destination":
                    None,
                }],
                "rule_sets": [],
            }

        flow.version_number = definition["version"]
        flow.save()

        json_flow = FlowRevision.migrate_definition(
            definition, flow, to_version=Flow.FINAL_LEGACY_VERSION)
        flow.update(json_flow)

        return flow
Example #10
0
    def form_valid(self, form):
        from .type import RocketChatType

        base_url = form.cleaned_data["base_url"]
        bot_username = form.cleaned_data["bot_username"]
        admin_auth_token = form.cleaned_data["admin_auth_token"]
        admin_user_id = form.cleaned_data["admin_user_id"]
        secret = form.cleaned_data["secret"]
        config = {
            RocketChatType.CONFIG_BASE_URL: base_url,
            RocketChatType.CONFIG_BOT_USERNAME: bot_username,
            RocketChatType.CONFIG_ADMIN_AUTH_TOKEN: admin_auth_token,
            RocketChatType.CONFIG_ADMIN_USER_ID: admin_user_id,
            RocketChatType.CONFIG_SECRET: secret,
        }

        rc_host = urlparse(base_url).netloc

        self.object = Channel(
            uuid=uuid4(),
            org=self.org,
            channel_type=RocketChatType.code,
            config=config,
            name=truncate(f"{RocketChatType.name}: {rc_host}",
                          Channel._meta.get_field("name").max_length),
            created_by=self.request.user,
            modified_by=self.request.user,
        )

        client = Client(config[RocketChatType.CONFIG_BASE_URL],
                        config[RocketChatType.CONFIG_SECRET])
        webhook_url = "https://" + self.object.callback_domain + reverse(
            "courier.rc", args=[self.object.uuid])

        try:
            client.settings(webhook_url, bot_username)
        except ClientError as err:
            messages.error(
                self.request,
                err.msg if err.msg else _("Configuration has failed"))
            return super().get(self.request, *self.args, **self.kwargs)
        else:
            self.request.session.pop(self.SESSION_KEY, None)

        self.object.save()
        return super().form_valid(form)
Example #11
0
    def claim_number(self, user, phone_number, country, role):
        org = user.get_org()

        client = org.get_twilio_client()
        twilio_phones = client.api.incoming_phone_numbers.stream(phone_number=phone_number)
        channel_uuid = uuid4()

        # create new TwiML app
        callback_domain = org.get_brand_domain()

        twilio_phone = next(twilio_phones, None)
        if not twilio_phone:
            raise Exception(_("Only existing Twilio WhatsApp number are supported"))

        phone = phonenumbers.format_number(
            phonenumbers.parse(phone_number, None), phonenumbers.PhoneNumberFormat.NATIONAL
        )

        number_sid = twilio_phone.sid

        org_config = org.config
        config = {
            Channel.CONFIG_NUMBER_SID: number_sid,
            Channel.CONFIG_ACCOUNT_SID: org_config[Org.CONFIG_TWILIO_SID],
            Channel.CONFIG_AUTH_TOKEN: org_config[Org.CONFIG_TWILIO_TOKEN],
            Channel.CONFIG_CALLBACK_DOMAIN: callback_domain,
        }

        role = Channel.ROLE_SEND + Channel.ROLE_RECEIVE

        channel = Channel.create(
            org,
            user,
            country,
            "TWA",
            name=phone,
            address=phone_number,
            role=role,
            config=config,
            uuid=channel_uuid,
            schemes=[WHATSAPP_SCHEME],
        )

        analytics.track(user.username, "temba.channel_claim_twilio_whatsapp", properties=dict(number=phone_number))

        return channel
Example #12
0
    def create_archive(self, org, idx, start_date=None, period="D"):

        if not start_date:
            start_date = date(2018, idx, 1)
            period = "M"

        archive_hash = uuid4().hex
        return Archive.objects.create(
            archive_type=Archive.TYPE_MSG if idx % 2 == 0 else Archive.TYPE_FLOWRUN,
            size=100_000 * idx,
            hash=archive_hash,
            url=f"http://s3-bucket.aws.com/my/{archive_hash}.jsonl.gz",
            record_count=123_456_789 * idx,
            start_date=start_date,
            period=period,
            build_time=idx * 123,
            org=org,
        )
Example #13
0
    def create(cls, org, user, classifier_type, name, config, sync=True):
        classifier = Classifier.objects.create(
            uuid=uuid4(),
            name=name,
            classifier_type=classifier_type,
            config=config,
            org=org,
            created_by=user,
            modified_by=user,
            created_on=timezone.now(),
            modified_on=timezone.now(),
        )

        # trigger a sync of this classifier's intents
        if sync:
            classifier.async_sync()

        return classifier
Example #14
0
    def create_flow(self,
                    name="Test Flow",
                    *,
                    flow_type=Flow.TYPE_MESSAGE,
                    nodes=None,
                    is_system=False,
                    org=None):
        org = org or self.org
        flow = Flow.create(org,
                           self.admin,
                           name,
                           flow_type=flow_type,
                           is_system=is_system)
        if not nodes:
            nodes = [{
                "uuid":
                "f3d5ccd0-fee0-4955-bcb7-21613f049eae",
                "actions": [{
                    "uuid": "f661e3f0-5148-4397-92ef-925629ad444d",
                    "type": "send_msg",
                    "text": "Hey everybody!"
                }],
                "exits": [{
                    "uuid": "72a3f1da-bde1-4549-a986-d35809807be8"
                }],
            }]
        definition = {
            "uuid": str(uuid4()),
            "name": name,
            "type": Flow.GOFLOW_TYPES[flow_type],
            "revision": 1,
            "spec_version": "13.1.0",
            "expire_after_minutes": Flow.DEFAULT_EXPIRES_AFTER,
            "language": "eng",
            "nodes": nodes,
        }

        flow.version_number = definition["spec_version"]
        flow.save()

        json_flow = Flow.migrate_definition(definition, flow)
        flow.save_revision(self.admin, json_flow)

        return flow
Example #15
0
    def form_valid(self, form):
        org = self.request.user.get_org()
        data = form.cleaned_data

        country = data["country"]
        url = data["url"]
        number = data["number"]
        role = Channel.ROLE_SEND + Channel.ROLE_RECEIVE

        config = {
            Channel.CONFIG_SEND_URL: url,
            Channel.CONFIG_VERIFY_SSL: data.get("verify_ssl", False),
            Channel.CONFIG_USE_NATIONAL: data.get("use_national", False),
            Channel.CONFIG_USERNAME: data.get("username", None),
            Channel.CONFIG_PASSWORD: data.get("password", None),
            Channel.CONFIG_ENCODING: data.get("encoding",
                                              Channel.ENCODING_DEFAULT),
            Channel.CONFIG_CALLBACK_DOMAIN: org.get_brand_domain(),
        }
        self.object = Channel.add_config_external_channel(org,
                                                          self.request.user,
                                                          country,
                                                          number,
                                                          "KN",
                                                          config,
                                                          role=role,
                                                          parent=None)

        # if they didn't set a username or password, generate them, we do this after the addition above
        # because we use the channel id in the configuration
        config = self.object.config
        if not config.get(Channel.CONFIG_USERNAME, None):
            config[Channel.CONFIG_USERNAME] = "%s_%d" % (
                self.request.branding["name"].lower(), self.object.pk)

        if not config.get(Channel.CONFIG_PASSWORD, None):
            config[Channel.CONFIG_PASSWORD] = str(uuid4())

        self.object.config = config
        self.object.save()

        return super().form_valid(form)
Example #16
0
    def visit(self, node, exit_index=None):
        if self.current_node:
            from_exit = None
            if exit_index:
                from_exit = self.current_node["exits"][exit_index]
            else:
                # use first exit that points to this destination
                for e in self.current_node["exits"]:
                    if e.get("destination_uuid") == node["uuid"]:
                        from_exit = e
                        break

            assert from_exit, f"previous node {self.current_node['uuid']} has no exit to new node {node['uuid']}"

            self.current_run["path"][-1]["exit_uuid"] = from_exit["uuid"]

        self.current_run["path"].append({"uuid": str(uuid4()), "node_uuid": node["uuid"], "arrived_on": self._now()})
        self.current_run["modified_on"] = self._now()
        self.current_node = node
        return self
Example #17
0
def populate_session_uuids(apps, schema_editor):  # pragma: no cover
    FlowSession = apps.get_model("flows", "FlowSession")

    num_updated = 0
    max_id = -1
    while True:
        batch = list(
            FlowSession.objects.filter(uuid=None, id__gt=max_id).only(
                "id", "uuid").order_by("id")[:BATCH_SIZE])
        if not batch:
            break

        with transaction.atomic():
            for session in batch:
                session.uuid = str(uuid4())
                session.save(update_fields=("uuid", ))

        num_updated += len(batch)
        print(f" > Updated {num_updated} flow sessions with a UUID")

        max_id = batch[-1].id
Example #18
0
    def test_iter_records(self):
        archive = Archive.objects.create(
            org=self.org,
            archive_type=Archive.TYPE_FLOWRUN,
            size=10,
            hash=uuid4().hex,
            url=f"http://s3-bucket.aws.com/my/32562662.jsonl.gz",
            record_count=2,
            start_date=timezone.now(),
            period="D",
            build_time=23425,
        )

        mock_s3 = MockS3Client()
        mock_s3.put_jsonl("s3-bucket", "my/32562662.jsonl.gz", [{"id": 1}, {"id": 2}, {"id": 3}])

        with patch("temba.archives.models.Archive.s3_client", return_value=mock_s3):
            records_iter = archive.iter_records()

            self.assertEqual(next(records_iter), {"id": 1})
            self.assertEqual(next(records_iter), {"id": 2})
            self.assertEqual(next(records_iter), {"id": 3})
            self.assertRaises(StopIteration, next, records_iter)
Example #19
0
 def __init__(self, uuid):
     self.uuid = uuid if uuid else str(uuid4())
Example #20
0
    def claim_number(self, user, phone_number, country, role):
        org = user.get_org()

        client = org.get_twilio_client()
        twilio_phones = client.api.incoming_phone_numbers.stream(
            phone_number=phone_number)
        channel_uuid = uuid4()

        # create new TwiML app
        callback_domain = org.get_brand_domain()
        base_url = "https://" + callback_domain
        receive_url = base_url + reverse("courier.t",
                                         args=[channel_uuid, "receive"])
        status_url = base_url + reverse("mailroom.ivr_handler",
                                        args=[channel_uuid, "status"])
        voice_url = base_url + reverse("mailroom.ivr_handler",
                                       args=[channel_uuid, "incoming"])

        new_app = client.api.applications.create(
            friendly_name="%s/%s" % (callback_domain.lower(), channel_uuid),
            sms_method="POST",
            sms_url=receive_url,
            voice_method="POST",
            voice_url=voice_url,
            status_callback_method="POST",
            status_callback=status_url,
            voice_fallback_method="GET",
            voice_fallback_url=f"{settings.STORAGE_URL}/voice_unavailable.xml",
        )

        is_short_code = len(phone_number) <= 6
        if is_short_code:
            short_codes = client.api.short_codes.stream(
                short_code=phone_number)
            short_code = next(short_codes, None)

            if short_code:
                number_sid = short_code.sid
                app_url = "https://" + callback_domain + "%s" % reverse(
                    "courier.t", args=[channel_uuid, "receive"])
                client.api.short_codes.get(number_sid).update(
                    sms_url=app_url, sms_method="POST")

                role = Channel.ROLE_SEND + Channel.ROLE_RECEIVE
                phone = phone_number

            else:  # pragma: no cover
                raise Exception(
                    _("Short code not found on your Twilio Account. "
                      "Please check you own the short code and Try again"))
        else:
            twilio_phone = next(twilio_phones, None)
            if twilio_phone:

                client.api.incoming_phone_numbers.get(twilio_phone.sid).update(
                    voice_application_sid=new_app.sid,
                    sms_application_sid=new_app.sid)

            else:  # pragma: needs cover
                twilio_phone = client.api.incoming_phone_numbers.create(
                    phone_number=phone_number,
                    voice_application_sid=new_app.sid,
                    sms_application_sid=new_app.sid)

            phone = phonenumbers.format_number(
                phonenumbers.parse(phone_number, None),
                phonenumbers.PhoneNumberFormat.NATIONAL)

            number_sid = twilio_phone.sid

        org_config = org.config
        config = {
            Channel.CONFIG_APPLICATION_SID: new_app.sid,
            Channel.CONFIG_NUMBER_SID: number_sid,
            Channel.CONFIG_ACCOUNT_SID: org_config[Org.CONFIG_TWILIO_SID],
            Channel.CONFIG_AUTH_TOKEN: org_config[Org.CONFIG_TWILIO_TOKEN],
            Channel.CONFIG_CALLBACK_DOMAIN: callback_domain,
        }

        channel = Channel.create(org,
                                 user,
                                 country,
                                 "T",
                                 name=phone,
                                 address=phone_number,
                                 role=role,
                                 config=config,
                                 uuid=channel_uuid)

        analytics.track(user.username,
                        "temba.channel_claim_twilio",
                        properties=dict(number=phone_number))

        return channel
Example #21
0
 def generate_key(self):
     unique = uuid4()
     return hmac.new(unique.bytes, digestmod=sha1).hexdigest()
Example #22
0
def generate_uuid():
    """
    Returns a random stringified UUID for use with older models that use char fields instead of UUID fields
    """
    return str(uuid.uuid4())