def test_create_complaint_callback_data(
    notify_db,
    notify_db_session,
    sample_email_template,
):
    notification = create_sample_notification(
        notify_db,
        notify_db_session,
        template=sample_email_template,
        status="delivered",
        sent_at=datetime.utcnow(),
    )
    complaint = create_complaint(notification=notification,
                                 service=notification.service)
    callback_api = create_service_callback_api(
        service=sample_email_template.service, url="https://original_url.com")

    assert encryption.decrypt(
        create_complaint_callback_data(
            complaint, notification, callback_api,
            "*****@*****.**")) == {
                "complaint_id": str(complaint.id),
                "notification_id": str(notification.id),
                "reference": notification.client_reference,
                "to": "*****@*****.**",
                "complaint_date":
                complaint.complaint_date.strftime(DATETIME_FORMAT),
                "service_callback_api_url": callback_api.url,
                "service_callback_api_bearer_token": callback_api.bearer_token,
            }
def test_save_service_inbound_api(sample_service):
    service_inbound_api = ServiceInboundApi(
        service_id=sample_service.id,
        url="https://some_service/inbound_messages",
        bearer_token="some_unique_string",
        updated_by_id=sample_service.users[0].id
    )

    save_service_inbound_api(service_inbound_api)

    results = ServiceInboundApi.query.all()
    assert len(results) == 1
    inbound_api = results[0]
    assert inbound_api.id is not None
    assert inbound_api.service_id == sample_service.id
    assert inbound_api.updated_by_id == sample_service.users[0].id
    assert inbound_api.url == "https://some_service/inbound_messages"
    assert inbound_api.bearer_token == "some_unique_string"
    assert inbound_api._bearer_token != "some_unique_string"
    assert inbound_api.updated_at is None

    versioned = ServiceInboundApi.get_history_model().query.filter_by(id=inbound_api.id).one()
    assert versioned.id == inbound_api.id
    assert versioned.service_id == sample_service.id
    assert versioned.updated_by_id == sample_service.users[0].id
    assert versioned.url == "https://some_service/inbound_messages"
    assert encryption.decrypt(versioned._bearer_token) == "some_unique_string"
    assert versioned.updated_at is None
    assert versioned.version == 1
Example #3
0
def test_ses_callback_should_send_on_complaint_to_user_callback_api(
        sample_email_template, mocker):
    send_mock = mocker.patch(
        "app.celery.service_callback_tasks.send_complaint_to_service.apply_async"
    )
    create_service_callback_api(
        service=sample_email_template.service,
        url="https://original_url.com",
        callback_type="complaint",
    )

    notification = create_notification(
        template=sample_email_template,
        reference="ref1",
        sent_at=datetime.utcnow(),
        status="sending",
    )
    response = ses_complaint_callback()
    assert process_ses_results(response)

    assert send_mock.call_count == 1
    assert encryption.decrypt(send_mock.call_args[0][0][0]) == {
        "complaint_date": "2018-06-05T13:59:58.000000Z",
        "complaint_id": str(Complaint.query.one().id),
        "notification_id": str(notification.id),
        "reference": None,
        "service_callback_api_bearer_token": "some_super_secret",
        "service_callback_api_url": "https://original_url.com",
        "to": "*****@*****.**",
    }
Example #4
0
def send_delivery_status_to_service(
    self, notification_id, encrypted_status_update
):
    status_update = encryption.decrypt(encrypted_status_update)

    data = {
        "id": str(notification_id),
        "reference": status_update['notification_client_reference'],
        "to": status_update['notification_to'],
        "status": status_update['notification_status'],
        "created_at": status_update['notification_created_at'],
        "completed_at": status_update['notification_updated_at'],
        "sent_at": status_update['notification_sent_at'],
        "notification_type": status_update['notification_type'],
        "template_id": status_update['template_id'],
        "template_version": status_update['template_version']
    }

    _send_data_to_service_callback_api(
        self,
        data,
        status_update['service_callback_api_url'],
        status_update['service_callback_api_bearer_token'],
        'send_delivery_status_to_service'
    )
Example #5
0
def test_save_service_callback_api(sample_service):
    notification_statuses = [NOTIFICATION_FAILED]
    service_callback_api = ServiceCallbackApi(  # nosec
        service_id=sample_service.id,
        url="https://some_service/callback_endpoint",
        bearer_token="some_unique_string",
        updated_by_id=sample_service.users[0].id,
        notification_statuses=notification_statuses
    )

    save_service_callback_api(service_callback_api)

    results = ServiceCallbackApi.query.all()
    assert len(results) == 1
    callback_api = results[0]
    assert callback_api.id is not None
    assert callback_api.service_id == sample_service.id
    assert callback_api.updated_by_id == sample_service.users[0].id
    assert callback_api.url == "https://some_service/callback_endpoint"
    assert callback_api.bearer_token == "some_unique_string"
    assert callback_api._bearer_token != "some_unique_string"
    assert callback_api.updated_at is None
    assert callback_api.notification_statuses == notification_statuses

    versioned = ServiceCallbackApi.get_history_model().query.filter_by(id=callback_api.id).one()
    assert versioned.id == callback_api.id
    assert versioned.service_id == sample_service.id
    assert versioned.updated_by_id == sample_service.users[0].id
    assert versioned.url == "https://some_service/callback_endpoint"
    assert encryption.decrypt(versioned._bearer_token) == "some_unique_string"
    assert versioned.updated_at is None
    assert versioned.version == 1
Example #6
0
def test_ses_callback_should_send_on_complaint_to_user_callback_api(
        sample_email_template, mocker):
    send_mock = mocker.patch(
        'app.celery.service_callback_tasks.send_complaint_to_service.apply_async'
    )
    create_service_callback_api(service=sample_email_template.service,
                                url="https://original_url.com",
                                callback_type="complaint")

    notification = create_notification(template=sample_email_template,
                                       reference='ref1',
                                       sent_at=datetime.utcnow(),
                                       status='sending')
    response = ses_complaint_callback()
    assert process_ses_results(response)

    assert send_mock.call_count == 1
    assert encryption.decrypt(send_mock.call_args[0][0][0]) == {
        'complaint_date': '2018-06-05T13:59:58.000000Z',
        'complaint_id': str(Complaint.query.one().id),
        'notification_id': str(notification.id),
        'reference': None,
        'service_callback_api_bearer_token': 'some_super_secret',
        'service_callback_api_url': 'https://original_url.com',
        'to': '*****@*****.**'
    }
Example #7
0
def save_letter(
    self,
    service_id,
    notification_id,
    encrypted_notification,
):
    notification = encryption.decrypt(encrypted_notification)

    postal_address = PostalAddress.from_personalisation(
        Columns(notification['personalisation']))

    service = SerialisedService.from_id(service_id)
    template = SerialisedTemplate.from_id_and_service_id(
        notification['template'],
        service_id=service.id,
        version=notification['template_version'],
    )

    try:
        # if we don't want to actually send the letter, then start it off in SENDING so we don't pick it up
        status = NOTIFICATION_CREATED if not service.research_mode else NOTIFICATION_SENDING

        saved_notification = persist_notification(
            template_id=notification['template'],
            template_version=notification['template_version'],
            postage=postal_address.postage
            if postal_address.international else template.postage,
            recipient=postal_address.normalised,
            service=service,
            personalisation=notification['personalisation'],
            notification_type=LETTER_TYPE,
            api_key_id=None,
            key_type=KEY_TYPE_NORMAL,
            created_at=datetime.utcnow(),
            job_id=notification['job'],
            job_row_number=notification['row_number'],
            notification_id=notification_id,
            reference=create_random_identifier(),
            reply_to_text=template.reply_to_text,
            status=status)

        if not service.research_mode:
            letters_pdf_tasks.get_pdf_for_templated_letter.apply_async(
                [str(saved_notification.id)],
                queue=QueueNames.CREATE_LETTERS_PDF)
        elif current_app.config['NOTIFY_ENVIRONMENT'] in [
                'preview', 'development'
        ]:
            research_mode_tasks.create_fake_letter_response_file.apply_async(
                (saved_notification.reference, ),
                queue=QueueNames.RESEARCH_MODE)
        else:
            update_notification_status_by_reference(
                saved_notification.reference, 'delivered')

        current_app.logger.debug("Letter {} created at {}".format(
            saved_notification.id, saved_notification.created_at))
    except SQLAlchemyError as e:
        handle_exception(self, notification, notification_id, e)
Example #8
0
def save_sms(self,
             service_id,
             notification_id,
             encrypted_notification,
             sender_id=None):
    notification = encryption.decrypt(encrypted_notification)
    service = dao_fetch_service_by_id(service_id)
    template = dao_get_template_by_id(notification["template"],
                                      version=notification["template_version"])

    if sender_id:
        reply_to_text = dao_get_service_sms_senders_by_id(
            service_id, sender_id).sms_sender
    else:
        reply_to_text = template.get_reply_to_text()

    if not service_allowed_to_send_to(notification["to"], service,
                                      KEY_TYPE_NORMAL):
        current_app.logger.debug(
            "SMS {} failed as restricted service".format(notification_id))
        return

    check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service)

    try:
        # this task is used by two main things... process_job and process_sms_or_email_notification
        # if the data is not present in the encrypted data then fallback on whats needed for process_job
        saved_notification = persist_notification(
            notification_id=notification.get("id", notification_id),
            template_id=notification["template"],
            template_version=notification["template_version"],
            recipient=notification["to"],
            service=service,
            personalisation=notification.get("personalisation"),
            notification_type=SMS_TYPE,
            simulated=notification.get("simulated", None),
            api_key_id=notification.get("api_key", None),
            key_type=notification.get("key_type", KEY_TYPE_NORMAL),
            created_at=datetime.utcnow(),
            job_id=notification.get("job", None),
            job_row_number=notification.get("row_number", None),
            reply_to_text=reply_to_text,
        )

        send_notification_to_queue(
            saved_notification,
            service.research_mode,
            queue=notification.get("queue") or template.queue_to_use(),
        )

        current_app.logger.debug("SMS {} created at {} for job {}".format(
            saved_notification.id,
            saved_notification.created_at,
            notification.get("job", None),
        ))

    except SQLAlchemyError as e:
        handle_exception(self, notification, notification_id, e)
Example #9
0
def save_letter(
    self,
    service_id,
    notification_id,
    encrypted_notification,
):
    notification = encryption.decrypt(encrypted_notification)

    # we store the recipient as just the first item of the person's address
    recipient = notification["personalisation"]["addressline1"]

    service = dao_fetch_service_by_id(service_id)
    template = dao_get_template_by_id(notification["template"],
                                      version=notification["template_version"])

    check_service_over_daily_message_limit(KEY_TYPE_NORMAL, service)

    try:
        # if we don't want to actually send the letter, then start it off in SENDING so we don't pick it up
        status = NOTIFICATION_CREATED if not service.research_mode else NOTIFICATION_SENDING

        saved_notification = persist_notification(
            template_id=notification["template"],
            template_version=notification["template_version"],
            template_postage=template.postage,
            recipient=recipient,
            service=service,
            personalisation=notification["personalisation"],
            notification_type=LETTER_TYPE,
            api_key_id=notification.get("api_key", None),
            key_type=KEY_TYPE_NORMAL,
            created_at=datetime.utcnow(),
            job_id=notification["job"],
            job_row_number=notification["row_number"],
            notification_id=notification_id,
            reference=create_random_identifier(),
            reply_to_text=template.get_reply_to_text(),
            status=status,
        )

        if not service.research_mode:
            send_notification_to_queue(saved_notification,
                                       service.research_mode)
        elif current_app.config["NOTIFY_ENVIRONMENT"] in [
                "preview", "development"
        ]:
            research_mode_tasks.create_fake_letter_response_file.apply_async(
                (saved_notification.reference, ),
                queue=QueueNames.RESEARCH_MODE)
        else:
            update_notification_status_by_reference(
                saved_notification.reference, "delivered")

        current_app.logger.debug("Letter {} created at {}".format(
            saved_notification.id, saved_notification.created_at))
    except SQLAlchemyError as e:
        handle_exception(self, notification, notification_id, e)
Example #10
0
def save_sms(self,
             service_id,
             notification_id,
             encrypted_notification,
             sender_id=None):
    notification = encryption.decrypt(encrypted_notification)
    service = SerialisedService.from_id(service_id)
    template = SerialisedTemplate.from_id_and_service_id(
        notification['template'],
        service_id=service.id,
        version=notification['template_version'],
    )

    if sender_id:
        reply_to_text = dao_get_service_sms_senders_by_id(service_id, sender_id).sms_sender
    else:
        reply_to_text = template.reply_to_text

    if not service_allowed_to_send_to(notification['to'], service, KEY_TYPE_NORMAL):
        current_app.logger.debug(
            "SMS {} failed as restricted service".format(notification_id)
        )
        return

    try:
        saved_notification = persist_notification(
            template_id=notification['template'],
            template_version=notification['template_version'],
            recipient=notification['to'],
            service=service,
            personalisation=notification.get('personalisation'),
            notification_type=SMS_TYPE,
            api_key_id=None,
            key_type=KEY_TYPE_NORMAL,
            created_at=datetime.utcnow(),
            job_id=notification.get('job', None),
            job_row_number=notification.get('row_number', None),
            notification_id=notification_id,
            reply_to_text=reply_to_text
        )

        provider_tasks.deliver_sms.apply_async(
            [str(saved_notification.id)],
            queue=QueueNames.SEND_SMS if not service.research_mode else QueueNames.RESEARCH_MODE
        )

        current_app.logger.debug(
            "SMS {} created at {} for job {}".format(
                saved_notification.id,
                saved_notification.created_at,
                notification.get('job', None))
        )

    except SQLAlchemyError as e:
        handle_exception(self, notification, notification_id, e)
Example #11
0
def save_letter(
    self,
    service_id,
    notification_id,
    encrypted_notification,
):
    notification = encryption.decrypt(encrypted_notification)

    # we store the recipient as just the first item of the person's address
    recipient = notification['personalisation']['addressline1']

    service = dao_fetch_service_by_id(service_id)
    template = dao_get_template_by_id(notification['template'],
                                      version=notification['template_version'])

    try:
        # if we don't want to actually send the letter, then start it off in SENDING so we don't pick it up
        status = NOTIFICATION_CREATED if not service.research_mode else NOTIFICATION_SENDING

        saved_notification = persist_notification(
            template_id=notification['template'],
            template_version=notification['template_version'],
            recipient=recipient,
            service=service,
            personalisation=notification['personalisation'],
            notification_type=LETTER_TYPE,
            api_key_id=None,
            key_type=KEY_TYPE_NORMAL,
            created_at=datetime.utcnow(),
            job_id=notification['job'],
            job_row_number=notification['row_number'],
            notification_id=notification_id,
            reference=create_random_identifier(),
            reply_to_text=template.get_reply_to_text(),
            status=status)

        if not service.research_mode:
            letters_pdf_tasks.create_letters_pdf.apply_async(
                [str(saved_notification.id)],
                queue=QueueNames.CREATE_LETTERS_PDF)
        elif current_app.config['NOTIFY_ENVIRONMENT'] in [
                'preview', 'development'
        ]:
            research_mode_tasks.create_fake_letter_response_file.apply_async(
                (saved_notification.reference, ),
                queue=QueueNames.RESEARCH_MODE)
        else:
            update_notification_status_by_reference(
                saved_notification.reference, 'delivered')

        current_app.logger.debug("Letter {} created at {}".format(
            saved_notification.id, saved_notification.created_at))
    except SQLAlchemyError as e:
        handle_exception(self, notification, notification_id, e)
Example #12
0
def send_sms(
    self, service_id, notification_id, encrypted_notification, created_at, api_key_id=None, key_type=KEY_TYPE_NORMAL
):
    notification = encryption.decrypt(encrypted_notification)
    service = dao_fetch_service_by_id(service_id)

    if not service_allowed_to_send_to(notification["to"], service, key_type):
        current_app.logger.info("SMS {} failed as restricted service".format(notification_id))
        return

    try:
        saved_notification = persist_notification(
            template_id=notification["template"],
            template_version=notification["template_version"],
            recipient=notification["to"],
            service_id=service.id,
            personalisation=notification.get("personalisation"),
            notification_type=SMS_TYPE,
            api_key_id=api_key_id,
            key_type=key_type,
            created_at=created_at,
            job_id=notification.get("job", None),
            job_row_number=notification.get("row_number", None),
            notification_id=notification_id,
        )

        provider_tasks.deliver_sms.apply_async(
            [str(saved_notification.id)], queue="send-sms" if not service.research_mode else "research-mode"
        )

        current_app.logger.info(
            "SMS {} created at {} for job {}".format(saved_notification.id, created_at, notification.get("job", None))
        )

    except SQLAlchemyError as e:
        if not get_notification_by_id(notification_id):
            # Sometimes, SQS plays the same message twice. We should be able to catch an IntegrityError, but it seems
            # SQLAlchemy is throwing a FlushError. So we check if the notification id already exists then do not
            # send to the retry queue.
            current_app.logger.error(
                "RETRY: send_sms notification for job {} row number {} and notification id {}".format(
                    notification.get("job", None), notification.get("row_number", None), notification_id
                )
            )
            current_app.logger.exception(e)
            try:
                raise self.retry(queue="retry", exc=e)
            except self.MaxRetriesExceededError:
                current_app.logger.error(
                    "RETRY FAILED: send_sms notification for job {} row number {} and notification id {}".format(
                        notification.get("job", None), notification.get("row_number", None), notification_id
                    )
                )
                current_app.logger.exception(e)
Example #13
0
def send_complaint_to_service(self, complaint_data):
    complaint = encryption.decrypt(complaint_data)

    data = {
        "notification_id": complaint['notification_id'],
        "complaint_id": complaint['complaint_id'],
        "reference": complaint['reference'],
        "to": complaint['to'],
        "complaint_date": complaint['complaint_date']
    }

    _send_data_to_service_callback_api(
        self, data, complaint['service_callback_api_url'],
        complaint['service_callback_api_bearer_token'],
        'send_complaint_to_service')
Example #14
0
def save_sms(self,
             service_id,
             notification_id,
             encrypted_notification,
             sender_id=None):
    notification = encryption.decrypt(encrypted_notification)
    service = dao_fetch_service_by_id(service_id)
    template = dao_get_template_by_id(notification['template'],
                                      version=notification['template_version'])

    if sender_id:
        reply_to_text = dao_get_service_sms_senders_by_id(
            service_id, sender_id).sms_sender
    else:
        reply_to_text = template.get_reply_to_text()

    if not service_allowed_to_send_to(notification['to'], service,
                                      KEY_TYPE_NORMAL):
        current_app.logger.debug(
            "SMS {} failed as restricted service".format(notification_id))
        return

    try:
        saved_notification = persist_notification(
            template_id=notification['template'],
            template_version=notification['template_version'],
            recipient=notification['to'],
            service=service,
            personalisation=notification.get('personalisation'),
            notification_type=SMS_TYPE,
            api_key_id=None,
            key_type=KEY_TYPE_NORMAL,
            created_at=datetime.utcnow(),
            job_id=notification.get('job', None),
            job_row_number=notification.get('row_number', None),
            notification_id=notification_id,
            reply_to_text=reply_to_text)

        send_notification_to_queue(saved_notification, service.research_mode)

        current_app.logger.debug("SMS {} created at {} for job {}".format(
            saved_notification.id, saved_notification.created_at,
            notification.get('job', None)))

    except SQLAlchemyError as e:
        handle_exception(self, notification, notification_id, e)
def test_update_service_callback_api(sample_service):
    service_callback_api = ServiceCallbackApi(
        service_id=sample_service.id,
        url="https://some_service/callback_endpoint",
        bearer_token="some_unique_string",
        updated_by_id=sample_service.users[0].id,
    )

    save_service_callback_api(service_callback_api)
    results = ServiceCallbackApi.query.all()
    assert len(results) == 1
    saved_callback_api = results[0]

    reset_service_callback_api(
        saved_callback_api,
        updated_by_id=sample_service.users[0].id,
        url="https://some_service/changed_url",
    )
    updated_results = ServiceCallbackApi.query.all()
    assert len(updated_results) == 1
    updated = updated_results[0]
    assert updated.id is not None
    assert updated.service_id == sample_service.id
    assert updated.updated_by_id == sample_service.users[0].id
    assert updated.url == "https://some_service/changed_url"
    assert updated.bearer_token == "some_unique_string"
    assert updated._bearer_token != "some_unique_string"
    assert updated.updated_at is not None

    versioned_results = ServiceCallbackApi.get_history_model().query.filter_by(
        id=saved_callback_api.id).all()
    assert len(versioned_results) == 2
    for x in versioned_results:
        if x.version == 1:
            assert x.url == "https://some_service/callback_endpoint"
            assert not x.updated_at
        elif x.version == 2:
            assert x.url == "https://some_service/changed_url"
            assert x.updated_at
        else:
            pytest.fail("version should not exist")
        assert x.id is not None
        assert x.service_id == sample_service.id
        assert x.updated_by_id == sample_service.users[0].id
        assert encryption.decrypt(x._bearer_token) == "some_unique_string"
Example #16
0
def send_delivery_status_to_service(self, notification_id,
                                    encrypted_status_update):
    try:
        status_update = encryption.decrypt(encrypted_status_update)

        data = {
            "id": str(notification_id),
            "reference": status_update['notification_client_reference'],
            "to": status_update['notification_to'],
            "status": status_update['notification_status'],
            "created_at": status_update['notification_created_at'],
            "completed_at": status_update['notification_updated_at'],
            "sent_at": status_update['notification_sent_at'],
            "notification_type": status_update['notification_type']
        }

        response = request(
            method="POST",
            url=status_update['service_callback_api_url'],
            data=json.dumps(data),
            headers={
                'Content-Type':
                'application/json',
                'Authorization':
                'Bearer {}'.format(
                    status_update['service_callback_api_bearer_token'])
            },
            timeout=60)
        current_app.logger.info(
            'send_delivery_status_to_service sending {} to {}, response {}'.
            format(notification_id, status_update['service_callback_api_url'],
                   response.status_code))
        response.raise_for_status()
    except RequestException as e:
        current_app.logger.warning(
            "send_delivery_status_to_service request failed for notification_id: {} and url: {}. exc: {}"
            .format(notification_id, status_update['service_callback_api_url'],
                    e))
        if not isinstance(e, HTTPError) or e.response.status_code >= 500:
            try:
                self.retry(queue=QueueNames.RETRY)
            except self.MaxRetriesExceededError:
                current_app.logger.exception(
                    """Retry: send_delivery_status_to_service has retried the max num of times
                     for notification: {}""".format(notification_id))
Example #17
0
def send_complaint_to_service(self, complaint_data):
    complaint = encryption.decrypt(complaint_data)

    data = {
        "notification_id": complaint["notification_id"],
        "complaint_id": complaint["complaint_id"],
        "reference": complaint["reference"],
        "to": complaint["to"],
        "complaint_date": complaint["complaint_date"],
    }

    _send_data_to_service_callback_api(
        self,
        data,
        complaint["service_callback_api_url"],
        complaint["service_callback_api_bearer_token"],
        "send_complaint_to_service",
    )
Example #18
0
def save_api_email_or_sms(self, encrypted_notification):
    notification = encryption.decrypt(encrypted_notification)
    service = SerialisedService.from_id(notification['service_id'])
    q = QueueNames.SEND_EMAIL if notification[
        'notification_type'] == EMAIL_TYPE else QueueNames.SEND_SMS
    provider_task = provider_tasks.deliver_email if notification['notification_type'] == EMAIL_TYPE \
        else provider_tasks.deliver_sms
    try:

        persist_notification(
            notification_id=notification["id"],
            template_id=notification['template_id'],
            template_version=notification['template_version'],
            recipient=notification['to'],
            service=service,
            personalisation=notification.get('personalisation'),
            notification_type=notification['notification_type'],
            client_reference=notification['client_reference'],
            api_key_id=notification.get('api_key_id'),
            key_type=KEY_TYPE_NORMAL,
            created_at=notification['created_at'],
            reply_to_text=notification['reply_to_text'],
            status=notification['status'],
            document_download_count=notification['document_download_count'])

        q = q if not service.research_mode else QueueNames.RESEARCH_MODE
        provider_task.apply_async([notification['id']], queue=q)
        current_app.logger.debug(
            f"{notification['notification_type']} {notification['id']} has been persisted and sent to delivery queue."
        )
    except IntegrityError:
        current_app.logger.info(
            f"{notification['notification_type']} {notification['id']} already exists."
        )

    except SQLAlchemyError:

        try:
            self.retry(queue=QueueNames.RETRY)
        except self.MaxRetriesExceededError:
            current_app.logger.error(
                f"Max retry failed Failed to persist notification {notification['id']}"
            )
Example #19
0
def save_email(self,
               service_id,
               notification_id,
               encrypted_notification,
               api_key_id=None,
               key_type=KEY_TYPE_NORMAL):
    notification = encryption.decrypt(encrypted_notification)

    service = dao_fetch_service_by_id(service_id)
    template = dao_get_template_by_id(notification['template'],
                                      version=notification['template_version'])

    if not service_allowed_to_send_to(notification['to'], service, key_type):
        current_app.logger.info(
            "Email {} failed as restricted service".format(notification_id))
        return

    try:
        saved_notification = persist_notification(
            template_id=notification['template'],
            template_version=notification['template_version'],
            recipient=notification['to'],
            service=service,
            personalisation=notification.get('personalisation'),
            notification_type=EMAIL_TYPE,
            api_key_id=api_key_id,
            key_type=key_type,
            created_at=datetime.utcnow(),
            job_id=notification.get('job', None),
            job_row_number=notification.get('row_number', None),
            notification_id=notification_id,
            reply_to_text=template.get_reply_to_text())

        provider_tasks.deliver_email.apply_async(
            [str(saved_notification.id)],
            queue=QueueNames.SEND_EMAIL
            if not service.research_mode else QueueNames.RESEARCH_MODE)

        current_app.logger.debug("Email {} created at {}".format(
            saved_notification.id, saved_notification.created_at))
    except SQLAlchemyError as e:
        handle_exception(self, notification, notification_id, e)
Example #20
0
def send_delivery_status_to_service(self, notification_id,
                                    encrypted_status_update):
    status_update = encryption.decrypt(encrypted_status_update)

    payload = {
        "id": str(notification_id),
        "reference": status_update['notification_client_reference'],
        "to": status_update['notification_to'],
        "status": status_update['notification_status'],
        "created_at": status_update['notification_created_at'],
        "completed_at": status_update['notification_updated_at'],
        "sent_at": status_update['notification_sent_at'],
        "notification_type": status_update['notification_type']
    }
    _send_to_service_callback_api(
        self,
        payload,
        status_update['service_callback_api_url'],
        status_update['service_callback_api_bearer_token'],
        logging_tags={'notification_id': str(notification_id)})
Example #21
0
def send_complaint_to_service(self, complaint_data):
    complaint = encryption.decrypt(complaint_data)

    payload = {
        "notification_id": complaint['notification_id'],
        "complaint_id": complaint['complaint_id'],
        "reference": complaint['reference'],
        "to": complaint['to'],
        "complaint_date": complaint['complaint_date']
    }

    _send_to_service_callback_api(
        self,
        payload,
        complaint['service_callback_api_url'],
        complaint['service_callback_api_bearer_token'],
        logging_tags={
            'notification_id': complaint['notification_id'],
            'complaint_id': complaint['complaint_id']
        })
Example #22
0
def send_delivery_status_to_service(self, notification_id, encrypted_status_update):
    status_update = encryption.decrypt(encrypted_status_update)

    data = {
        "id": str(notification_id),
        "reference": status_update["notification_client_reference"],
        "to": status_update["notification_to"],
        "status": status_update["notification_status"],
        "provider_response": status_update["notification_provider_response"],
        "created_at": status_update["notification_created_at"],
        "completed_at": status_update["notification_updated_at"],
        "sent_at": status_update["notification_sent_at"],
        "notification_type": status_update["notification_type"],
    }
    _send_data_to_service_callback_api(
        self,
        data,
        status_update["service_callback_api_url"],
        status_update["service_callback_api_bearer_token"],
        "send_delivery_status_to_service",
    )
def test_create_delivery_status_callback_data(
    notify_db,
    notify_db_session,
    sample_email_template,
):
    notification = create_sample_notification(
        notify_db,
        notify_db_session,
        template=sample_email_template,
        status="sending",
        sent_at=datetime.utcnow(),
    )
    callback_api = create_service_callback_api(
        service=sample_email_template.service, url="https://original_url.com")

    assert encryption.decrypt(
        create_delivery_status_callback_data(notification, callback_api)) == {
            "notification_client_reference":
            notification.client_reference,
            "notification_created_at":
            notification.created_at.strftime(DATETIME_FORMAT),
            "notification_id":
            str(notification.id),
            "notification_provider_response":
            notification.provider_response,
            "notification_sent_at":
            notification.sent_at.strftime(DATETIME_FORMAT),
            "notification_status":
            notification.status,
            "notification_to":
            notification.to,
            "notification_type":
            notification.notification_type,
            "notification_updated_at":
            notification.updated_at,
            "service_callback_api_bearer_token":
            callback_api.bearer_token,
            "service_callback_api_url":
            callback_api.url,
        }
def process_sanitised_letter(self, sanitise_data):
    letter_details = encryption.decrypt(sanitise_data)

    filename = letter_details['filename']
    notification_id = letter_details['notification_id']

    current_app.logger.info('Processing sanitised letter with id {}'.format(notification_id))
    notification = get_notification_by_id(notification_id, _raise=True)

    if notification.status != NOTIFICATION_PENDING_VIRUS_CHECK:
        current_app.logger.info(
            'process-sanitised-letter task called for notification {} which is in {} state'.format(
                notification.id, notification.status)
        )
        return

    try:
        original_pdf_object = s3.get_s3_object(current_app.config['LETTERS_SCAN_BUCKET_NAME'], filename)

        if letter_details['validation_status'] == 'failed':
            current_app.logger.info('Processing invalid precompiled pdf with id {} (file {})'.format(
                notification_id, filename))

            _move_invalid_letter_and_update_status(
                notification=notification,
                filename=filename,
                scan_pdf_object=original_pdf_object,
                message=letter_details['message'],
                invalid_pages=letter_details['invalid_pages'],
                page_count=letter_details['page_count'],
            )
            return

        current_app.logger.info('Processing valid precompiled pdf with id {} (file {})'.format(
            notification_id, filename))

        billable_units = get_billable_units_for_letter_page_count(letter_details['page_count'])
        is_test_key = notification.key_type == KEY_TYPE_TEST

        # Updating the notification needs to happen before the file is moved. This is so that if updating the
        # notification fails, the task can retry because the file is in the same place.
        update_letter_pdf_status(
            reference=notification.reference,
            status=NOTIFICATION_DELIVERED if is_test_key else NOTIFICATION_CREATED,
            billable_units=billable_units,
            recipient_address=letter_details['address']
        )

        # The original filename could be wrong because we didn't know the postage.
        # Now we know if the letter is international, we can check what the filename should be.
        upload_file_name = get_letter_pdf_filename(
            reference=notification.reference,
            crown=notification.service.crown,
            created_at=notification.created_at,
            ignore_folder=True,
            postage=notification.postage
        )

        move_sanitised_letter_to_test_or_live_pdf_bucket(
            filename,
            is_test_key,
            notification.created_at,
            upload_file_name,
        )
        # We've moved the sanitised PDF from the sanitise bucket, but still need to delete the original file:
        original_pdf_object.delete()

    except BotoClientError:
        # Boto exceptions are likely to be caused by the file(s) being in the wrong place, so retrying won't help -
        # we'll need to manually investigate
        current_app.logger.exception(
            f"Boto error when processing sanitised letter for notification {notification.id} (file {filename})"
        )
        update_notification_status_by_id(notification.id, NOTIFICATION_TECHNICAL_FAILURE)
        raise NotificationTechnicalFailureException
    except Exception:
        try:
            current_app.logger.exception(
                "RETRY: calling process_sanitised_letter task for notification {} failed".format(notification.id)
            )
            self.retry(queue=QueueNames.RETRY)
        except self.MaxRetriesExceededError:
            message = "RETRY FAILED: Max retries reached. " \
                      "The task process_sanitised_letter failed for notification {}. " \
                      "Notification has been updated to technical-failure".format(notification.id)
            update_notification_status_by_id(notification.id, NOTIFICATION_TECHNICAL_FAILURE)
            raise NotificationTechnicalFailureException(message)
Example #25
0
def send_delivery_status_to_service(self, notification_id,
                                    encrypted_status_update
                                    ):
    start_time = datetime.utcnow()
    status_update = encryption.decrypt(encrypted_status_update)

    try:
        data = {
            "id": str(notification_id),
            "reference": status_update['notification_client_reference'],
            "to": status_update['notification_to'],
            "status": status_update['notification_status'],
            "created_at": status_update['notification_created_at'],
            "completed_at": status_update['notification_updated_at'],
            "sent_at": status_update['notification_sent_at'],
            "notification_type": status_update['notification_type']
        }

        response = request(
            method="POST",
            url=status_update['service_callback_api_url'],
            data=json.dumps(data),
            headers={
                'Content-Type': 'application/json',
                'Authorization': 'Bearer {}'.format(status_update['service_callback_api_bearer_token'])
            },
            timeout=10
        )
        current_app.logger.info('send_delivery_status_to_service sending {} to {}, response {}'.format(
            notification_id,
            status_update['service_callback_api_url'],
            response.status_code
        ))
        response.raise_for_status()
    except RequestException as e:
        end_time = datetime.utcnow()
        current_app.logger.warning(
            "send_delivery_status_to_service request failed for notification_id: {} and url: {}. exc: {}".format(
                notification_id,
                status_update['service_callback_api_url'],
                e
            )
        )
        record_failed_status_callback.apply_async([], dict(
            notification_id=notification_id,
            service_id=status_update['service_id'],
            service_callback_url=status_update['service_callback_api_url'],
            notification_api_key_id=status_update['notification_api_key_id'],
            notification_api_key_type=status_update['notification_api_key_type'],
            callback_attempt_number=self.request.retries,
            callback_attempt_started=start_time,
            callback_attempt_ended=end_time,
            callback_failure_type=type(e).__name__,
            service_callback_type='send_delivery_status_to_service',
        ), queue=QueueNames.NOTIFY)

        if not isinstance(e, HTTPError) or e.response.status_code >= 500:
            try:
                self.retry(queue=QueueNames.RETRY)
            except self.MaxRetriesExceededError:
                current_app.logger.exception(
                    """Retry: send_delivery_status_to_service has retried the max num of times
                        for notification: {}""".format(notification_id)
                )
Example #26
0
 def personalisation(self):
     if self._personalisation:
         return encryption.decrypt(self._personalisation)
     return None