def get_pdf_for_notification(notification_id):
    _data = {"notification_id": notification_id}
    validate(_data, notification_by_id)
    notification = notifications_dao.get_notification_by_id(
        notification_id, authenticated_service.id, _raise=True)

    if notification.notification_type != LETTER_TYPE:
        raise BadRequestError(message="Notification is not a letter")

    if notification.status == NOTIFICATION_VIRUS_SCAN_FAILED:
        raise BadRequestError(message='Document did not pass the virus scan')

    if notification.status == NOTIFICATION_TECHNICAL_FAILURE:
        raise BadRequestError(
            message='PDF not available for letters in status {}'.format(
                notification.status))

    if notification.status == NOTIFICATION_PENDING_VIRUS_CHECK:
        raise PDFNotReadyError()

    try:
        pdf_data, metadata = get_letter_pdf_and_metadata(notification)
    except Exception:
        raise PDFNotReadyError()

    return send_file(filename_or_fp=BytesIO(pdf_data),
                     mimetype='application/pdf')
Exemplo n.º 2
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
Exemplo n.º 3
0
def validate_and_format_recipient(send_to,
                                  key_type,
                                  service,
                                  notification_type,
                                  allow_safelisted_recipients=True):
    if send_to is None:
        raise BadRequestError(message="Recipient can't be empty")

    service_can_send_to_recipient(send_to, key_type, service,
                                  allow_safelisted_recipients)

    if notification_type == SMS_TYPE:
        international_phone_info = get_international_phone_info(send_to)

        if international_phone_info.international and INTERNATIONAL_SMS_TYPE not in [
                p.permission for p in service.permissions
        ]:
            raise BadRequestError(
                message="Cannot send to international mobile numbers")

        return validate_and_format_phone_number(
            number=send_to,
            international=international_phone_info.international)
    elif notification_type == EMAIL_TYPE:
        return validate_and_format_email_address(email_address=send_to)
Exemplo n.º 4
0
def create_broadcast():

    check_service_has_permission(
        BROADCAST_TYPE,
        authenticated_service.permissions,
    )

    if request.content_type != 'application/cap+xml':
        raise BadRequestError(
            message=f'Content type {request.content_type} not supported',
            status_code=415,
        )

    cap_xml = request.get_data()

    if not validate_xml(cap_xml, 'CAP-v1.2.xsd'):
        raise BadRequestError(
            message='Request data is not valid CAP XML',
            status_code=400,
        )

    broadcast_json = cap_xml_to_dict(cap_xml)

    validate(broadcast_json, post_broadcast_schema)

    polygons = Polygons(
        list(
            chain.from_iterable(
                (area['polygons'] for area in broadcast_json['areas']))))

    broadcast_message = BroadcastMessage(
        service_id=authenticated_service.id,
        content=broadcast_json['content'],
        reference=broadcast_json['reference'],
        areas={
            'areas': [area['name'] for area in broadcast_json['areas']],
            'simple_polygons':
            polygons.smooth.simplify.as_coordinate_pairs_long_lat,
        },
        status=BroadcastStatusType.PENDING_APPROVAL,
        api_key_id=api_user.id,
        stubbed=authenticated_service.restricted
        # The client may pass in broadcast_json['expires'] but it’s
        # simpler for now to ignore it and have the rules around expiry
        # for broadcasts created with the API match those created from
        # the admin app
    )

    dao_save_object(broadcast_message)

    current_app.logger.info(
        f'Broadcast message {broadcast_message.id} created for service '
        f'{authenticated_service.id} with reference {broadcast_json["reference"]}'
    )

    return jsonify(broadcast_message.serialize()), 201
Exemplo n.º 5
0
def get_valid_json():
    try:
        request_json = request.get_json(force=True)
    except BadRequest:
        raise BadRequestError(message="Invalid JSON supplied in POST data",
                              status_code=400)
    return request_json or {}
Exemplo n.º 6
0
def validate_template(template_id,
                      personalisation,
                      service,
                      notification_type,
                      check_char_count=True):
    try:
        template = SerialisedTemplate.from_id_and_service_id(
            template_id, service.id)
    except NoResultFound:
        message = 'Template not found'
        raise BadRequestError(message=message, fields=[{'template': message}])

    check_template_is_for_notification_type(notification_type,
                                            template.template_type)
    check_template_is_active(template)

    template_with_content = create_content_for_notification(
        template, personalisation)

    check_notification_content_is_not_empty(template_with_content)

    # validating the template in post_notifications happens before the file is uploaded for doc download,
    # which means the length of the message can be exceeded because it's including the file.
    # The document download feature is only available through the api.
    if check_char_count:
        check_is_message_too_long(template_with_content)

    return template, template_with_content
Exemplo n.º 7
0
def process_document_uploads(personalisation_data, service, simulated, template_id):
    file_keys = [k for k, v in (personalisation_data or {}).items() if isinstance(v, dict) and "file" in v]
    if not file_keys:
        return personalisation_data

    personalisation_data = personalisation_data.copy()

    check_service_has_permission(UPLOAD_DOCUMENT, authenticated_service.permissions)

    for key in file_keys:
        if simulated:
            personalisation_data[key] = document_download_client.get_upload_url(service.id) + "/test-document"
        else:
            try:
                personalisation_data[key] = document_download_client.upload_document(service.id, personalisation_data[key])
            except DocumentDownloadError as e:
                raise BadRequestError(message=e.message, status_code=e.status_code)

    if not simulated:
        save_stats_for_attachments(
            [v for k, v in personalisation_data.items() if k in file_keys],
            service.id,
            template_id,
        )

    return personalisation_data
Exemplo n.º 8
0
def process_document_uploads(personalisation_data, service, simulated=False):
    file_keys = [
        k for k, v in (personalisation_data or {}).items()
        if isinstance(v, dict) and 'file' in v
    ]
    if not file_keys:
        return personalisation_data

    personalisation_data = personalisation_data.copy()

    check_service_has_permission(UPLOAD_DOCUMENT,
                                 authenticated_service.permissions)

    for key in file_keys:
        if simulated:
            personalisation_data[
                key] = document_download_client.get_upload_url(
                    service.id) + '/test-document'
        else:
            try:
                personalisation_data[
                    key] = document_download_client.upload_document(
                        service.id, personalisation_data[key]['file'])
            except DocumentDownloadError as e:
                raise BadRequestError(message=e.message,
                                      status_code=e.status_code)

    return personalisation_data
def process_document_uploads(personalisation_data, service, simulated=False):
    """
    Returns modified personalisation dict and a count of document uploads. If there are no document uploads, returns
    a count of `None` rather than `0`.
    """
    file_keys = [
        k for k, v in (personalisation_data or {}).items()
        if isinstance(v, dict) and 'file' in v
    ]
    if not file_keys:
        return personalisation_data, None

    personalisation_data = personalisation_data.copy()

    check_if_service_can_send_files_by_email(
        service_contact_link=authenticated_service.contact_link,
        service_id=authenticated_service.id)

    for key in file_keys:
        if simulated:
            personalisation_data[
                key] = document_download_client.get_upload_url(
                    service.id) + '/test-document'
        else:
            try:
                personalisation_data[
                    key] = document_download_client.upload_document(
                        service.id, personalisation_data[key]['file'])
            except DocumentDownloadError as e:
                raise BadRequestError(message=e.message,
                                      status_code=e.status_code)

    return personalisation_data, len(file_keys)
Exemplo n.º 10
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'])
    except ValueError:
        raise BadRequestError(
            message='Cannot decode letter content (invalid base64 encoding)',
            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)

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

    # call task to add the filename to anti virus queue
    if current_app.config['ANTIVIRUS_ENABLED']:
        notify_celery.send_task(
            name=TaskNames.SCAN_FILE,
            kwargs={'filename': filename},
            queue=QueueNames.ANTIVIRUS,
        )
    else:
        # stub out antivirus in dev
        sanitise_letter.apply_async([filename], queue=QueueNames.LETTERS)

    return notification
Exemplo n.º 11
0
def check_if_service_can_send_files_by_email(service_contact_link, service_id):
    if not service_contact_link:
        raise BadRequestError(
            message=
            f"Send files by email has not been set up - add contact details for your service at "
            f"{current_app.config['ADMIN_BASE_URL']}/services/{service_id}/service-settings/send-files-by-email"
        )
Exemplo n.º 12
0
def check_template_exists_by_id_and_service(template_id, service):
    try:
        return templates_dao.dao_get_template_by_id_and_service_id(
            template_id=template_id, service_id=service.id)
    except NoResultFound:
        message = "Template not found"
        raise BadRequestError(message=message, fields=[{"template": message}])
Exemplo n.º 13
0
def process_letter_notification(*,
                                letter_data,
                                api_key,
                                template,
                                reply_to_text,
                                precompiled=False):
    if api_key.key_type == KEY_TYPE_TEAM:
        raise BadRequestError(
            message='Cannot send letters with a team api key', status_code=403)

    if not api_key.service.research_mode and api_key.service.restricted and api_key.key_type != KEY_TYPE_TEST:
        raise BadRequestError(
            message='Cannot send letters when service is in trial mode',
            status_code=403)

    if precompiled:
        return process_precompiled_letter_notifications(
            letter_data=letter_data,
            api_key=api_key,
            template=template,
            reply_to_text=reply_to_text)

    test_key = api_key.key_type == KEY_TYPE_TEST

    # 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 test_key else NOTIFICATION_SENDING
    queue = QueueNames.CREATE_LETTERS_PDF if not test_key else QueueNames.RESEARCH_MODE

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

    create_letters_pdf.apply_async([str(notification.id)], queue=queue)

    if test_key:
        if current_app.config['NOTIFY_ENVIRONMENT'] in [
                'preview', 'development'
        ]:
            create_fake_letter_response_file.apply_async(
                (notification.reference, ), queue=queue)
        else:
            update_notification_status_by_reference(notification.reference,
                                                    NOTIFICATION_DELIVERED)

    return notification
Exemplo n.º 14
0
def check_service_letter_contact_id(service_id, letter_contact_id, notification_type):
    if letter_contact_id:
        try:
            return dao_get_letter_contact_by_id(service_id, letter_contact_id).contact_block
        except NoResultFound:
            message = 'letter_contact_id {} does not exist in database for service id {}'\
                .format(letter_contact_id, service_id)
            raise BadRequestError(message=message)
Exemplo n.º 15
0
def check_service_sms_sender_id(service_id, sms_sender_id, notification_type):
    if sms_sender_id:
        try:
            return dao_get_service_sms_senders_by_id(service_id, sms_sender_id).sms_sender
        except NoResultFound:
            message = 'sms_sender_id {} does not exist in database for service id {}'\
                .format(sms_sender_id, service_id)
            raise BadRequestError(message=message)
Exemplo n.º 16
0
def check_service_email_reply_to_id(service_id, reply_to_id, notification_type):
    if reply_to_id:
        try:
            return dao_get_reply_to_by_id(service_id, reply_to_id).email_address
        except NoResultFound:
            message = 'email_reply_to_id {} does not exist in database for service id {}'\
                .format(reply_to_id, service_id)
            raise BadRequestError(message=message)
Exemplo n.º 17
0
def validate_created_by(service, created_by_id):
    user = get_user_by_id(created_by_id)
    if service not in user.services:
        message = 'Can’t create notification - {} is not part of the "{}" service'.format(
            user.name,
            service.name
        )
        raise BadRequestError(message=message)
Exemplo n.º 18
0
def check_template_is_active(template):
    if template.archived:
        raise BadRequestError(
            fields=[{
                "template": "Template has been deleted"
            }],
            message="Template has been deleted",
        )
Exemplo n.º 19
0
def service_can_send_to_recipient(send_to, key_type, service, allow_whitelisted_recipients=True):
    if not service_allowed_to_send_to(send_to, service, key_type, allow_whitelisted_recipients):
        if key_type == KEY_TYPE_TEAM:
            message = 'Can’t send to this recipient using a team-only API key'
        else:
            message = (
                'Can’t send to this recipient when service is in trial mode '
                '– see https://www.notifications.service.gov.uk/trial-mode'
            )
        raise BadRequestError(message=message)
Exemplo n.º 20
0
def post_bulk():
    try:
        request_json = request.get_json()
    except werkzeug.exceptions.BadRequest as e:
        raise BadRequestError(message=f"Error decoding arguments: {e.description}", status_code=400)

    max_rows = current_app.config["CSV_MAX_ROWS"]
    form = validate(request_json, post_bulk_request(max_rows))

    if len([source for source in [form.get("rows"), form.get("csv")] if source]) != 1:
        raise BadRequestError(message="You should specify either rows or csv", status_code=400)
    template = validate_template_exists(form["template_id"], authenticated_service)
    check_service_has_permission(template.template_type, authenticated_service.permissions)

    remaining_messages = authenticated_service.message_limit - fetch_todays_total_message_count(authenticated_service.id)

    form["validated_sender_id"] = validate_sender_id(template, form.get("reply_to_id"))

    try:
        if form.get("rows"):
            output = StringIO()
            writer = csv.writer(output)
            writer.writerows(form["rows"])
            file_data = output.getvalue()
        else:
            file_data = form["csv"]

        recipient_csv = RecipientCSV(
            file_data,
            template_type=template.template_type,
            placeholders=template._as_utils_template().placeholders,
            max_rows=max_rows,
            safelist=safelisted_members(authenticated_service, api_user.key_type),
            remaining_messages=remaining_messages,
        )
    except csv.Error as e:
        raise BadRequestError(message=f"Error converting to CSV: {str(e)}", status_code=400)

    check_for_csv_errors(recipient_csv, max_rows, remaining_messages)
    job = create_bulk_job(authenticated_service, api_user, template, form, recipient_csv)

    return jsonify(data=job_schema.dump(job).data), 201
Exemplo n.º 21
0
def service_can_send_to_recipient(send_to, key_type, service, allow_safelisted_recipients=True):
    if not service_allowed_to_send_to(send_to, service, key_type, allow_safelisted_recipients):
        # FIXME: hard code it for now until we can get en/fr specific links and text
        if key_type == KEY_TYPE_TEAM:
            message = 'Can’t send to this recipient using a team-only API key '\
                      f'- see {get_document_url("en", "keys.html#team-and-safelist")}'
        else:
            message = (
                'Can’t send to this recipient when service is in trial mode '
                f'– see {get_document_url("en", "keys.html#live")}'
            )
        raise BadRequestError(message=message)
Exemplo n.º 22
0
def check_is_message_too_long(template_with_content):
    if template_with_content.is_message_too_long():
        message = "Your message is too long. "
        if template_with_content.template_type == SMS_TYPE:
            message += (
                f"Text messages cannot be longer than {SMS_CHAR_COUNT_LIMIT} characters. "
                f"Your message is {template_with_content.content_count_without_prefix} characters long."
            )
        elif template_with_content.template_type == EMAIL_TYPE:
            message += (
                f"Emails cannot be longer than 2000000 bytes. "
                f"Your message is {template_with_content.content_size_in_bytes} bytes."
            )
        raise BadRequestError(message=message)
Exemplo n.º 23
0
def check_if_service_can_send_to_number(service, number):
    international_phone_info = get_international_phone_info(number)

    if service.permissions and isinstance(service.permissions[0], ServicePermission):
        permissions = [p.permission for p in service.permissions]
    else:
        permissions = service.permissions

    if (
        # if number is international and not a crown dependency
        international_phone_info.international and not international_phone_info.crown_dependency
    ) and INTERNATIONAL_SMS_TYPE not in permissions:
        raise BadRequestError(message="Cannot send to international mobile numbers")
    else:
        return international_phone_info
Exemplo n.º 24
0
def validate_and_format_recipient(send_to, key_type, service, notification_type, allow_guest_list_recipients=True):
    if send_to is None:
        raise BadRequestError(message="Recipient can't be empty")

    service_can_send_to_recipient(send_to, key_type, service, allow_guest_list_recipients)

    if notification_type == SMS_TYPE:
        international_phone_info = check_if_service_can_send_to_number(service, send_to)

        return validate_and_format_phone_number(
            number=send_to,
            international=international_phone_info.international
        )
    elif notification_type == EMAIL_TYPE:
        return validate_and_format_email_address(email_address=send_to)
Exemplo n.º 25
0
def get_reply_to_text(notification_type, sender_id, service, template):
    reply_to = None
    if sender_id:
        try:
            if notification_type == EMAIL_TYPE:
                message = "Reply to email address not found"
                reply_to = dao_get_reply_to_by_id(service.id, sender_id).email_address
            elif notification_type == SMS_TYPE:
                message = "SMS sender not found"
                reply_to = dao_get_service_sms_senders_by_id(service.id, sender_id).get_reply_to_text()
        except NoResultFound:
            raise BadRequestError(message=message)
    else:
        reply_to = template.get_reply_to_text()
    return reply_to
Exemplo n.º 26
0
def check_for_csv_errors(recipient_csv, max_rows, remaining_messages):
    nb_rows = len(recipient_csv)

    if recipient_csv.has_errors:
        if recipient_csv.missing_column_headers:
            raise BadRequestError(
                message=f"Missing column headers: {', '.join(sorted(recipient_csv.missing_column_headers))}",
                status_code=400,
            )
        if recipient_csv.duplicate_recipient_column_headers:
            raise BadRequestError(
                message=f"Duplicate column headers: {', '.join(sorted(recipient_csv.duplicate_recipient_column_headers))}",
                status_code=400,
            )
        if recipient_csv.more_rows_than_can_send:
            raise BadRequestError(
                message=f"You only have {remaining_messages} remaining messages before you reach your daily limit. You've tried to send {nb_rows} messages.",
                status_code=400,
            )

        if recipient_csv.too_many_rows:
            raise BadRequestError(
                message=f"Too many rows. Maximum number of rows allowed is {max_rows}",
                status_code=400,
            )
        if not recipient_csv.allowed_to_send_to:
            if api_user.key_type == KEY_TYPE_TEAM:
                explanation = "because you used a team and safelist API key."
            if authenticated_service.restricted:
                explanation = (
                    "because your service is in trial mode. You can only send to members of your team and your safelist."
                )
            raise BadRequestError(
                message=f"You cannot send to these recipients {explanation}",
                status_code=400,
            )
        if recipient_csv.rows_with_errors:

            def row_error(row):
                content = []
                for header in [header for header in recipient_csv.column_headers if row[header].error]:
                    if row[header].recipient_error:
                        content.append(f"`{header}`: invalid recipient")
                    else:
                        content.append(f"`{header}`: {row[header].error}")
                return f"Row {row.index} - {','.join(content)}"

            errors = ". ".join([row_error(row) for row in recipient_csv.initial_rows_with_errors])
            raise BadRequestError(
                message=f"Some rows have errors. {errors}.",
                status_code=400,
            )
        else:
            raise NotImplementedError("Got errors but code did not handle")
Exemplo n.º 27
0
def validate_template(template_id, personalisation, service,
                      notification_type):
    try:
        template = templates_dao.dao_get_template_by_id_and_service_id(
            template_id=template_id, service_id=service.id)
    except NoResultFound:
        message = 'Template not found'
        raise BadRequestError(message=message, fields=[{'template': message}])

    check_template_is_for_notification_type(notification_type,
                                            template.template_type)
    check_template_is_active(template)
    template_with_content = create_content_for_notification(
        template, personalisation)
    if template.template_type == SMS_TYPE:
        check_sms_content_char_count(template_with_content.content_count)
    return template, template_with_content
Exemplo n.º 28
0
def validate_template(template_id, personalisation, service, notification_type):

    try:
        template = SerialisedTemplate.from_id_and_service_id(template_id, service.id)
    except NoResultFound:
        message = 'Template not found'
        raise BadRequestError(message=message,
                              fields=[{'template': message}])

    check_template_is_for_notification_type(notification_type, template.template_type)
    check_template_is_active(template)

    template_with_content = create_content_for_notification(template, personalisation)

    check_notification_content_is_not_empty(template_with_content)

    check_content_char_count(template_with_content)

    return template, template_with_content
Exemplo n.º 29
0
def check_service_has_permission(notify_type, permissions):
    if not service_has_permission(notify_type, permissions):
        raise BadRequestError(
            message="Service is not allowed to send {}".format(
                get_public_notify_type_text(notify_type, plural=True)))
Exemplo n.º 30
0
def check_template_is_for_notification_type(notification_type, template_type):
    if notification_type != template_type:
        message = "{0} template is not suitable for {1} notification".format(
            template_type, notification_type)
        raise BadRequestError(fields=[{'template': message}], message=message)