Example #1
0
def process_precompiled_letter_notifications(*, letter_data, api_key, template, reply_to_text):
    try:
        status = NOTIFICATION_PENDING_VIRUS_CHECK
        letter_content = base64.b64decode(letter_data['content'])
        pages = pdf_page_count(io.BytesIO(letter_content))
    except ValueError:
        raise BadRequestError(message='Cannot decode letter content (invalid base64 encoding)', status_code=400)
    except PdfReadError:
        current_app.logger.exception(msg='Invalid PDF received')
        raise BadRequestError(message='Letter content is not a valid PDF', status_code=400)

    notification = create_letter_notification(letter_data=letter_data,
                                              template=template,
                                              api_key=api_key,
                                              status=status,
                                              reply_to_text=reply_to_text)

    filename = upload_letter_pdf(notification, letter_content, precompiled=True)
    pages_per_sheet = 2
    notification.billable_units = math.ceil(pages / pages_per_sheet)

    dao_update_notification(notification)

    current_app.logger.info('Calling task scan-file for {}'.format(filename))

    # call task to add the filename to anti virus queue
    notify_celery.send_task(
        name=TaskNames.SCAN_FILE,
        kwargs={'filename': filename},
        queue=QueueNames.ANTIVIRUS,
    )

    return notification
Example #2
0
def sanitise_file_contents(encoded_string,
                           *,
                           allow_international_letters,
                           filename=None):
    """
    Given a PDF, returns a new PDF that has been sanitised and dvla approved đź‘Ť

    * makes sure letter meets DVLA's printable boundaries and page dimensions requirements
    * re-writes address block (to ensure it's in arial in the right location)
    * adds NOTIFY tag if not present
    """
    try:
        file_data = BytesIO(encoded_string)

        page_count = pdf_page_count(file_data)
        if is_letter_too_long(page_count):
            message = "letter-too-long"
            raise ValidationFailed(message, page_count=page_count)

        message, invalid_pages = get_invalid_pages_with_message(file_data)
        if message:
            raise ValidationFailed(message,
                                   invalid_pages,
                                   page_count=page_count)

        file_data, recipient_address, redaction_failed_message = rewrite_pdf(
            file_data,
            page_count=page_count,
            allow_international_letters=allow_international_letters,
            filename=filename)

        return {
            "recipient_address": recipient_address,
            "page_count": page_count,
            "message": None,
            "invalid_pages": None,
            "redaction_failed_message": redaction_failed_message,
            "file": base64.b64encode(file_data.read()).decode('utf-8')
        }
    except Exception as error:
        if isinstance(error, ValidationFailed):
            current_app.logger.warning(
                'Validation Failed for precompiled pdf: {} for file name: {}'.
                format(repr(error), filename))
        else:
            current_app.logger.exception(
                'Unhandled exception with precompiled pdf: {} for file name: {}'
                .format(repr(error), filename))

        return {
            "page_count": getattr(error, 'page_count', None),
            "recipient_address": None,
            "message": getattr(error, 'message', 'unable-to-read-the-file'),
            "invalid_pages": getattr(error, 'invalid_pages', None),
            "file": None
        }
Example #3
0
def view_notification(service_id, notification_id):
    notification = notification_api_client.get_notification(
        service_id, str(notification_id))
    notification['template'].update(
        {'reply_to_text': notification['reply_to_text']})

    if notification['template']['is_precompiled_letter']:
        file_contents = view_letter_notification_as_preview(
            service_id, notification_id, "pdf")
        page_count = pdf_page_count(io.BytesIO(file_contents))
    else:
        page_count = get_page_count_for_letter(notification['template'])

    template = get_template(
        notification['template'],
        current_service,
        letter_preview_url=url_for(
            '.view_letter_notification_as_preview',
            service_id=service_id,
            notification_id=notification_id,
            filetype='png',
        ),
        page_count=page_count,
        show_recipient=True,
        redact_missing_personalisation=True,
    )
    template.values = get_all_personalisation_from_notification(notification)
    if notification['job']:
        job = job_api_client.get_job(service_id,
                                     notification['job']['id'])['data']
    else:
        job = None

    return render_template(
        'views/notifications/notification.html',
        finished=(notification['status']
                  in (DELIVERED_STATUSES + FAILURE_STATUSES)),
        uploaded_file_name='Report',
        template=template,
        job=job,
        updates_url=url_for(".view_notification_updates",
                            service_id=service_id,
                            notification_id=notification['id'],
                            status=request.args.get('status'),
                            help=get_help_argument()),
        partials=get_single_notification_partials(notification),
        created_by=notification.get('created_by'),
        created_at=notification['created_at'],
        help=get_help_argument(),
        estimated_letter_delivery_date=get_letter_timings(
            notification['created_at']).earliest_delivery,
        notification_id=notification['id'],
        can_receive_inbound=(current_service.has_permission('inbound_sms')),
        is_precompiled_letter=notification['template']
        ['is_precompiled_letter'])
Example #4
0
def test_replace_first_page_of_pdf_with_new_content():
    x2 = A4_WIDTH * mm
    y2 = A4_HEIGHT * mm

    assert _extract_text_from_first_page_of_pdf(BytesIO(single_sample_page),
                                                x1=0,
                                                y1=0,
                                                x2=x2,
                                                y2=y2) == 'Z'
    assert _extract_text_from_first_page_of_pdf(BytesIO(sample_pages),
                                                x1=0,
                                                y1=0,
                                                x2=x2,
                                                y2=y2) == 'A'

    modified_pdf = replace_first_page_of_pdf_with_new_content(
        BytesIO(sample_pages), BytesIO(single_sample_page))

    assert _extract_text_from_first_page_of_pdf(modified_pdf,
                                                x1=0,
                                                y1=0,
                                                x2=x2,
                                                y2=y2) == 'Z'
    assert pdf_page_count(modified_pdf) == 3
def view_notification(service_id, notification_id):
    notification = notification_api_client.get_notification(
        service_id, str(notification_id))
    notification['template'].update(
        {'reply_to_text': notification['reply_to_text']})

    personalisation = get_all_personalisation_from_notification(notification)

    if notification['template']['is_precompiled_letter']:
        try:
            file_contents = view_letter_notification_as_preview(
                service_id, notification_id, "pdf")
            page_count = pdf_page_count(io.BytesIO(file_contents))
        except PdfReadError:
            return render_template(
                'views/notifications/invalid_precompiled_letter.html',
                created_at=notification['created_at'])
    else:
        page_count = get_page_count_for_letter(notification['template'],
                                               values=personalisation)

    if notification.get('postage'):
        notification['template']['postage'] = notification['postage']
    template = get_template(
        notification['template'],
        current_service,
        letter_preview_url=url_for(
            '.view_letter_notification_as_preview',
            service_id=service_id,
            notification_id=notification_id,
            filetype='png',
        ),
        page_count=page_count,
        show_recipient=True,
        redact_missing_personalisation=True,
    )
    template.values = personalisation
    if notification['job']:
        job = job_api_client.get_job(service_id,
                                     notification['job']['id'])['data']
    else:
        job = None

    letter_print_day = get_letter_printing_statement(
        notification['status'], notification['created_at'])

    notification_created = parser.parse(
        notification['created_at']).replace(tzinfo=None)

    show_cancel_button = notification['notification_type'] == 'letter' and \
        letter_can_be_cancelled(notification['status'], notification_created)

    if get_help_argument() or request.args.get('help') == '0':
        # help=0 is set when you’ve just sent a notification. We
        # only want to show the back link when you’ve navigated to a
        # notification, not when you’ve just sent it.
        back_link = None
    elif request.args.get('from_job'):
        back_link = url_for(
            'main.view_job',
            service_id=current_service.id,
            job_id=request.args.get('from_job'),
        )
    else:
        back_link = url_for(
            'main.view_notifications',
            service_id=current_service.id,
            message_type=template.template_type,
            status='sending,delivered,failed',
        )

    return render_template(
        'views/notifications/notification.html',
        finished=(notification['status']
                  in (DELIVERED_STATUSES + FAILURE_STATUSES)),
        notification_status=notification['status'],
        uploaded_file_name='Report',
        template=template,
        job=job,
        updates_url=url_for(".view_notification_updates",
                            service_id=service_id,
                            notification_id=notification['id'],
                            status=request.args.get('status'),
                            help=get_help_argument()),
        partials=get_single_notification_partials(notification),
        created_by=notification.get('created_by'),
        created_at=notification['created_at'],
        updated_at=notification['updated_at'],
        help=get_help_argument(),
        estimated_letter_delivery_date=get_letter_timings(
            notification['created_at'],
            postage=notification['postage']).earliest_delivery,
        notification_id=notification['id'],
        postage=notification['postage'],
        can_receive_inbound=(current_service.has_permission('inbound_sms')),
        is_precompiled_letter=notification['template']
        ['is_precompiled_letter'],
        letter_print_day=letter_print_day,
        show_cancel_button=show_cancel_button,
        sent_with_test_key=(notification.get('key_type') == KEY_TYPE_TEST),
        back_link=back_link,
    )
def get_page_count(pdf):
    return pdf_page_count(io.BytesIO(pdf))
Example #7
0
def get_page_count(pdf):
    pages = pdf_page_count(io.BytesIO(pdf))
    pages_per_sheet = 2
    billable_units = math.ceil(pages / pages_per_sheet)
    return billable_units
Example #8
0
def upload_letter(service_id):
    form = PDFUploadForm()
    error = {}

    if form.validate_on_submit():
        pdf_file_bytes = form.file.data.read()
        original_filename = form.file.data.filename

        if current_app.config['ANTIVIRUS_ENABLED']:
            virus_free = antivirus_client.scan(BytesIO(pdf_file_bytes))
            if not virus_free:
                return invalid_upload_error('Your file contains a virus')

        if len(pdf_file_bytes) > MAX_FILE_UPLOAD_SIZE:
            return invalid_upload_error('Your file is too big',
                                        'Files must be smaller than 2MB.')

        try:
            # TODO: get page count from the sanitise response once template preview handles malformed files nicely
            page_count = pdf_page_count(BytesIO(pdf_file_bytes))
        except PdfReadError:
            current_app.logger.info(
                'Invalid PDF uploaded for service_id: {}'.format(service_id))
            return invalid_upload_error(
                "There’s a problem with your file",
                'Notify cannot read this PDF.<br>Save a new copy of your file and try again.'
            )

        upload_id = uuid.uuid4()
        file_location = get_transient_letter_file_location(
            service_id, upload_id)

        try:
            response = sanitise_letter(
                BytesIO(pdf_file_bytes),
                allow_international_letters=current_service.has_permission(
                    'international_letters'),
            )
            response.raise_for_status()
        except RequestException as ex:
            if ex.response is not None and ex.response.status_code == 400:
                validation_failed_message = response.json().get('message')
                invalid_pages = response.json().get('invalid_pages')

                status = 'invalid'
                upload_letter_to_s3(pdf_file_bytes,
                                    file_location=file_location,
                                    status=status,
                                    page_count=page_count,
                                    filename=original_filename,
                                    message=validation_failed_message,
                                    invalid_pages=invalid_pages)
            else:
                raise ex
        else:
            response = response.json()
            recipient = response['recipient_address']
            status = 'valid'
            file_contents = base64.b64decode(response['file'].encode())

            upload_letter_to_s3(file_contents,
                                file_location=file_location,
                                status=status,
                                page_count=page_count,
                                filename=original_filename,
                                recipient=recipient)

        return redirect(
            url_for(
                'main.uploaded_letter_preview',
                service_id=current_service.id,
                file_id=upload_id,
            ))

    if form.file.errors:
        error = _get_error_from_upload_form(form.file.errors[0])

    return render_template('views/uploads/choose-file.html',
                           error=error,
                           form=form)
Example #9
0
def test_pdf_page_count_src_pdf_not_a_pdf():
    with pytest.raises(PdfReadError):
        file_data = base64.b64decode(not_pdf)
        pdf_page_count(BytesIO(file_data))
Example #10
0
def test_pdf_page_count_src_pdf_has_multiple_pages():
    file_data = base64.b64decode(multi_page_pdf)
    num = pdf_page_count(BytesIO(file_data))
    assert num == 10
Example #11
0
def test_pdf_page_count_src_pdf_has_one_page():
    file_data = base64.b64decode(one_page_pdf)
    num = pdf_page_count(BytesIO(file_data))
    assert num == 1
Example #12
0
def test_pdf_page_count_src_pdf_is_null():
    with pytest.raises(PdfReadError):
        pdf_page_count(None)
def view_notification(service_id, notification_id):
    notification = notification_api_client.get_notification(service_id, str(notification_id))
    notification["template"].update({"reply_to_text": notification["reply_to_text"]})

    personalisation = get_all_personalisation_from_notification(notification)

    if notification["template"]["is_precompiled_letter"]:
        try:
            file_contents = view_letter_notification_as_preview(service_id, notification_id, "pdf")
            page_count = pdf_page_count(io.BytesIO(file_contents))
        except PdfReadError:
            return render_template(
                "views/notifications/invalid_precompiled_letter.html",
                created_at=notification["created_at"],
            )
    else:
        page_count = get_page_count_for_letter(notification["template"], values=personalisation)

    if notification.get("postage"):
        notification["template"]["postage"] = notification["postage"]
    template = get_template(
        notification["template"],
        current_service,
        letter_preview_url=url_for(
            ".view_letter_notification_as_preview",
            service_id=service_id,
            notification_id=notification_id,
            filetype="png",
        ),
        page_count=page_count,
        show_recipient=True,
        redact_missing_personalisation=True,
    )
    template.values = personalisation
    if notification["job"]:
        job = job_api_client.get_job(service_id, notification["job"]["id"])["data"]
    else:
        job = None

    letter_print_day = get_letter_printing_statement(notification["status"], notification["created_at"])

    notification_created = parser.parse(notification["created_at"]).replace(tzinfo=None)

    show_cancel_button = notification["notification_type"] == "letter" and letter_can_be_cancelled(
        notification["status"], notification_created
    )

    if get_help_argument() or request.args.get("help") == "0":
        # help=0 is set when you’ve just sent a notification. We
        # only want to show the back link when you’ve navigated to a
        # notification, not when you’ve just sent it.
        back_link = None
    elif request.args.get("from_job"):
        back_link = url_for(
            "main.view_job",
            service_id=current_service.id,
            job_id=request.args.get("from_job"),
        )
    else:
        back_link = url_for(
            "main.view_notifications",
            service_id=current_service.id,
            message_type=template.template_type,
            status="sending,delivered,failed",
        )

    return render_template(
        "views/notifications/notification.html",
        finished=(notification["status"] in (DELIVERED_STATUSES + FAILURE_STATUSES)),
        notification_status=notification["status"],
        uploaded_file_name="Report",
        template=template,
        job=job,
        updates_url=url_for(
            ".view_notification_updates",
            service_id=service_id,
            notification_id=notification["id"],
            status=request.args.get("status"),
            help=get_help_argument(),
        ),
        partials=get_single_notification_partials(notification),
        created_by=notification.get("created_by"),
        created_at=notification["created_at"],
        updated_at=notification["updated_at"],
        help=get_help_argument(),
        estimated_letter_delivery_date=get_letter_timings(
            notification["created_at"], postage=notification["postage"]
        ).earliest_delivery,
        notification_id=notification["id"],
        postage=notification["postage"],
        can_receive_inbound=(current_service.has_permission("inbound_sms")),
        is_precompiled_letter=notification["template"]["is_precompiled_letter"],
        letter_print_day=letter_print_day,
        show_cancel_button=show_cancel_button,
        sent_with_test_key=(notification.get("key_type") == KEY_TYPE_TEST),
        back_link=back_link,
        just_sent=request.args.get("just_sent"),
        attachments=get_attachments(notification, "attach").values(),
    )