Example #1
0
def test_should_send_template_to_correct_sms_task_and_persist(sample_template_with_placeholders, mocker):
    notification = _notification_json(sample_template_with_placeholders,
                                      to="+447234123123", personalisation={"name": "Jo"})

    mocked_deliver_sms = mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')

    send_sms(
        sample_template_with_placeholders.service_id,
        uuid.uuid4(),
        encryption.encrypt(notification),
        datetime.utcnow().strftime(DATETIME_FORMAT)
    )

    persisted_notification = Notification.query.one()
    assert persisted_notification.to == '+447234123123'
    assert persisted_notification.template_id == sample_template_with_placeholders.id
    assert persisted_notification.template_version == sample_template_with_placeholders.version
    assert persisted_notification.status == 'created'
    assert persisted_notification.created_at <= datetime.utcnow()
    assert not persisted_notification.sent_at
    assert not persisted_notification.sent_by
    assert not persisted_notification.job_id
    assert persisted_notification.personalisation == {'name': 'Jo'}
    assert persisted_notification._personalisation == encryption.encrypt({"name": "Jo"})
    assert persisted_notification.notification_type == 'sms'
    mocked_deliver_sms.assert_called_once_with(
        [str(persisted_notification.id)],
        queue="send-sms"
    )
 def _ensure_admin(self):
     """
     For development use only. Creates a default admin user
     if one does not yet exist.
     """
     with Session(self._engine, future=True) as session:
         accounts = session.query(User).all()
         if len(accounts):
             log.info("Found an existing account.")
             return
         log.info("Creating a default admin account.")
         # cur_user = session.query(User).filter(User.username == 'admin').one()
         # session.delete(cur_user)
         # session.commit()
         user = User(
             id=str(uuid4()),
             username="******",
             password=encrypt("admin"),
             f_name="tha",
             l_name="admin",
             email="*****@*****.**",
             type="admin",
             confirmed=True,
             deactivated=False,
         )
         session.add(user)
         session.commit()
    def update_user(
            self, token: str, user_details: dict,
            credentials: Optional[dict[str,
                                       str]]) -> Tuple[Token, UserDetails]:
        """Update a user's data"""
        if not credentials:
            credentials = {}
        user_details = {**user_details}  # don't mutate original object
        user_id, token = validate_token(token)

        if "password" in user_details or "username" in user_details:
            # Must login again to change access credentials
            token = self.login(  # will raise an error if login fails
                username=credentials["username"],
                password=credentials["password"])
        if "password" in user_details:  # encrypt new password
            password = user_details["password"]
            user_details["password"] = encrypt(password)

        with Session(self._engine, future=True) as session:
            user = self._get_user_by_id(user_id, session)
            for key, val in user_details.items():
                if key in PROTECTED_FIELDS:
                    if not user.is_admin:
                        raise UnauthorizedUserError
                setattr(user, key, val)
            session.add(user)
            session.commit()
            return str(token), user.to_dict()
Example #4
0
def test_process_sanitised_letter_retries_if_there_is_an_exception(
    mocker,
    sample_letter_notification,
):
    mocker.patch('app.celery.letters_pdf_tasks.update_letter_pdf_status',
                 side_effect=Exception())
    mock_celery_retry = mocker.patch(
        'app.celery.letters_pdf_tasks.process_sanitised_letter.retry')

    sample_letter_notification.status = NOTIFICATION_PENDING_VIRUS_CHECK
    encrypted_data = encryption.encrypt({
        'page_count':
        2,
        'message':
        None,
        'invalid_pages':
        None,
        'validation_status':
        'passed',
        'filename':
        'NOTIFY.{}'.format(sample_letter_notification.reference),
        'notification_id':
        str(sample_letter_notification.id),
        'address':
        None
    })

    process_sanitised_letter(encrypted_data)

    mock_celery_retry.assert_called_once_with(queue='retry-tasks')
def save_email_to_queue(*,
                        notification_id,
                        form,
                        notification_type,
                        api_key,
                        template,
                        service_id,
                        personalisation,
                        document_download_count,
                        reply_to_text=None):
    data = {
        "id": notification_id,
        "template_id": str(template.id),
        "template_version": template.version,
        "to": form['email_address'],
        "service_id": str(service_id),
        "personalisation": personalisation,
        "notification_type": notification_type,
        "api_key_id": str(api_key.id),
        "key_type": api_key.key_type,
        "client_reference": form.get('reference', None),
        "reply_to_text": reply_to_text,
        "document_download_count": document_download_count,
        "status": NOTIFICATION_CREATED,
        "created_at": datetime.utcnow().strftime(DATETIME_FORMAT),
    }
    encrypted = encryption.encrypt(data)

    save_api_email.apply_async([encrypted], queue=QueueNames.SAVE_API_EMAIL)
    return Notification(**data)
Example #6
0
def test_should_put_send_email_task_in_research_mode_queue_if_research_mode_service(
        notify_db, notify_db_session, mocker
):
    service = sample_service(notify_db, notify_db_session)
    service.research_mode = True
    services_dao.dao_update_service(service)

    template = sample_email_template(notify_db, notify_db_session, service=service)

    notification = _notification_json(template, to="*****@*****.**")

    mocker.patch('app.celery.provider_tasks.deliver_email.apply_async')

    notification_id = uuid.uuid4()

    send_email(
        template.service_id,
        notification_id,
        encryption.encrypt(notification),
        datetime.utcnow().strftime(DATETIME_FORMAT)
    )

    persisted_notification = Notification.query.one()
    provider_tasks.deliver_email.apply_async.assert_called_once_with(
        [str(persisted_notification.id)],
        queue="research-mode"
    )
Example #7
0
def test_process_sanitised_letter_puts_letter_into_tech_failure_for_boto_errors(
    sample_letter_notification,
    mocker,
):
    mocker.patch('app.celery.letters_pdf_tasks.s3.get_s3_object',
                 side_effect=ClientError({}, 'operation_name'))
    sample_letter_notification.status = NOTIFICATION_PENDING_VIRUS_CHECK

    encrypted_data = encryption.encrypt({
        'page_count':
        2,
        'message':
        None,
        'invalid_pages':
        None,
        'validation_status':
        'passed',
        'filename':
        'NOTIFY.{}'.format(sample_letter_notification.reference),
        'notification_id':
        str(sample_letter_notification.id),
        'address':
        None
    })

    with pytest.raises(NotificationTechnicalFailureException):
        process_sanitised_letter(encrypted_data)

    assert sample_letter_notification.status == NOTIFICATION_TECHNICAL_FAILURE
Example #8
0
def test_get_pdf_for_templated_letter_happy_path(mocker, sample_letter_notification, branding_name, logo_filename):
    if branding_name:
        letter_branding = create_letter_branding(name=branding_name, filename=logo_filename)
        sample_letter_notification.service.letter_branding = letter_branding
    mock_celery = mocker.patch('app.celery.letters_pdf_tasks.notify_celery.send_task')
    mocker.patch('app.celery.letters_pdf_tasks.get_letter_pdf_filename', return_value='LETTER.PDF')
    get_pdf_for_templated_letter(sample_letter_notification.id)

    letter_data = {
        'letter_contact_block': sample_letter_notification.reply_to_text,
        'template': {
            "subject": sample_letter_notification.template.subject,
            "content": sample_letter_notification.template.content,
            "template_type": sample_letter_notification.template.template_type
        },
        'values': sample_letter_notification.personalisation,
        'logo_filename': logo_filename,
        'letter_filename': 'LETTER.PDF',
        "notification_id": str(sample_letter_notification.id),
        'key_type': sample_letter_notification.key_type
    }

    encrypted_data = encryption.encrypt(letter_data)

    mock_celery.assert_called_once_with(
        name=TaskNames.CREATE_PDF_FOR_TEMPLATED_LETTER,
        args=(encrypted_data,),
        queue=QueueNames.SANITISE_LETTERS
    )
Example #9
0
def test_should_use_email_template_and_persist_without_personalisation(sample_email_template, mocker):
    notification = _notification_json(sample_email_template, "my_email@my_email.com")
    mocker.patch('app.celery.provider_tasks.deliver_email.apply_async')

    notification_id = uuid.uuid4()

    now = datetime.utcnow()
    send_email(
        sample_email_template.service_id,
        notification_id,
        encryption.encrypt(notification),
        now.strftime(DATETIME_FORMAT)
    )
    persisted_notification = Notification.query.one()
    assert persisted_notification.to == 'my_email@my_email.com'
    assert persisted_notification.template_id == sample_email_template.id
    assert persisted_notification.created_at == now
    assert not persisted_notification.sent_at
    assert persisted_notification.status == 'created'
    assert not persisted_notification.sent_by
    assert not persisted_notification.personalisation
    assert not persisted_notification.reference
    assert persisted_notification.notification_type == 'email'
    provider_tasks.deliver_email.apply_async.assert_called_once_with([str(persisted_notification.id)],
                                                                     queue='send-email')
Example #10
0
def test_process_sanitised_letter_when_letter_status_is_not_pending_virus_scan(
    sample_letter_notification,
    mocker,
):
    mock_s3 = mocker.patch('app.celery.letters_pdf_tasks.s3')
    sample_letter_notification.status = NOTIFICATION_CREATED

    encrypted_data = encryption.encrypt({
        'page_count':
        2,
        'message':
        None,
        'invalid_pages':
        None,
        'validation_status':
        'passed',
        'filename':
        'NOTIFY.{}'.format(sample_letter_notification.reference),
        'notification_id':
        str(sample_letter_notification.id),
        'address':
        None
    })
    process_sanitised_letter(encrypted_data)

    assert not mock_s3.called
Example #11
0
def test_process_sanitised_letter_puts_letter_into_technical_failure_if_max_retries_exceeded(
    mocker,
    sample_letter_notification,
):
    mocker.patch('app.celery.letters_pdf_tasks.update_letter_pdf_status',
                 side_effect=Exception())
    mocker.patch('app.celery.letters_pdf_tasks.process_sanitised_letter.retry',
                 side_effect=MaxRetriesExceededError())

    sample_letter_notification.status = NOTIFICATION_PENDING_VIRUS_CHECK
    encrypted_data = encryption.encrypt({
        'page_count':
        2,
        'message':
        None,
        'invalid_pages':
        None,
        'validation_status':
        'passed',
        'filename':
        'NOTIFY.{}'.format(sample_letter_notification.reference),
        'notification_id':
        str(sample_letter_notification.id),
        'address':
        None
    })

    with pytest.raises(NotificationTechnicalFailureException):
        process_sanitised_letter(encrypted_data)

    assert sample_letter_notification.status == NOTIFICATION_TECHNICAL_FAILURE
Example #12
0
def process_row(row, template, job, service):
    template_type = template.template_type
    encrypted = encryption.encrypt({
        'template': str(template.id),
        'template_version': job.template_version,
        'job': str(job.id),
        'to': row.recipient,
        'row_number': row.index,
        'personalisation': dict(row.personalisation)
    })

    send_fns = {
        SMS_TYPE: save_sms,
        EMAIL_TYPE: save_email,
        LETTER_TYPE: save_letter
    }

    send_fn = send_fns[template_type]

    send_fn.apply_async(
        (
            str(service.id),
            create_uuid(),
            encrypted,
        ),
        queue=QueueNames.DATABASE if not service.research_mode else QueueNames.RESEARCH_MODE
    )
Example #13
0
def test_should_send_sms_if_restricted_service_and_valid_number(notify_db, notify_db_session, mocker):
    user = sample_user(notify_db, notify_db_session, mobile_numnber="07700 900890")
    service = sample_service(notify_db, notify_db_session, user=user, restricted=True)
    template = sample_template(notify_db, notify_db_session, service=service)
    notification = _notification_json(template, "+447700900890")  # The user’s own number, but in a different format

    mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')

    notification_id = uuid.uuid4()
    encrypt_notification = encryption.encrypt(notification)
    send_sms(
        service.id,
        notification_id,
        encrypt_notification,
        datetime.utcnow().strftime(DATETIME_FORMAT)
    )

    persisted_notification = Notification.query.one()
    assert persisted_notification.to == '+447700900890'
    assert persisted_notification.template_id == template.id
    assert persisted_notification.template_version == template.version
    assert persisted_notification.status == 'created'
    assert persisted_notification.created_at <= datetime.utcnow()
    assert not persisted_notification.sent_at
    assert not persisted_notification.sent_by
    assert not persisted_notification.job_id
    assert not persisted_notification.personalisation
    assert persisted_notification.notification_type == 'sms'
    provider_tasks.deliver_sms.apply_async.assert_called_once_with(
        [str(persisted_notification.id)],
        queue="send-sms"
    )
Example #14
0
def test_send_email_should_use_template_version_from_job_not_latest(sample_email_template, mocker):
    notification = _notification_json(sample_email_template, 'my_email@my_email.com')
    version_on_notification = sample_email_template.version
    # Change the template
    from app.dao.templates_dao import dao_update_template, dao_get_template_by_id
    sample_email_template.content = sample_email_template.content + " another version of the template"
    mocker.patch('app.celery.provider_tasks.deliver_email.apply_async')
    dao_update_template(sample_email_template)
    t = dao_get_template_by_id(sample_email_template.id)
    assert t.version > version_on_notification
    now = datetime.utcnow()
    send_email(
        sample_email_template.service_id,
        uuid.uuid4(),
        encryption.encrypt(notification),
        now.strftime(DATETIME_FORMAT)
    )

    persisted_notification = Notification.query.one()
    assert persisted_notification.to == 'my_email@my_email.com'
    assert persisted_notification.template_id == sample_email_template.id
    assert persisted_notification.template_version == version_on_notification
    assert persisted_notification.created_at == now
    assert not persisted_notification.sent_at
    assert persisted_notification.status == 'created'
    assert not persisted_notification.sent_by
    assert persisted_notification.notification_type == 'email'
    provider_tasks.deliver_email.apply_async.assert_called_once_with([str(persisted_notification.id)],
                                                                     queue='send-email')
Example #15
0
def test_process_sanitised_letter_with_valid_letter(
    sample_letter_notification,
    key_type,
    destination_bucket,
    expected_status,
    postage,
    destination_filename,
):
    # We save the letter as if it's 2nd class initially, and the task changes the filename to have the correct postage
    filename = 'NOTIFY.FOO.D.2.C.C.20180701120000.PDF'

    scan_bucket_name = current_app.config['LETTERS_SCAN_BUCKET_NAME']
    template_preview_bucket_name = current_app.config['LETTER_SANITISE_BUCKET_NAME']
    destination_bucket_name = current_app.config[destination_bucket]
    conn = boto3.resource('s3', region_name='eu-west-1')

    scan_bucket = conn.create_bucket(
        Bucket=scan_bucket_name,
        CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'}
    )
    template_preview_bucket = conn.create_bucket(
        Bucket=template_preview_bucket_name,
        CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'}
    )
    destination_bucket = conn.create_bucket(
        Bucket=destination_bucket_name,
        CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'}
    )

    s3 = boto3.client('s3', region_name='eu-west-1')
    s3.put_object(Bucket=scan_bucket_name, Key=filename, Body=b'original_pdf_content')
    s3.put_object(Bucket=template_preview_bucket_name, Key=filename, Body=b'sanitised_pdf_content')

    sample_letter_notification.status = NOTIFICATION_PENDING_VIRUS_CHECK
    sample_letter_notification.key_type = key_type
    sample_letter_notification.billable_units = 1
    sample_letter_notification.created_at = datetime(2018, 7, 1, 12)
    sample_letter_notification.postage = postage

    encrypted_data = encryption.encrypt({
        'page_count': 2,
        'message': None,
        'invalid_pages': None,
        'validation_status': 'passed',
        'filename': filename,
        'notification_id': str(sample_letter_notification.id),
        'address': 'A. User\nThe house on the corner'
    })
    process_sanitised_letter(encrypted_data)

    assert sample_letter_notification.status == expected_status
    assert sample_letter_notification.billable_units == 1
    assert sample_letter_notification.to == 'A. User\nThe house on the corner'

    assert not [x for x in scan_bucket.objects.all()]
    assert not [x for x in template_preview_bucket.objects.all()]
    assert len([x for x in destination_bucket.objects.all()]) == 1

    file_contents = conn.Object(destination_bucket_name, destination_filename).get()['Body'].read().decode('utf-8')
    assert file_contents == 'sanitised_pdf_content'
Example #16
0
def create_delivery_status_callback_data(notification, service_callback_api):
    data = {
        "notification_id":
        str(notification.id),
        "notification_client_reference":
        notification.client_reference,
        "notification_to":
        notification.to,
        "notification_status":
        notification.status,
        "notification_created_at":
        notification.created_at.strftime(DATETIME_FORMAT),
        "notification_updated_at":
        notification.updated_at.strftime(DATETIME_FORMAT)
        if notification.updated_at else None,
        "notification_sent_at":
        notification.sent_at.strftime(DATETIME_FORMAT)
        if notification.sent_at else None,
        "notification_type":
        notification.notification_type,
        "service_callback_api_url":
        service_callback_api.url,
        "service_callback_api_bearer_token":
        service_callback_api.bearer_token,
    }
    return encryption.encrypt(data)
Example #17
0
def test_should_send_email_if_restricted_service_and_non_team_email_address_with_test_key(notify_db,
                                                                                          notify_db_session,
                                                                                          mocker):
    user = sample_user(notify_db, notify_db_session)
    service = sample_service(notify_db, notify_db_session, user=user, restricted=True)
    template = sample_template(
        notify_db, notify_db_session, service=service, template_type='email', subject_line='Hello'
    )

    notification = _notification_json(template, to="*****@*****.**")
    mocked_deliver_email = mocker.patch('app.celery.provider_tasks.deliver_email.apply_async')

    notification_id = uuid.uuid4()
    send_email(
        service.id,
        notification_id,
        encryption.encrypt(notification),
        datetime.utcnow().strftime(DATETIME_FORMAT),
        key_type=KEY_TYPE_TEST
    )

    persisted_notification = Notification.query.one()
    mocked_deliver_email.assert_called_once_with(
        [str(persisted_notification.id)],
        queue="send-email"
    )
Example #18
0
def test_should_send_sms_template_to_and_persist_with_job_id(sample_job, sample_api_key, mocker):
    notification = _notification_json(
        sample_job.template,
        to="+447234123123",
        job_id=sample_job.id,
        row_number=2)
    mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')

    notification_id = uuid.uuid4()
    now = datetime.utcnow()
    send_sms(
        sample_job.service.id,
        notification_id,
        encryption.encrypt(notification),
        now.strftime(DATETIME_FORMAT),
        api_key_id=str(sample_api_key.id),
        key_type=KEY_TYPE_NORMAL
    )
    persisted_notification = Notification.query.one()
    assert persisted_notification.to == '+447234123123'
    assert persisted_notification.job_id == sample_job.id
    assert persisted_notification.template_id == sample_job.template.id
    assert persisted_notification.status == 'created'
    assert not persisted_notification.sent_at
    assert persisted_notification.created_at <= now
    assert not persisted_notification.sent_by
    assert persisted_notification.job_row_number == 2
    assert persisted_notification.api_key_id == sample_api_key.id
    assert persisted_notification.key_type == KEY_TYPE_NORMAL
    assert persisted_notification.notification_type == 'sms'

    provider_tasks.deliver_sms.apply_async.assert_called_once_with(
        [str(persisted_notification.id)],
        queue="send-sms"
    )
Example #19
0
def test_should_not_send_email_if_team_key_and_recipient_not_in_team(sample_email_template_with_placeholders,
                                                                     sample_team_api_key,
                                                                     mocker):
    notification = _notification_json(
        sample_email_template_with_placeholders,
        "my_email@my_email.com",
        {"name": "Jo"},
        row_number=1)
    apply_async = mocker.patch('app.celery.provider_tasks.deliver_email.apply_async')
    notification_id = uuid.uuid4()

    team_members = [user.email_address for user in sample_email_template_with_placeholders.service.users]
    assert "my_email@my_email.com" not in team_members

    with freeze_time("2016-01-01 11:09:00.00000"):
        now = datetime.utcnow()

        send_email(
            sample_email_template_with_placeholders.service_id,
            notification_id,
            encryption.encrypt(notification),
            now.strftime(DATETIME_FORMAT),
            api_key_id=str(sample_team_api_key.id),
            key_type=KEY_TYPE_TEAM
        )

        assert Notification.query.count() == 0

    apply_async.not_called()
def _set_up_data_for_status_update(callback_api, notification):
    data = {
        "notification_id":
        str(notification.id),
        "notification_client_reference":
        notification.client_reference,
        "notification_to":
        notification.to,
        "notification_status":
        notification.status,
        "notification_created_at":
        notification.created_at.strftime(DATETIME_FORMAT),
        "notification_updated_at":
        notification.updated_at.strftime(DATETIME_FORMAT)
        if notification.updated_at else None,
        "notification_sent_at":
        notification.sent_at.strftime(DATETIME_FORMAT)
        if notification.sent_at else None,
        "notification_type":
        notification.notification_type,
        "service_callback_api_url":
        callback_api.url,
        "service_callback_api_bearer_token":
        callback_api.bearer_token,
    }
    encrypted_status_update = encryption.encrypt(data)
    return encrypted_status_update
Example #21
0
def add_new_user(login, password):
    hashed, salt = enc.encrypt(password)
    cursor = connection.cursor(buffered=True)
    cursor.execute(
        "INSERT INTO users(username, passwd, salt) VALUES (%s, %s, %s)",
        (login, hashed, salt))
    connection.commit()
    cursor.close()
def test_process_sanitised_letter_with_invalid_letter(
        sample_letter_notification, key_type):
    filename = 'NOTIFY.{}'.format(sample_letter_notification.reference)

    scan_bucket_name = current_app.config['LETTERS_SCAN_BUCKET_NAME']
    template_preview_bucket_name = current_app.config[
        'LETTER_SANITISE_BUCKET_NAME']
    invalid_letter_bucket_name = current_app.config['INVALID_PDF_BUCKET_NAME']
    conn = boto3.resource('s3', region_name='eu-west-1')

    scan_bucket = conn.create_bucket(
        Bucket=scan_bucket_name,
        CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'})
    template_preview_bucket = conn.create_bucket(
        Bucket=template_preview_bucket_name,
        CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'})
    invalid_letter_bucket = conn.create_bucket(
        Bucket=invalid_letter_bucket_name,
        CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'})

    s3 = boto3.client('s3', region_name='eu-west-1')
    s3.put_object(Bucket=scan_bucket_name,
                  Key=filename,
                  Body=b'original_pdf_content')

    sample_letter_notification.status = NOTIFICATION_PENDING_VIRUS_CHECK
    sample_letter_notification.key_type = key_type
    sample_letter_notification.billable_units = 1
    sample_letter_notification.created_at = datetime(2018, 7, 1, 12)

    encrypted_data = encryption.encrypt({
        'page_count':
        2,
        'message':
        'content-outside-printable-area',
        'invalid_pages': [1],
        'validation_status':
        'failed',
        'filename':
        filename,
        'notification_id':
        str(sample_letter_notification.id),
        'address':
        None,
    })
    process_sanitised_letter(encrypted_data)

    assert sample_letter_notification.status == NOTIFICATION_VALIDATION_FAILED
    assert sample_letter_notification.billable_units == 0

    assert not [x for x in scan_bucket.objects.all()]
    assert not [x for x in template_preview_bucket.objects.all()]
    assert len([x for x in invalid_letter_bucket.objects.all()]) == 1

    file_contents = conn.Object(invalid_letter_bucket_name,
                                filename).get()['Body'].read().decode('utf-8')
    assert file_contents == 'original_pdf_content'
def test_process_sanitised_letter_sets_postage_international(
        sample_letter_notification, expected_postage, expected_international,
        address):
    filename = 'NOTIFY.{}'.format(sample_letter_notification.reference)

    scan_bucket_name = current_app.config['LETTERS_SCAN_BUCKET_NAME']
    template_preview_bucket_name = current_app.config[
        'LETTER_SANITISE_BUCKET_NAME']
    destination_bucket_name = current_app.config['LETTERS_PDF_BUCKET_NAME']
    conn = boto3.resource('s3', region_name='eu-west-1')
    conn.create_bucket(
        Bucket=scan_bucket_name,
        CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'})
    conn.create_bucket(
        Bucket=template_preview_bucket_name,
        CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'})
    conn.create_bucket(
        Bucket=destination_bucket_name,
        CreateBucketConfiguration={'LocationConstraint': 'eu-west-1'})

    s3 = boto3.client('s3', region_name='eu-west-1')
    s3.put_object(Bucket=scan_bucket_name,
                  Key=filename,
                  Body=b'original_pdf_content')
    s3.put_object(Bucket=template_preview_bucket_name,
                  Key=filename,
                  Body=b'sanitised_pdf_content')

    sample_letter_notification.status = NOTIFICATION_PENDING_VIRUS_CHECK
    sample_letter_notification.billable_units = 1
    sample_letter_notification.created_at = datetime(2018, 7, 1, 12)

    encrypted_data = encryption.encrypt({
        'page_count':
        2,
        'message':
        None,
        'invalid_pages':
        None,
        'validation_status':
        'passed',
        'filename':
        filename,
        'notification_id':
        str(sample_letter_notification.id),
        'address':
        address
    })
    process_sanitised_letter(encrypted_data)

    assert sample_letter_notification.status == 'created'
    assert sample_letter_notification.billable_units == 1
    assert sample_letter_notification.to == address
    assert sample_letter_notification.postage == expected_postage
    assert sample_letter_notification.international == expected_international
def create_complaint_callback_data(complaint, notification, service_callback_api, recipient):
    data = {
        "complaint_id": str(complaint.id),
        "notification_id": str(notification.id),
        "reference": notification.client_reference,
        "to": recipient,
        "complaint_date": complaint.complaint_date.strftime(DATETIME_FORMAT),
        "service_callback_api_url": service_callback_api.url,
        "service_callback_api_bearer_token": service_callback_api.bearer_token,
    }
    return encryption.encrypt(data)
def loaded_db(db, user_details, unconfirmed_user):
    """An active database instance with two users -- one admin, one contrib"""
    encrypted_user_details = {
        **user_details,
        "password": encrypt(user_details["password"]),
    }
    with Session(db._engine, future=True) as session:
        session.add(User(**encrypted_user_details))
        session.add(User(**unconfirmed_user))
        session.commit()
    return db
def db(bare_db, admin_user_details):
    """An active database instance with one admin user"""
    # must start with an admin user
    encrypted_admin_details = {
        **admin_user_details,
        "password": encrypt(admin_user_details["password"]),
    }
    with Session(bare_db._engine, future=True) as session:
        session.add(User(**encrypted_admin_details))
        session.commit()
    return bare_db
Example #27
0
def process_job(job_id):
    start = datetime.utcnow()
    job = dao_get_job_by_id(job_id)

    if job.job_status != "pending":
        return

    service = job.service

    if __sending_limits_for_job_exceeded(service, job, job_id):
        return

    job.job_status = "in progress"
    dao_update_job(job)

    template = Template(dao_get_template_by_id(job.template_id, job.template_version).__dict__)

    for row_number, recipient, personalisation in RecipientCSV(
        s3.get_job_from_s3(str(service.id), str(job_id)),
        template_type=template.template_type,
        placeholders=template.placeholders,
    ).enumerated_recipients_and_personalisation:

        encrypted = encryption.encrypt(
            {
                "template": str(template.id),
                "template_version": job.template_version,
                "job": str(job.id),
                "to": recipient,
                "row_number": row_number,
                "personalisation": dict(personalisation),
            }
        )

        if template.template_type == SMS_TYPE:
            send_sms.apply_async(
                (str(job.service_id), create_uuid(), encrypted, datetime.utcnow().strftime(DATETIME_FORMAT)),
                queue="db-sms" if not service.research_mode else "research-mode",
            )

        if template.template_type == EMAIL_TYPE:
            send_email.apply_async(
                (str(job.service_id), create_uuid(), encrypted, datetime.utcnow().strftime(DATETIME_FORMAT)),
                queue="db-email" if not service.research_mode else "research-mode",
            )

    finished = datetime.utcnow()
    job.job_status = "finished"
    job.processing_started = start
    job.processing_finished = finished
    dao_update_job(job)
    current_app.logger.info(
        "Job {} created at {} started at {} finished at {}".format(job_id, job.created_at, start, finished)
    )
Example #28
0
def test_should_use_email_template_and_persist(sample_email_template_with_placeholders, sample_api_key, mocker):
    with freeze_time("2016-01-01 12:00:00.000000"):
        notification = _notification_json(
            sample_email_template_with_placeholders,
            'my_email@my_email.com',
            {"name": "Jo"},
            row_number=1)
        mocker.patch('app.celery.provider_tasks.deliver_email.apply_async')

        notification_id = uuid.uuid4()

        with freeze_time("2016-01-01 11:09:00.00000"):
            now = datetime.utcnow()

        with freeze_time("2016-01-01 11:10:00.00000"):
            send_email(
                sample_email_template_with_placeholders.service_id,
                notification_id,
                encryption.encrypt(notification),
                now.strftime(DATETIME_FORMAT),
                api_key_id=str(sample_api_key.id),
                key_type=sample_api_key.key_type
            )

    persisted_notification = Notification.query.one()
    assert persisted_notification.to == 'my_email@my_email.com'
    assert persisted_notification.template_id == sample_email_template_with_placeholders.id
    assert persisted_notification.template_version == sample_email_template_with_placeholders.version
    assert persisted_notification.created_at == now
    assert not persisted_notification.sent_at
    assert persisted_notification.status == 'created'
    assert not persisted_notification.sent_by
    assert persisted_notification.job_row_number == 1
    assert persisted_notification.personalisation == {'name': 'Jo'}
    assert persisted_notification._personalisation == encryption.encrypt({"name": "Jo"})
    assert persisted_notification.api_key_id == sample_api_key.id
    assert persisted_notification.key_type == KEY_TYPE_NORMAL
    assert persisted_notification.notification_type == 'email'

    provider_tasks.deliver_email.apply_async.assert_called_once_with(
        [str(persisted_notification.id)], queue='send-email')
Example #29
0
def _set_up_data_for_complaint(callback_api, complaint, notification):
    data = {
        "complaint_id": str(complaint.id),
        "notification_id": str(notification.id),
        "reference": notification.client_reference,
        "to": notification.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,
    }
    obscured_status_update = encryption.encrypt(data)
    return obscured_status_update
def get_pdf_for_templated_letter(self, notification_id):
    try:
        notification = get_notification_by_id(notification_id, _raise=True)

        letter_filename = get_letter_pdf_filename(
            reference=notification.reference,
            crown=notification.service.crown,
            sending_date=notification.created_at,
            dont_use_sending_date=notification.key_type == KEY_TYPE_TEST,
            postage=notification.postage)
        letter_data = {
            'letter_contact_block':
            notification.reply_to_text,
            'template': {
                "subject": notification.template.subject,
                "content": notification.template.content,
                "template_type": notification.template.template_type
            },
            'values':
            notification.personalisation,
            'logo_filename':
            notification.service.letter_branding
            and notification.service.letter_branding.filename,
            'letter_filename':
            letter_filename,
            "notification_id":
            str(notification_id),
            'key_type':
            notification.key_type
        }

        encrypted_data = encryption.encrypt(letter_data)

        notify_celery.send_task(name=TaskNames.CREATE_PDF_FOR_TEMPLATED_LETTER,
                                args=(encrypted_data, ),
                                queue=QueueNames.SANITISE_LETTERS)
    except Exception:
        try:
            current_app.logger.exception(
                f"RETRY: calling create-letter-pdf task for notification {notification_id} failed"
            )
            self.retry(queue=QueueNames.RETRY)
        except self.MaxRetriesExceededError:
            message = f"RETRY FAILED: Max retries reached. " \
                      f"The task create-letter-pdf failed for notification id {notification_id}. " \
                      f"Notification has been updated to technical-failure"
            update_notification_status_by_id(notification_id,
                                             NOTIFICATION_TECHNICAL_FAILURE)
            raise NotificationTechnicalFailureException(message)
Example #31
0
def replay_service_callbacks(file_name, service_id):
    print("Start send service callbacks for service: ", service_id)
    callback_api = get_service_delivery_status_callback_api_for_service(
        service_id=service_id)
    if not callback_api:
        print("Callback api was not found for service: {}".format(service_id))
        return

    errors = []
    notifications = []
    file = open(file_name)

    for ref in file:
        try:
            notification = Notification.query.filter_by(
                client_reference=ref.strip()).one()
            notifications.append(notification)
        except NoResultFound:
            errors.append(
                "Reference: {} was not found in notifications.".format(ref))

    for e in errors:
        print(e)
    if errors:
        raise Exception(
            "Some notifications for the given references were not found")

    for n in notifications:
        data = {
            "notification_id": str(n.id),
            "notification_client_reference": n.client_reference,
            "notification_to": n.to,
            "notification_status": n.status,
            "notification_created_at": n.created_at.strftime(DATETIME_FORMAT),
            "notification_updated_at": n.updated_at.strftime(DATETIME_FORMAT),
            "notification_sent_at": n.sent_at.strftime(DATETIME_FORMAT),
            "notification_type": n.notification_type,
            "service_callback_api_url": callback_api.url,
            "service_callback_api_bearer_token": callback_api.bearer_token,
        }
        encrypted_status_update = encryption.encrypt(data)
        send_delivery_status_to_service.apply_async(
            [str(n.id), encrypted_status_update], queue=QueueNames.CALLBACKS)

    print(
        "Replay service status for service: {}. Sent {} notification status updates to the queue"
        .format(service_id, len(notifications)))
def test_create_invited_user(notify_api, sample_service, mocker, invitation_email_template):
    with notify_api.test_request_context():
        with notify_api.test_client() as client:
            mocker.patch('uuid.uuid4', return_value='some_uuid')  # for the notification id
            mocker.patch('app.celery.tasks.send_email.apply_async')
            mocker.patch('notifications_utils.url_safe_token.generate_token', return_value='the-token')
            email_address = '*****@*****.**'
            invite_from = sample_service.users[0]

            data = {
                'service': str(sample_service.id),
                'email_address': email_address,
                'from_user': str(invite_from.id),
                'permissions': 'send_messages,manage_service,manage_api_keys'
            }
            auth_header = create_authorization_header()

            response = client.post(
                '/service/{}/invite'.format(sample_service.id),
                headers=[('Content-Type', 'application/json'), auth_header],
                data=json.dumps(data)
            )
            assert response.status_code == 201
            json_resp = json.loads(response.get_data(as_text=True))

            assert json_resp['data']['service'] == str(sample_service.id)
            assert json_resp['data']['email_address'] == email_address
            assert json_resp['data']['from_user'] == str(invite_from.id)
            assert json_resp['data']['permissions'] == 'send_messages,manage_service,manage_api_keys'
            assert json_resp['data']['id']

            message = {
                'template': str(invitation_email_template.id),
                'template_version': invitation_email_template.version,
                'to': email_address,
                'personalisation': {
                    'user_name': invite_from.name,
                    'service_name': sample_service.name,
                    'url': '{0}/invitation/{1}'.format(current_app.config['ADMIN_BASE_URL'], 'the-token')
                }
            }
            app.celery.tasks.send_email.apply_async.assert_called_once_with(
                (str(current_app.config['NOTIFY_SERVICE_ID']),
                 'some_uuid',
                 encryption.encrypt(message),
                 "2016-01-01T11:09:00.061258Z"),
                queue="notify")
Example #33
0
def test_send_sms_does_not_send_duplicate_and_does_not_put_in_retry_queue(sample_notification, mocker):
    json = _notification_json(sample_notification.template, sample_notification.to, job_id=uuid.uuid4(), row_number=1)
    deliver_sms = mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')
    retry = mocker.patch('app.celery.tasks.send_sms.retry', side_effect=Exception())
    now = datetime.utcnow()

    notification_id = sample_notification.id

    send_sms(
        sample_notification.service_id,
        notification_id,
        encryption.encrypt(json),
        now.strftime(DATETIME_FORMAT)
    )
    assert Notification.query.count() == 1
    assert not deliver_sms.called
    assert not retry.called
Example #34
0
def test_should_not_send_sms_if_restricted_service_and_invalid_number(notify_db, notify_db_session, mocker):
    user = sample_user(notify_db, notify_db_session, mobile_numnber="07700 900205")
    service = sample_service(notify_db, notify_db_session, user=user, restricted=True)
    template = sample_template(notify_db, notify_db_session, service=service)

    notification = _notification_json(template, "07700 900849")
    mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')

    notification_id = uuid.uuid4()
    send_sms(
        service.id,
        notification_id,
        encryption.encrypt(notification),
        datetime.utcnow().strftime(DATETIME_FORMAT)
    )
    assert provider_tasks.deliver_sms.apply_async.called is False
    assert Notification.query.count() == 0
Example #35
0
def test_should_not_send_email_if_restricted_service_and_invalid_email_address(notify_db, notify_db_session, mocker):
    user = sample_user(notify_db, notify_db_session)
    service = sample_service(notify_db, notify_db_session, user=user, restricted=True)
    template = sample_template(
        notify_db, notify_db_session, service=service, template_type='email', subject_line='Hello'
    )
    notification = _notification_json(template, to="*****@*****.**")

    notification_id = uuid.uuid4()
    send_email(
        service.id,
        notification_id,
        encryption.encrypt(notification),
        datetime.utcnow().strftime(DATETIME_FORMAT)
    )

    assert Notification.query.count() == 0
Example #36
0
def process_row(row: Row, template: Template, job: Job, service: Service):
    template_type = template.template_type
    encrypted = encryption.encrypt({
        "api_key":
        job.api_key_id and str(job.api_key_id),
        "template":
        str(template.id),
        "template_version":
        job.template_version,
        "job":
        str(job.id),
        "to":
        row.recipient,
        "row_number":
        row.index,
        "personalisation":
        dict(row.personalisation),
        "queue":
        queue_to_use(job.notification_count),
    })
    sender_id = str(job.sender_id) if job.sender_id else None

    send_fns = {
        SMS_TYPE: save_sms,
        EMAIL_TYPE: save_email,
        LETTER_TYPE: save_letter
    }

    send_fn = send_fns[template_type]

    task_kwargs = {}
    if sender_id:
        task_kwargs["sender_id"] = sender_id

    send_fn.apply_async(
        (
            str(service.id),
            create_uuid(),
            encrypted,
        ),
        task_kwargs,
        queue=QueueNames.DATABASE
        if not service.research_mode else QueueNames.RESEARCH_MODE,
    )
Example #37
0
def send_user_reset_password():
    email, errors = email_data_request_schema.load(request.get_json())

    user_to_send_to = get_user_by_email(email['email'])

    template = dao_get_template_by_id(current_app.config['PASSWORD_RESET_TEMPLATE_ID'])
    message = {
        'template': str(template.id),
        'template_version': template.version,
        'to': user_to_send_to.email_address,
        'personalisation': {
            'user_name': user_to_send_to.name,
            'url': _create_reset_password_url(user_to_send_to.email_address)
        }
    }
    send_email.apply_async([current_app.config['NOTIFY_SERVICE_ID'],
                            str(uuid.uuid4()),
                            encryption.encrypt(message),
                            datetime.utcnow().strftime(DATETIME_FORMAT)], queue='notify')

    return jsonify({}), 204
    def add_user(self, token: Token, user_details: Dict) -> UserDetails:
        """Adds a user to the database"""

        user_id, token = validate_token(token)
        if not self.check_if_username_is_unique(user_details["username"]):
            raise DuplicateUsernameError
        with Session(self._engine, future=True) as session:
            self._require_admin_user(
                user_id=user_id,
                session=session,
            )

            for field in user_details.keys():
                if field in PROTECTED_FIELDS:
                    raise UnauthorizedUserError

            # don't mutate original dict
            user_details = {
                **user_details,
                "id": str(uuid4()),
                "type": "contrib",
                "confirmed": False,
                "deactivated": False,
            }

            # handle password
            password = user_details["password"]
            user_details["password"] = encrypt(password)
            # create user object
            new_user = User(**user_details)

            session.add(new_user)
            session.commit()

            # TODO: when email service is enabled, add call here to send a token to
            # the provided email address.
            new_user_token = get_token(new_user.id)
            log.info(f"New User Token is: {new_user_token}")

            return token, new_user.to_dict()
Example #39
0
def send_already_registered_email(user_id):
    to, errors = email_data_request_schema.load(request.get_json())
    template = dao_get_template_by_id(current_app.config['ALREADY_REGISTERED_EMAIL_TEMPLATE_ID'])

    message = {
        'template': str(template.id),
        'template_version': template.version,
        'to': to['email'],
        'personalisation': {
            'signin_url': current_app.config['ADMIN_BASE_URL'] + '/sign-in',
            'forgot_password_url': current_app.config['ADMIN_BASE_URL'] + '/forgot-password',
            'feedback_url': current_app.config['ADMIN_BASE_URL'] + '/feedback'
        }
    }
    send_email.apply_async((
        current_app.config['NOTIFY_SERVICE_ID'],
        str(uuid.uuid4()),
        encryption.encrypt(message),
        datetime.utcnow().strftime(DATETIME_FORMAT)
    ), queue='notify')

    return jsonify({}), 204
Example #40
0
def test_send_email_should_go_to_retry_queue_if_database_errors(sample_email_template, mocker):
    notification = _notification_json(sample_email_template, "*****@*****.**")

    expected_exception = SQLAlchemyError()

    mocker.patch('app.celery.provider_tasks.deliver_email.apply_async')
    mocker.patch('app.celery.tasks.send_email.retry', side_effect=Exception())
    mocker.patch('app.notifications.process_notifications.dao_create_notification', side_effect=expected_exception)
    now = datetime.utcnow()

    notification_id = uuid.uuid4()

    with pytest.raises(Exception):
        send_email(
            sample_email_template.service_id,
            notification_id,
            encryption.encrypt(notification),
            now.strftime(DATETIME_FORMAT)
        )
    assert not provider_tasks.deliver_email.apply_async.called
    tasks.send_email.retry.assert_called_with(exc=expected_exception, queue='retry')

    assert Notification.query.count() == 0
Example #41
0
def test_should_send_sms_if_restricted_service_and_non_team_number_with_test_key(notify_db,
                                                                                 notify_db_session,
                                                                                 mocker):
    user = sample_user(notify_db, notify_db_session, mobile_numnber="07700 900205")
    service = sample_service(notify_db, notify_db_session, user=user, restricted=True)
    template = sample_template(notify_db, notify_db_session, service=service)

    notification = _notification_json(template, "07700 900849")
    mocked_deliver_sms = mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')

    notification_id = uuid.uuid4()
    send_sms(
        service.id,
        notification_id,
        encryption.encrypt(notification),
        datetime.utcnow().strftime(DATETIME_FORMAT),
        key_type=KEY_TYPE_TEST
    )

    persisted_notification = Notification.query.one()
    mocked_deliver_sms.assert_called_once_with(
        [str(persisted_notification.id)],
        queue="send-sms"
    )
Example #42
0
def get_api_key_by_secret(secret):
    return db.on_reader().query(ApiKey).filter_by(
        _secret=encryption.encrypt(str(secret))).options(
            joinedload('service')).one()
Example #43
0
 def personalisation(self, personalisation):
     if personalisation:
         self._personalisation = encryption.encrypt(personalisation)
Example #44
0
def test_encrypt():
    value = "some random value"
    encrypted_val = encrypt(value)
    assert value != encrypted_val
Example #45
0
def test_check_password():
    password = "******"
    encrypted_password = encrypt(password)
    assert password != encrypted_password
    assert check_password(password, encrypted_password)
Example #46
0
def process_sms_or_email_notification(*, form, notification_type, api_key, template, service, reply_to_text=None):
    form_send_to = form["email_address"] if notification_type == EMAIL_TYPE else form["phone_number"]

    send_to = validate_and_format_recipient(
        send_to=form_send_to,
        key_type=api_key.key_type,
        service=service,
        notification_type=notification_type,
    )

    # Do not persist or send notification to the queue if it is a simulated recipient
    simulated = simulated_recipient(send_to, notification_type)

    personalisation = process_document_uploads(form.get("personalisation"), service, simulated, template.id)

    notification = {
        "id": create_uuid(),
        "template": str(template.id),
        "template_version": str(template.version),
        "to": form_send_to,
        "personalisation": personalisation,
        "simulated": simulated,
        "api_key": str(api_key.id),
        "key_type": str(api_key.key_type),
        "client_reference": form.get("reference", None),
    }

    encrypted_notification_data = encryption.encrypt(notification)

    scheduled_for = form.get("scheduled_for", None)
    if scheduled_for:
        notification = persist_notification(
            template_id=template.id,
            template_version=template.version,
            recipient=form_send_to,
            service=service,
            personalisation=personalisation,
            notification_type=notification_type,
            api_key_id=api_key.id,
            key_type=api_key.key_type,
            client_reference=form.get("reference", None),
            simulated=simulated,
            reply_to_text=reply_to_text,
        )
        persist_scheduled_notification(notification.id, form["scheduled_for"])

    elif current_app.config["FF_NOTIFICATION_CELERY_PERSISTENCE"] and not simulated:
        # depending on the type route to the appropriate save task
        if notification_type == EMAIL_TYPE:
            current_app.logger.info("calling save email task")
            save_email.apply_async(
                (authenticated_service.id, create_uuid(), encrypted_notification_data),
                queue=QueueNames.DATABASE if not authenticated_service.research_mode else QueueNames.RESEARCH_MODE,
            )
        elif notification_type == SMS_TYPE:
            save_sms.apply_async(
                (authenticated_service.id, create_uuid(), encrypted_notification_data),
                queue=QueueNames.DATABASE if not authenticated_service.research_mode else QueueNames.RESEARCH_MODE,
            )

    else:
        notification = persist_notification(
            template_id=template.id,
            template_version=template.version,
            recipient=form_send_to,
            service=service,
            personalisation=personalisation,
            notification_type=notification_type,
            api_key_id=api_key.id,
            key_type=api_key.key_type,
            client_reference=form.get("reference", None),
            simulated=simulated,
            reply_to_text=reply_to_text,
        )
        if not simulated:
            send_notification_to_queue(
                notification=notification,
                research_mode=service.research_mode,
                queue=template.queue_to_use(),
            )
        else:
            current_app.logger.debug("POST simulated notification for id: {}".format(notification.id))

    if not isinstance(notification, Notification):
        notification["template_id"] = notification["template"]
        notification["api_key_id"] = notification["api_key"]
        notification["template_version"] = template.version
        notification["service"] = service
        notification["service_id"] = service.id
        notification["reply_to_text"] = reply_to_text
        del notification["template"]
        del notification["api_key"]
        del notification["simulated"]
        notification = Notification(**notification)

    return notification
Example #47
0
def test_notification_personalisation_getter_always_returns_empty_dict():
    noti = Notification()
    noti._personalisation = encryption.encrypt({})
    assert noti.personalisation == {}
Example #48
0
def test_notification_personalisation_setter_always_sets_empty_dict(
        input_value):
    noti = Notification()
    noti.personalisation = input_value

    assert noti._personalisation == encryption.encrypt({})