Beispiel #1
0
def _update_broadcast_message(broadcast_message, new_status, updating_user):
    if updating_user not in broadcast_message.service.users:
        raise InvalidRequest(
            f'User {updating_user.id} cannot approve broadcast_message {broadcast_message.id} from other service',
            status_code=400)

    if new_status not in BroadcastStatusType.ALLOWED_STATUS_TRANSITIONS[
            broadcast_message.status]:
        raise InvalidRequest(
            f'Cannot move broadcast_message {broadcast_message.id} from {broadcast_message.status} to {new_status}',
            status_code=400)

    if new_status == BroadcastStatusType.BROADCASTING:
        # TODO: Remove this platform admin shortcut when the feature goes live
        if updating_user == broadcast_message.created_by and not (
                # platform admins and trial mode services can approve their own broadcasts
                updating_user.platform_admin
                or broadcast_message.service.restricted):
            raise InvalidRequest(
                f'User {updating_user.id} cannot approve their own broadcast_message {broadcast_message.id}',
                status_code=400)
        elif not broadcast_message.areas:
            raise InvalidRequest(
                f'broadcast_message {broadcast_message.id} has no selected areas and so cannot be broadcasted.',
                status_code=400)
        else:
            broadcast_message.approved_at = datetime.utcnow()
            broadcast_message.approved_by = updating_user

    if new_status == BroadcastStatusType.CANCELLED:
        broadcast_message.cancelled_at = datetime.utcnow()
        broadcast_message.cancelled_by = updating_user

    current_app.logger.info(
        f'broadcast_message {broadcast_message.id} moving from {broadcast_message.status} to {new_status}'
    )
    broadcast_message.status = new_status
def sns_callback_handler():
    message_type = request.headers.get('x-amz-sns-message-type')
    try:
        verify_message_type(message_type)
    except InvalidMessageTypeException:
        raise InvalidRequest("SES-SNS callback failed: invalid message type",
                             400)

    try:
        message = json.loads(request.data)
    except decoder.JSONDecodeError:
        raise InvalidRequest("SES-SNS callback failed: invalid JSON given",
                             400)

    try:
        validatesns.validate(message, get_certificate=get_certificate)
    except validatesns.ValidationError:
        raise InvalidRequest("SES-SNS callback failed: validation failed", 400)

    if message.get('Type') == 'SubscriptionConfirmation':
        url = message.get('SubscribeURL')
        response = requests.get(url)
        try:
            response.raise_for_status()
        except Exception as e:
            current_app.logger.warning("Response: {}".format(response.text))
            raise e

        return jsonify(result="success",
                       message="SES-SNS auto-confirm callback succeeded"), 200

    process_ses_results.apply_async([{
        "Message": message.get("Message")
    }],
                                    queue=QueueNames.NOTIFY)

    return jsonify(result="success", message="SES-SNS callback succeeded"), 200
Beispiel #3
0
def _update_broadcast_message(broadcast_message, new_status, updating_user):
    if updating_user not in broadcast_message.service.users:
        raise InvalidRequest(
            f'User {updating_user.id} cannot approve broadcast_message {broadcast_message.id} from other service',
            status_code=400
        )

    if new_status not in BroadcastStatusType.ALLOWED_STATUS_TRANSITIONS[broadcast_message.status]:
        raise InvalidRequest(
            f'Cannot move broadcast_message {broadcast_message.id} from {broadcast_message.status} to {new_status}',
            status_code=400
        )

    if new_status == BroadcastStatusType.BROADCASTING:
        # training mode services can approve their own broadcasts
        if updating_user == broadcast_message.created_by and not broadcast_message.service.restricted:
            raise InvalidRequest(
                f'User {updating_user.id} cannot approve their own broadcast_message {broadcast_message.id}',
                status_code=400
            )
        elif len(broadcast_message.areas['simple_polygons']) == 0:
            raise InvalidRequest(
                f'broadcast_message {broadcast_message.id} has no selected areas and so cannot be broadcasted.',
                status_code=400
            )
        else:
            broadcast_message.approved_at = datetime.utcnow()
            broadcast_message.approved_by = updating_user

    if new_status == BroadcastStatusType.CANCELLED:
        broadcast_message.cancelled_at = datetime.utcnow()
        broadcast_message.cancelled_by = updating_user

    current_app.logger.info(
        f'broadcast_message {broadcast_message.id} moving from {broadcast_message.status} to {new_status}'
    )
    broadcast_message.status = new_status
Beispiel #4
0
def modify_service_data_retention(service_id, data_retention_id):
    form = validate(request.get_json(), update_service_data_retention_request)

    update_count = update_service_data_retention(
        service_data_retention_id=data_retention_id,
        service_id=service_id,
        days_of_retention=form.get("days_of_retention"))
    if update_count == 0:
        raise InvalidRequest(
            message=
            "The service data retention for id: {} was not found for service: {}"
            .format(data_retention_id, service_id),
            status_code=404)

    return '', 204
Beispiel #5
0
def update_service_sms_sender(service_id, sms_sender_id):
    form = validate(request.get_json(), add_service_sms_sender_request)

    sms_sender_to_update = dao_get_service_sms_senders_by_id(service_id=service_id,
                                                             service_sms_sender_id=sms_sender_id)
    if sms_sender_to_update.inbound_number_id and form['sms_sender'] != sms_sender_to_update.sms_sender:
        raise InvalidRequest("You can not change the inbound number for service {}".format(service_id),
                             status_code=400)

    new_sms_sender = dao_update_service_sms_sender(service_id=service_id,
                                                   service_sms_sender_id=sms_sender_id,
                                                   is_default=form['is_default'],
                                                   sms_sender=form['sms_sender']
                                                   )
    return jsonify(new_sms_sender.serialize()), 200
Beispiel #6
0
def create_smtp_relay(service_id):
    service = dao_fetch_service_by_id(service_id)

    alphabet = "1234567890abcdefghijklmnopqrstuvwxyz"

    if service.smtp_user is None:
        user_id = generate(alphabet, size=7)
        credentials = smtp_add(user_id)
        service.smtp_user = credentials["iam"]
        dao_update_service(service)
        return jsonify(credentials), 201
    else:
        raise InvalidRequest(
            message="SMTP user already exists",
            status_code=500)
Beispiel #7
0
def preview_template_by_id_and_service_id(service_id, template_id):
    fetched_template = dao_get_template_by_id_and_service_id(template_id=template_id, service_id=service_id)
    data = template_schema.dump(fetched_template).data
    template_object = get_template_instance(data, values=request.args.to_dict())

    if template_object.missing_data:
        raise InvalidRequest(
            {'template': [
                'Missing personalisation: {}'.format(", ".join(template_object.missing_data))
            ]}, status_code=400
        )

    data['subject'], data['content'] = template_object.subject, str(template_object)

    return jsonify(data)
Beispiel #8
0
def update_broadcast_message(service_id, broadcast_message_id):
    data = request.get_json()

    validate(data, update_broadcast_message_schema)

    broadcast_message = dao_get_broadcast_message_by_id_and_service_id(
        broadcast_message_id, service_id)

    if broadcast_message.status not in BroadcastStatusType.PRE_BROADCAST_STATUSES:
        raise InvalidRequest(
            f'Cannot update broadcast_message {broadcast_message.id} while it has status {broadcast_message.status}',
            status_code=400)

    if ('areas' in data and 'simple_polygons' not in data) or (
            'areas' not in data and 'simple_polygons' in data):
        raise InvalidRequest(
            f'Cannot update broadcast_message {broadcast_message.id}, areas or polygons are missing.',
            status_code=400)

    if 'personalisation' in data:
        broadcast_message.personalisation = data['personalisation']
    if 'starts_at' in data:
        broadcast_message.starts_at = _parse_nullable_datetime(
            data['starts_at'])
    if 'finishes_at' in data:
        broadcast_message.finishes_at = _parse_nullable_datetime(
            data['finishes_at'])
    if 'areas' in data and 'simple_polygons' in data:
        broadcast_message.areas = {
            "areas": data["areas"],
            "simple_polygons": data["simple_polygons"]
        }

    dao_save_object(broadcast_message)

    return jsonify(broadcast_message.serialize()), 200
Beispiel #9
0
def create_service_data_retention(service_id):
    form = validate(request.get_json(), add_service_data_retention_request)
    try:
        new_data_retention = insert_service_data_retention(
            service_id=service_id,
            notification_type=form.get("notification_type"),
            days_of_retention=form.get("days_of_retention")
        )
    except IntegrityError:
        raise InvalidRequest(
            message="Service already has data retention for {} notification type".format(form.get("notification_type")),
            status_code=400
        )

    return jsonify(result=new_data_retention.serialize()), 201
Beispiel #10
0
def get_whitelist(service_id):
    from app.models import (EMAIL_TYPE, MOBILE_TYPE)
    service = dao_fetch_service_by_id(service_id)

    if not service:
        raise InvalidRequest("Service does not exist", status_code=404)

    whitelist = dao_fetch_service_whitelist(service.id)
    return jsonify(email_addresses=[
        item.recipient for item in whitelist
        if item.recipient_type == EMAIL_TYPE
    ],
                   phone_numbers=[
                       item.recipient for item in whitelist
                       if item.recipient_type == MOBILE_TYPE
                   ])
Beispiel #11
0
 def it_logs_429_status_code_response(self, mocker, db_session,
                                      sample_email, sample_member):
     mocker.patch('app.na_celery.email_tasks.send_email',
                  side_effect=InvalidRequest('Minute limit reached', 429))
     mock_logger_error = mocker.patch(
         'app.na_celery.email_tasks.current_app.logger.error')
     mock_send_periodic_task = mocker.patch(
         'app.na_celery.email_tasks.send_periodic_emails.apply_async')
     with pytest.raises(expected_exception=InvalidRequest):
         send_emails(sample_email.id)
     assert mock_logger_error.called
     args = mock_logger_error.call_args[0]
     assert args[0] == 'Email limit reached: %r'
     assert args[1] == 'Minute limit reached'
     assert mock_send_periodic_task.called
     assert mock_send_periodic_task.call_args == call(countdown=60)
Beispiel #12
0
def update_reply_to_email_address(service_id, reply_to_id, email_address,
                                  is_default):
    old_default = _get_existing_default(service_id)
    if is_default:
        _reset_old_default_to_false(old_default)
    else:
        if old_default.id == reply_to_id:
            raise InvalidRequest(
                "You must have at least one reply to email address as the default.",
                400)

    reply_to_update = ServiceEmailReplyTo.query.get(reply_to_id)
    reply_to_update.email_address = email_address
    reply_to_update.is_default = is_default
    db.session.add(reply_to_update)
    return reply_to_update
Beispiel #13
0
def create_service():
    data = request.get_json()

    if not data.get('user_id'):
        errors = {'user_id': ['Missing data for required field.']}
        raise InvalidRequest(errors, status_code=400)

    # validate json with marshmallow
    service_schema.load(data)

    user = get_user_by_id(data.pop('user_id'))

    # unpack valid json into service object
    valid_service = Service.from_json(data)

    dao_create_service(valid_service, user)
    return jsonify(data=service_schema.dump(valid_service).data), 201
Beispiel #14
0
def update_user_attribute(user_id):
    user_to_update = get_user_by_id(user_id=user_id)
    req_json = request.get_json()
    if 'updated_by' in req_json:
        updated_by = get_user_by_id(user_id=req_json.pop('updated_by'))
    else:
        updated_by = None

    update_dct, errors = user_update_schema_load_json.load(req_json)
    if errors:
        raise InvalidRequest(errors, status_code=400)
    save_user_attribute(user_to_update, update_dict=update_dct)
    if updated_by:
        if 'email_address' in update_dct:
            template = dao_get_template_by_id(
                current_app.config['TEAM_MEMBER_EDIT_EMAIL_TEMPLATE_ID'])
            recipient = user_to_update.email_address
            reply_to = template.service.get_default_reply_to_email_address()
        elif 'mobile_number' in update_dct:
            template = dao_get_template_by_id(
                current_app.config['TEAM_MEMBER_EDIT_MOBILE_TEMPLATE_ID'])
            recipient = user_to_update.mobile_number
            reply_to = template.service.get_default_sms_sender()
        else:
            return jsonify(data=user_to_update.serialize()), 200
        service = Service.query.get(current_app.config['NOTIFY_SERVICE_ID'])

        saved_notification = persist_notification(
            template_id=template.id,
            template_version=template.version,
            recipient=recipient,
            service=service,
            personalisation={
                'name': user_to_update.name,
                'servicemanagername': updated_by.name,
                'email address': user_to_update.email_address
            },
            notification_type=template.template_type,
            api_key_id=None,
            key_type=KEY_TYPE_NORMAL,
            reply_to_text=reply_to)

        send_notification_to_queue(saved_notification,
                                   False,
                                   queue=QueueNames.NOTIFY)
    return jsonify(data=user_to_update.serialize()), 200
def update_letter_contact(service_id, letter_contact_id, contact_block,
                          is_default):
    old_default = _get_existing_default(service_id)
    # if we want to make this the default, ensure there are no other existing defaults
    if is_default:
        _reset_old_default_to_false(old_default)
    else:
        if old_default.id == letter_contact_id:
            raise InvalidRequest(
                "You must have at least one letter contact as the default.",
                400)

    letter_contact_update = ServiceLetterContact.query.get(letter_contact_id)
    letter_contact_update.contact_block = contact_block
    letter_contact_update.is_default = is_default
    db.session.add(letter_contact_update)
    return letter_contact_update
Beispiel #16
0
def get_jobs_by_service(service_id):
    if request.args.get("limit_days"):
        try:
            limit_days = int(request.args["limit_days"])
        except ValueError:
            errors = {
                "limit_days":
                ["{} is not an integer".format(request.args["limit_days"])]
            }
            raise InvalidRequest(errors, status_code=400)
    else:
        limit_days = None

    statuses = [x.strip() for x in request.args.get("statuses", "").split(",")]

    page = int(request.args.get("page", 1))
    return jsonify(
        **get_paginated_jobs(service_id, limit_days, statuses, page))
Beispiel #17
0
def get_jobs_by_service(service_id):
    if request.args.get('limit_days'):
        try:
            limit_days = int(request.args['limit_days'])
        except ValueError:
            errors = {
                'limit_days':
                ['{} is not an integer'.format(request.args['limit_days'])]
            }
            raise InvalidRequest(errors, status_code=400)
    else:
        limit_days = None

    statuses = [x.strip() for x in request.args.get('statuses', '').split(',')]

    page = int(request.args.get('page', 1))
    return jsonify(
        **get_paginated_jobs(service_id, limit_days, statuses, page))
Beispiel #18
0
def make_predict():
    payload = request.get_json()
    err = validate_predict(request)
    if err: raise InvalidRequest(err[0])

    try:
        text = payload['text']

        text = _normalize_text(text)

        prevision = model.predict(text)

    except:
        raise InternalError('Internal Error')

    else:
        return jsonify(
            {'response': 'positive' if bool(prevision[0]) else 'negative'})
Beispiel #19
0
def create_magazine():
    data = request.get_json(force=True)

    validate(data, post_create_magazine_schema)

    new_filename = get_magazine_filename(data['filename'])

    if new_filename:
        magazine = Magazine(title=data['title'], filename=new_filename)

        dao_create_record(magazine)

        upload_tasks.upload_magazine.apply_async(
            (str(magazine.id), data['pdf_data']))

        return jsonify(magazine.serialize()), 201

    raise InvalidRequest(
        'Invalid filename for magazine: {}'.format(data['filename']), 400)
Beispiel #20
0
def add_user_to_service(service_id, user_id):
    service = dao_fetch_service_by_id(service_id)
    user = get_user_by_id(user_id=user_id)

    if user in service.users:
        error = 'User id: {} already part of service id: {}'.format(user_id, service_id)
        raise InvalidRequest(error, status_code=400)

    data = request.get_json()
    validate(data, post_set_permissions_schema)

    permissions = [
        Permission(service_id=service_id, user_id=user_id, permission=p['permission'])
        for p in data['permissions']
    ]
    folder_permissions = data.get('folder_permissions', [])

    dao_add_user_to_service(service, user, permissions, folder_permissions)
    data = service_schema.dump(service).data
    return jsonify(data=data), 201
Beispiel #21
0
def get_monthly_template_usage(service_id):
    try:
        data = dao_fetch_monthly_historical_usage_by_template_for_service(
            service_id, int(request.args.get('year', 'NaN')))

        stats = list()
        for i in data:
            stats.append({
                'template_id': str(i.template_id),
                'name': i.name,
                'type': i.template_type,
                'month': i.month,
                'year': i.year,
                'count': i.count,
                'is_precompiled_letter': i.is_precompiled_letter
            })

        return jsonify(stats=stats), 200
    except ValueError:
        raise InvalidRequest('Year must be a number', status_code=400)
def dao_get_notifications_by_to_field(service_id,
                                      search_term,
                                      notification_type=None,
                                      statuses=None):
    if notification_type is None:
        notification_type = guess_notification_type(search_term)

    if notification_type == SMS_TYPE:
        normalised = try_validate_and_format_phone_number(search_term)

        for character in {'(', ')', ' ', '-'}:
            normalised = normalised.replace(character, '')

        normalised = normalised.lstrip('+0')

    elif notification_type == EMAIL_TYPE:
        try:
            normalised = validate_and_format_email_address(search_term)
        except InvalidEmailError:
            normalised = search_term.lower()

    else:
        raise InvalidRequest("Only email and SMS can use search by recipient",
                             400)

    normalised = escape_special_characters(normalised)

    filters = [
        Notification.service_id == service_id,
        Notification.normalised_to.like("%{}%".format(normalised)),
        Notification.key_type != KEY_TYPE_TEST,
    ]

    if statuses:
        filters.append(Notification.status.in_(statuses))
    if notification_type:
        filters.append(Notification.notification_type == notification_type)

    results = db.session.query(Notification).filter(*filters).order_by(
        desc(Notification.created_at)).all()
    return results
Beispiel #23
0
def create_service():
    data = request.get_json()

    if not data.get('user_id'):
        errors = {'user_id': ['Missing data for required field.']}
        raise InvalidRequest(errors, status_code=400)
    data.pop('service_domain', None)

    # validate json with marshmallow
    service_schema.load(data)

    user = get_user_by_id(data.pop('user_id'))

    # unpack valid json into service object
    valid_service = Service.from_json(data)

    with transaction():
        dao_create_service(valid_service, user)
        set_default_free_allowance_for_service(service=valid_service, year_start=None)

    return jsonify(data=service_schema.dump(valid_service).data), 201
Beispiel #24
0
def preview_template_by_id_and_service_id(service_id, template_id):
    fetched_template = dao_get_template_by_id_and_service_id(
        template_id=template_id, service_id=service_id)
    data = template_schema.dump(fetched_template).data
    template_object = fetched_template._as_utils_template_with_personalisation(
        request.args.to_dict())

    if template_object.missing_data:
        raise InvalidRequest(
            {
                'template': [
                    'Missing personalisation: {}'.format(", ".join(
                        template_object.missing_data))
                ]
            },
            status_code=400)

    data['subject'] = template_object.subject
    data['content'] = template_object.content_with_placeholders_filled_in

    return jsonify(data)
Beispiel #25
0
def send_user_2fa_code(user_id, code_type):
    user_to_send_to = get_user_by_id(user_id=user_id)

    if(verify_within_time(user_to_send_to, age=timedelta(seconds=10)) >= 1):
        raise InvalidRequest("Code already sent, wait 10 seconds", status_code=400)

    if count_user_verify_codes(user_to_send_to) >= current_app.config.get('MAX_VERIFY_CODE_COUNT'):
        # Prevent more than `MAX_VERIFY_CODE_COUNT` active verify codes at a time
        current_app.logger.warning('Too many verify codes created for user {}'.format(user_to_send_to.id))
    else:
        data = request.get_json()
        if code_type == SMS_TYPE:
            validate(data, post_send_user_sms_code_schema)
            send_user_sms_code(user_to_send_to, data)
        elif code_type == EMAIL_TYPE:
            validate(data, post_send_user_email_code_schema)
            send_user_email_code(user_to_send_to, data)
        else:
            abort(404)

    return '{}', 204
def process_govdelivery_response():
    try:
        data = request.form
        reference = data['message_url'].split("/")[-1]
        govdelivery_status = data['status']
        notify_status = map_govdelivery_status_to_notify_status(
            govdelivery_status)

    except Exception as e:
        raise InvalidRequest(
            'Error processing Govdelivery callback: {}'.format(e), 400)

    else:
        try:
            notification = notifications_dao.dao_get_notification_by_reference(
                reference)

        except (MultipleResultsFound, NoResultFound) as e:
            current_app.logger.exception(
                'Govdelivery callback for reference {} did not find exactly one notification: {}'
                .format(reference, type(e)))
            pass

        else:
            current_app.logger.info(
                'Govdelivery callback for notification {} has status "{}", which maps to notification-api status "{}"'
                .format(notification.id, govdelivery_status, notify_status))

            notifications_dao._update_notification_status(
                notification, notify_status)

            statsd_client.incr('callback.govdelivery.{}'.format(notify_status))

            if notification.sent_at:
                statsd_client.timing_with_dates(
                    'callback.govdelivery.elapsed-time', datetime.utcnow(),
                    notification.sent_at)

    return jsonify(result='success'), 200
Beispiel #27
0
def dao_archive_user(user):
    if not user_can_be_archived(user):
        msg = "User can’t be removed from a service - check all services have another team member with manage_settings"
        raise InvalidRequest(msg, 400)

    permission_dao.remove_user_service_permissions_for_all_services(user)

    service_users = dao_get_service_users_by_user_id(user.id)
    for service_user in service_users:
        db.session.delete(service_user)

    user.organisations = []

    user.auth_type = EMAIL_AUTH_TYPE
    user.email_address = get_archived_db_column_value(user.email_address)
    user.mobile_number = None
    user.password = str(uuid.uuid4())
    # Changing the current_session_id signs the user out
    user.current_session_id = '00000000-0000-0000-0000-000000000000'
    user.state = 'inactive'

    db.session.add(user)
Beispiel #28
0
def get_monthly_template_usage(service_id):
    try:
        start_date, end_date = get_financial_year(
            int(request.args.get("year", "NaN")))
        data = fetch_monthly_template_usage_for_service(start_date=start_date,
                                                        end_date=end_date,
                                                        service_id=service_id)
        stats = list()
        for i in data:
            stats.append({
                "template_id": str(i.template_id),
                "name": i.name,
                "type": i.template_type,
                "month": i.month,
                "year": i.year,
                "count": i.count,
                "is_precompiled_letter": i.is_precompiled_letter,
            })

        return jsonify(stats=stats), 200
    except ValueError:
        raise InvalidRequest("Year must be a number", status_code=400)
Beispiel #29
0
def get_monthly_notification_stats(service_id):
    # check service_id validity
    dao_fetch_service_by_id(service_id)

    try:
        year = int(request.args.get('year', 'NaN'))
    except ValueError:
        raise InvalidRequest('Year must be a number', status_code=400)

    start_date, end_date = get_financial_year(year)

    data = statistics.create_empty_monthly_notification_status_stats_dict(year)

    stats = fetch_notification_status_for_service_by_month(start_date, end_date, service_id)
    statistics.add_monthly_notification_status_stats(data, stats)

    now = datetime.utcnow()
    if end_date > now:
        todays_deltas = fetch_notification_status_for_service_for_day(convert_utc_to_bst(now), service_id=service_id)
        statistics.add_monthly_notification_status_stats(data, todays_deltas)

    return jsonify(data=data)
Beispiel #30
0
def create_template_folder(service_id):
    data = request.get_json()

    validate(data, post_create_template_folder_schema)
    if data.get('parent_id') is not None:
        try:
            parent_folder = dao_get_template_folder_by_id_and_service_id(
                data['parent_id'], service_id)
            users_with_permission = parent_folder.users
        except NoResultFound:
            raise InvalidRequest("parent_id not found", status_code=400)
    else:
        users_with_permission = dao_get_active_service_users(service_id)
    template_folder = TemplateFolder(
        service_id=service_id,
        name=data['name'].strip(),
        parent_id=data['parent_id'],
        users=users_with_permission,
    )

    dao_create_template_folder(template_folder)

    return jsonify(data=template_folder.serialize()), 201