Exemplo n.º 1
0
def check_rate_limit() -> None:
    """
    Check whether or not a user has exceeded the rate limits specified in the
    config. Rate limits per API key or session and per user are recorded.
    The redis database is used to keep track of caching, by incrementing
    "rate limit" cache keys on each request and setting a timeout on them.
    The rate limit can be adjusted in the configuration file.

    :raises APIException: If the rate limit has been exceeded
    """
    if not flask.g.user:
        return check_rate_limit_unauthenticated()

    user_cache_key = f'rate_limit_user_{flask.g.user.id}'
    key_cache_key = f'rate_limit_api_key_{flask.g.api_key.hash}'

    auth_specific_requests = cache.inc(
        key_cache_key, timeout=app.config['RATE_LIMIT_AUTH_SPECIFIC'][1]
    )
    if auth_specific_requests > app.config['RATE_LIMIT_AUTH_SPECIFIC'][0]:
        time_left = cache.ttl(key_cache_key)
        raise APIException(
            f'Client rate limit exceeded. {time_left} seconds until lock expires.'
        )

    user_specific_requests = cache.inc(
        user_cache_key, timeout=app.config['RATE_LIMIT_PER_USER'][1]
    )
    if user_specific_requests > app.config['RATE_LIMIT_PER_USER'][0]:
        time_left = cache.ttl(user_cache_key)
        raise APIException(
            f'User rate limit exceeded. {time_left} seconds until lock expires.'
        )
Exemplo n.º 2
0
def test_cache_inc_key_already_exists(app, client):
    """Incrementing an already-existing key just increments it."""
    assert cache.set('test-inc-key', 3, timeout=15)
    value = cache.inc('test-inc-key', 4, timeout=80)
    time_left = cache.ttl('test-inc-key')
    assert value == 7
    assert time_left > 13 and time_left < 16
Exemplo n.º 3
0
def test_forum_cache(app, authed_client):
    forum = Forum.from_pk(1)
    cache.cache_model(forum, timeout=60)
    forum = Forum.from_pk(1)
    assert forum.name == 'Pulsar'
    assert forum.description == 'Stuff about pulsar'
    assert cache.ttl(forum.cache_key) < 61
def test_category_cache(app, authed_client):
    category = ForumCategory.from_pk(1)
    cache.cache_model(category, timeout=60)
    category = ForumCategory.from_pk(1)
    assert category.name == 'Site'
    assert category.description == 'General site discussion'
    assert cache.ttl(category.cache_key) < 61
Exemplo n.º 5
0
def test_thread_cache(app, authed_client):
    thread = ForumThread.from_pk(1)
    cache.cache_model(thread, timeout=60)
    thread = ForumThread.from_pk(1)
    assert thread.id == 1
    assert thread.topic == 'New Site'
    assert cache.ttl(thread.cache_key) < 61
Exemplo n.º 6
0
def test_thread_last_post_already_cached(app, authed_client):
    thread = ForumThread.from_pk(2, include_dead=True)
    post = ForumPost.from_pk(6)
    cache.cache_model(post, timeout=60)
    post = thread.last_post
    assert post.contents == 'Delete this'
    assert cache.ttl(post.cache_key) < 61
Exemplo n.º 7
0
def test_user_class_cache(app, client, class_, class_id, permission):
    user_class = class_.from_pk(class_id)
    cache_key = cache.cache_model(user_class, timeout=60)
    user_class = class_.from_pk(class_id)
    assert user_class.id == class_id
    assert permission in user_class.permissions
    assert cache.ttl(cache_key) < 61
Exemplo n.º 8
0
def test_post_cache(app, authed_client):
    post = ForumPost.from_pk(1)
    cache.cache_model(post, timeout=60)
    post = ForumPost.from_pk(1)
    assert post.id == 1
    assert post.contents == '!site New yeah'
    assert cache.ttl(post.cache_key) < 61
Exemplo n.º 9
0
def test_view_api_key_cached(app, authed_client):
    add_permissions(app, ApikeyPermissions.VIEW, ApikeyPermissions.VIEW_OTHERS)
    api_key = APIKey.from_pk('1234567890', include_dead=True)
    cache_key = cache.cache_model(api_key, timeout=60)

    response = authed_client.get(f'/api_keys/1234567890')
    check_json_response(response, {'hash': '1234567890', 'revoked': True})
    assert cache.ttl(cache_key) < 61
Exemplo n.º 10
0
    def test_route():
        cache.set('key3', 1)
        cache.inc('key1')
        cache.get('key2')
        cache.ttl('key2')
        cache.ttl('key3')
        cache.get('key3')
        cache.delete('key3')
        cache.delete('key4')

        assert cache.has('key1')
        assert flask.g.cache_keys['inc'] == {'key1'}
        assert flask.g.cache_keys['get'] == {'key3'}
        assert flask.g.cache_keys['set'] == {'key3'}
        assert flask.g.cache_keys['delete'] == {'key3'}
        assert flask.g.cache_keys['ttl'] == {'key3'}
        assert flask.g.cache_keys['has'] == {'key1'}
        return flask.jsonify('complete')
Exemplo n.º 11
0
def check_rate_limit_unauthenticated() -> None:
    """Applies a harsher 30 req / minute to unauthenticated users."""
    cache_key = f'rate_limit_unauth_{flask.request.remote_addr}'
    requests = cache.inc(cache_key, timeout=60)
    if requests > 30:
        time_left = cache.ttl(cache_key)
        raise APIException(
            f'Unauthenticated rate limit exceeded. {time_left} seconds until lock expires.'
        )
Exemplo n.º 12
0
def test_get_rules_cache(app, authed_client, monkeypatch):
    add_permissions(app, RulePermissions.VIEW)
    authed_client.get('/rules/golden')  # cache
    monkeypatch.setattr('rules.os', None)
    response = authed_client.get('/rules/golden').get_json()
    assert isinstance(response['response'], dict)
    assert 'id' in response['response']
    assert response['response']['rules'][0]['rules'][0]['number'] == '1.1'
    assert cache.get('rules_golden')
    assert cache.ttl('rules_golden') is None
Exemplo n.º 13
0
def test_view_all_keys_cached(app, authed_client):
    add_permissions(app, ApikeyPermissions.VIEW)
    cache_key = APIKey.__cache_key_of_user__.format(user_id=1)
    cache.set(cache_key, ['abcdefghij', 'bcdefghijk'], timeout=60)

    response = authed_client.get('/api_keys')
    data = response.get_json()['response']
    assert any('hash' in api_key and api_key['hash'] == CODE_2[:10]
               for api_key in data)
    assert cache.ttl(cache_key)
Exemplo n.º 14
0
def test_auth_updates(app, client):
    """Test that the cache key delay for updating an API key works."""

    @app.route('/test_api_key')
    def test_api_key():
        return flask.jsonify('completed')

    db.engine.execute(
        "UPDATE api_keys SET ip = '127.0.0.1', user_agent = 'pulsar-test-client'"
    )
    cache.set('api_keys_abcdefghij_updated', 1, timeout=9000)
    response = client.get(
        '/test_api_key',
        environ_base={
            'HTTP_USER_AGENT': 'pulsar-test-client',
            'REMOTE_ADDR': '127.0.0.1',
        },
        headers={'Authorization': f'Token abcdefghij{CODE_1}'},
    )
    check_json_response(response, 'completed')
    assert cache.ttl('api_keys_abcdefghij_updated') > 8000
Exemplo n.º 15
0
def test_cache_inc_key_new(app, client):
    """Incrementing a nonexistent key should create it."""
    value = cache.inc('test-inc-key', 2, timeout=60)
    time_left = cache.ttl('test-inc-key')
    assert value == 2
    assert time_left < 61
Exemplo n.º 16
0
def test_post_edit_history_from_pk_cached(app, authed_client):
    history = ForumPostEditHistory.from_pk(2)
    cache.cache_model(history, timeout=60)
    history = ForumPostEditHistory.from_pk(2)
    assert history.post_id == 2
    assert cache.ttl(history.cache_key) < 61