示例#1
0
def test_check_service_message_limit_in_cache_over_message_limit_fails(
        notify_db_session,
        key_type,
        mocker):
    with freeze_time("2016-01-01 12:00:00.000000"):
        mocker.patch('app.redis_store.get', side_effect=[
            None,  # The serialised service
            5,  # The rolling count
        ])
        mocker.patch('app.notifications.validators.redis_store.set')
        mocker.patch('app.notifications.validators.services_dao')

        service = create_service(restricted=True, message_limit=4)
        serialised_service = SerialisedService.from_id(service.id)
        with pytest.raises(TooManyRequestsError) as e:
            check_service_over_daily_message_limit(key_type, serialised_service)
        assert e.value.status_code == 429
        assert e.value.message == 'Exceeded send limits (4) for today'
        assert e.value.fields == []
        app.notifications.validators.redis_store.set.assert_called_once_with(
            f'service-{serialised_service.id}',
            ANY,
            ex=ANY,
        )
        assert not app.notifications.validators.services_dao.mock_calls
示例#2
0
def test_that_when_exceed_rate_limit_request_fails(
        key_type,
        sample_service,
        mocker):
    with freeze_time("2016-01-01 12:00:00.000000"):

        if key_type == 'live':
            api_key_type = 'normal'
        else:
            api_key_type = key_type

        mocker.patch('app.redis_store.exceeded_rate_limit', return_value=True)
        mocker.patch('app.notifications.validators.services_dao')

        sample_service.restricted = True
        api_key = create_api_key(sample_service, key_type=api_key_type)
        serialised_service = SerialisedService.from_id(sample_service.id)
        serialised_api_key = SerialisedAPIKeyCollection.from_service_id(serialised_service.id)[0]

        with pytest.raises(RateLimitError) as e:
            check_service_over_api_rate_limit(serialised_service, serialised_api_key)

        assert app.redis_store.exceeded_rate_limit.called_with(
            "{}-{}".format(str(sample_service.id), api_key.key_type),
            sample_service.rate_limit,
            60
        )
        assert e.value.status_code == 429
        assert e.value.message == 'Exceeded rate limit for key type {} of {} requests per {} seconds'.format(
            key_type.upper(), sample_service.rate_limit, 60
        )
        assert e.value.fields == []
示例#3
0
def save_letter(
    self,
    service_id,
    notification_id,
    encrypted_notification,
):
    notification = encryption.decrypt(encrypted_notification)

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

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

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

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

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

        current_app.logger.debug("Letter {} created at {}".format(
            saved_notification.id, saved_notification.created_at))
    except SQLAlchemyError as e:
        handle_exception(self, notification, notification_id, e)
示例#4
0
def test_service_can_send_to_recipient_passes_for_live_service_non_team_member(key_type, sample_service):
    serialised_service = SerialisedService.from_id(sample_service.id)
    assert service_can_send_to_recipient("*****@*****.**",
                                         key_type,
                                         serialised_service) is None
    assert service_can_send_to_recipient('07513332413',
                                         key_type,
                                         serialised_service) is None
示例#5
0
def test_should_not_access_database_if_redis_disabled(notify_api, sample_service, mocker):
    serialised_service = SerialisedService.from_id(sample_service.id)
    with set_config(notify_api, 'REDIS_ENABLED', False):
        db_mock = mocker.patch('app.notifications.validators.services_dao')

        check_service_over_daily_message_limit('normal', serialised_service)

        assert db_mock.method_calls == []
示例#6
0
def test_service_can_send_to_recipient_passes(key_type, notify_db_session):
    trial_mode_service = create_service(service_name='trial mode', restricted=True)
    serialised_service = SerialisedService.from_id(trial_mode_service.id)
    assert service_can_send_to_recipient(trial_mode_service.users[0].email_address,
                                         key_type,
                                         serialised_service) is None
    assert service_can_send_to_recipient(trial_mode_service.users[0].mobile_number,
                                         key_type,
                                         serialised_service) is None
示例#7
0
def requires_auth():
    request_helper.check_proxy_header_before_request()

    auth_token = get_auth_token(request)
    issuer = __get_token_issuer(auth_token)  # ie the `iss` claim which should be a service ID

    try:
        with AUTH_DB_CONNECTION_DURATION_SECONDS.time():
            service = SerialisedService.from_id(issuer)
    except DataError:
        raise AuthError("Invalid token: service id is not the right data type", 403)
    except NoResultFound:
        raise AuthError("Invalid token: service not found", 403)

    if not service.api_keys:
        raise AuthError("Invalid token: service has no API keys", 403, service_id=service.id)

    if not service.active:
        raise AuthError("Invalid token: service is archived", 403, service_id=service.id)

    for api_key in service.api_keys:
        try:
            decode_jwt_token(auth_token, api_key.secret)
        except TokenExpiredError:
            err_msg = "Error: Your system clock must be accurate to within 30 seconds"
            raise AuthError(err_msg, 403, service_id=service.id, api_key_id=api_key.id)
        except TokenAlgorithmError:
            err_msg = "Invalid token: algorithm used is not HS256"
            raise AuthError(err_msg, 403, service_id=service.id, api_key_id=api_key.id)
        except TokenDecodeError:
            # we attempted to validate the token but it failed meaning it was not signed using this api key.
            # Let's try the next one
            # TODO: Change this so it doesn't also catch `TokenIssuerError` or `TokenIssuedAtError` exceptions (which
            # are children of `TokenDecodeError`) as these should cause an auth error immediately rather than
            # continue on to check the next API key
            continue
        except TokenError:
            # General error when trying to decode and validate the token
            raise AuthError(GENERAL_TOKEN_ERROR_MESSAGE, 403, service_id=service.id, api_key_id=api_key.id)

        if api_key.expiry_date:
            raise AuthError("Invalid token: API key revoked", 403, service_id=service.id, api_key_id=api_key.id)

        g.service_id = service.id
        _request_ctx_stack.top.authenticated_service = service
        _request_ctx_stack.top.api_user = api_key

        current_app.logger.info('API authorised for service {} with api key {}, using issuer {} for URL: {}'.format(
            service.id,
            api_key.id,
            request.headers.get('User-Agent'),
            request.base_url
        ))
        return
    else:
        # service has API keys, but none matching the one the user provided
        raise AuthError("Invalid token: API key not found", 403, service_id=service.id)
示例#8
0
def save_sms(self,
             service_id,
             notification_id,
             encrypted_notification,
             sender_id=None):
    notification = encryption.decrypt(encrypted_notification)
    service = SerialisedService.from_id(service_id)
    template = SerialisedTemplate.from_id_and_service_id(
        notification['template'],
        service_id=service.id,
        version=notification['template_version'],
    )

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

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

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

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

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

    except SQLAlchemyError as e:
        handle_exception(self, notification, notification_id, e)
示例#9
0
def test_rejects_api_calls_with_international_numbers_if_service_does_not_allow_int_sms(
        key_type,
        notify_db_session,
):
    service = create_service(service_permissions=[SMS_TYPE])
    service_model = SerialisedService.from_id(service.id)
    with pytest.raises(BadRequestError) as e:
        validate_and_format_recipient('20-12-1234-1234', key_type, service_model, SMS_TYPE)
    assert e.value.status_code == 400
    assert e.value.message == 'Cannot send to international mobile numbers'
    assert e.value.fields == []
示例#10
0
def test_should_not_rate_limit_if_limiting_is_disabled(
        sample_service,
        mocker):
    with freeze_time("2016-01-01 12:00:00.000000"):
        current_app.config['API_RATE_LIMIT_ENABLED'] = False

        mocker.patch('app.redis_store.exceeded_rate_limit', return_value=False)
        mocker.patch('app.notifications.validators.services_dao')

        sample_service.restricted = True
        create_api_key(sample_service)
        serialised_service = SerialisedService.from_id(sample_service.id)
        serialised_api_key = SerialisedAPIKeyCollection.from_service_id(serialised_service.id)[0]

        check_service_over_api_rate_limit(serialised_service, serialised_api_key)
        assert not app.redis_store.exceeded_rate_limit.called
示例#11
0
def test_should_set_cache_value_as_value_from_database_if_cache_not_set(
        key_type,
        sample_template,
        sample_service,
        mocker
):
    serialised_service = SerialisedService.from_id(sample_service.id)
    with freeze_time("2016-01-01 12:00:00.000000"):
        for _ in range(5):
            create_notification(sample_template)
        mocker.patch('app.notifications.validators.redis_store.get', return_value=None)
        mocker.patch('app.notifications.validators.redis_store.set')
        check_service_over_daily_message_limit(key_type, serialised_service)
        app.notifications.validators.redis_store.set.assert_called_with(
            str(sample_service.id) + "-2016-01-01-count", 5, ex=3600
        )
示例#12
0
def test_that_when_not_exceeded_rate_limit_request_succeeds(
        sample_service,
        mocker):
    with freeze_time("2016-01-01 12:00:00.000000"):
        mocker.patch('app.redis_store.exceeded_rate_limit', return_value=False)
        mocker.patch('app.notifications.validators.services_dao')

        sample_service.restricted = True
        api_key = create_api_key(sample_service)
        serialised_service = SerialisedService.from_id(sample_service.id)
        serialised_api_key = SerialisedAPIKeyCollection.from_service_id(serialised_service.id)[0]

        check_service_over_api_rate_limit(serialised_service, serialised_api_key)
        assert app.redis_store.exceeded_rate_limit.called_with(
            "{}-{}".format(str(sample_service.id), api_key.key_type),
            3000,
            60
        )
示例#13
0
def test_check_service_message_limit_in_cache_under_message_limit_passes(
        key_type,
        sample_service,
        mocker):
    mocker.patch('app.notifications.validators.redis_store.get', side_effect=[
        None,  # The serialised service
        1,  # The rolling count
    ])
    mocker.patch('app.notifications.validators.redis_store.set')
    mocker.patch('app.notifications.validators.services_dao')
    serialised_service = SerialisedService.from_id(sample_service.id)
    check_service_over_daily_message_limit(key_type, serialised_service)
    app.notifications.validators.redis_store.set.assert_called_once_with(
        f'service-{serialised_service.id}',
        ANY,
        ex=ANY,
    )
    assert not app.notifications.validators.services_dao.mock_calls
示例#14
0
def save_api_email_or_sms(self, encrypted_notification):
    notification = encryption.decrypt(encrypted_notification)
    service = SerialisedService.from_id(notification['service_id'])
    q = QueueNames.SEND_EMAIL if notification[
        'notification_type'] == EMAIL_TYPE else QueueNames.SEND_SMS
    provider_task = provider_tasks.deliver_email if notification['notification_type'] == EMAIL_TYPE \
        else provider_tasks.deliver_sms
    try:

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

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

    except SQLAlchemyError:

        try:
            self.retry(queue=QueueNames.RETRY)
        except self.MaxRetriesExceededError:
            current_app.logger.error(
                f"Max retry failed Failed to persist notification {notification['id']}"
            )
示例#15
0
def test_check_service_message_limit_over_message_limit_fails(key_type, sample_service, mocker):
    with freeze_time("2016-01-01 12:00:00.000000"):
        mocker.patch('app.redis_store.get', return_value=None)
        mocker.patch('app.notifications.validators.redis_store.set')

        sample_service.restricted = True
        sample_service.message_limit = 4
        template = create_template(sample_service)
        serialised_service = SerialisedService.from_id(sample_service.id)

        for _ in range(5):
            create_notification(template)
        with pytest.raises(TooManyRequestsError) as e:
            check_service_over_daily_message_limit(key_type, serialised_service)
        assert e.value.status_code == 429
        assert e.value.message == 'Exceeded send limits (4) for today'
        assert e.value.fields == []
        app.notifications.validators.redis_store.set.assert_called_with(
            str(sample_service.id) + "-2016-01-01-count", 5, ex=3600
        )
def test_get_html_email_options_return_email_branding_from_serialised_service(
        sample_service):
    branding = create_email_branding()
    sample_service.email_branding = branding
    service = SerialisedService.from_id(sample_service.id)
    email_options = get_html_email_options(service)
    assert email_options is not None
    assert email_options == {
        'govuk_banner':
        branding.brand_type == BRANDING_BOTH,
        'brand_banner':
        branding.brand_type == BRANDING_ORG_BANNER,
        'brand_colour':
        branding.colour,
        'brand_logo':
        get_logo_url(current_app.config['ADMIN_BASE_URL'], branding.logo),
        'brand_text':
        branding.text,
        'brand_name':
        branding.name,
    }
示例#17
0
def test_should_not_interact_with_cache_for_test_key(sample_service, mocker):
    mocker.patch('app.notifications.validators.redis_store')
    mocker.patch('app.notifications.validators.redis_store.get', side_effect=[None])
    serialised_service = SerialisedService.from_id(sample_service.id)
    check_service_over_daily_message_limit('test', serialised_service)
    assert not app.notifications.validators.redis_store.mock_calls
示例#18
0
def test_allows_api_calls_with_international_numbers_if_service_does_allow_int_sms(
        key_type, sample_service_full_permissions):
    service_model = SerialisedService.from_id(sample_service_full_permissions.id)
    result = validate_and_format_recipient('20-12-1234-1234', key_type, service_model, SMS_TYPE)
    assert result == '201212341234'