def test_different_key_prefixes():
    """ Test using different key_prefixes
    """

    limiter1 = Limiter(
        key_func=get_remote_addr,
        default_limits=["10 per hour", "1 per second"],
        config={
            'RATELIMIT_KEY_PREFIX': 'app1'
        }
    )

    @limiter1.limit()
    class ThingsResource:
        def on_get(self, req, resp):
            resp.body = 'Hello world!'

    app1 = API(middleware=limiter1.middleware)
    app1.add_route('/things', ThingsResource())

    client1 = testing.TestClient(app1)
    r = client1.simulate_get('/things')
    assert r.status == HTTP_200

    # due to the 1 second default limit on the default limiter
    r = client1.simulate_get('/things')
    assert r.status == HTTP_429


    #####
    # app2 - with a different key

    limiter2 = Limiter(
        key_func=get_remote_addr,
        default_limits=["10 per hour", "1 per second"],
        config={
            'RATELIMIT_KEY_PREFIX': 'app2'
        }
    )

    @limiter2.limit()
    class ThingsResource:
        def on_get(self, req, resp):
            resp.body = 'Hello world!'

    app2 = API(middleware=limiter2.middleware)
    app2.add_route('/things', ThingsResource())

    client2 = testing.TestClient(app2)
    r = client2.simulate_get('/things')
    assert r.status == HTTP_200

    # due to the 1 second default limit on the default limiter
    r = client2.simulate_get('/things')
    assert r.status == HTTP_429
Exemple #2
0
def test_redis(redis_server):
    """ Test using the redis backend
    """

    limiter = Limiter(
        key_func=get_remote_addr,
        default_limits=["10 per hour", "1 per second"],
        config={
            'RATELIMIT_KEY_PREFIX': 'myapp',
            'RATELIMIT_STORAGE_URL': f'redis://@localhost:{REDIS_PORT}'
        }
    )

    @limiter.limit()
    class ThingsResource:
        def on_get(self, req, resp):
            resp.body = 'Hello world!'

    app = API(middleware=limiter.middleware)
    app.add_route('/things', ThingsResource())

    client = testing.TestClient(app)
    r = client.simulate_get('/things')
    assert r.status == HTTP_200

    # due to the 1 second default limit on the default limiter
    r = client.simulate_get('/things')
    assert r.status == HTTP_429

    sleep(1)
    r = client.simulate_get('/things')
    assert r.status == HTTP_200
def test_limit_by_resource_and_method():
    """ Test using a custom key_func - one which creates different buckets by resource and method
    """

    def get_key(req, resp, resource, params) -> str:
        user_key = get_remote_addr(req, resp, resource, params)
        return f"{user_key}:{resource.__class__.__name__}:{req.method}"

    limiter = Limiter(
        key_func=get_key,
        default_limits=["10 per hour", "1 per second"]
    )

    @limiter.limit()
    class ThingsResource:
        def on_get(self, req, resp):
            resp.body = 'Hello world!'

        def on_post(self, req, resp):
            resp.body = 'Hello world!'

    app = API(middleware=limiter.middleware)
    app.add_route('/things', ThingsResource())

    client = testing.TestClient(app)
    r = client.simulate_get('/things')
    assert r.status == HTTP_200

    r = client.simulate_get('/things')
    assert r.status == HTTP_429

    # but a different endpoint can still be hit
    r = client.simulate_post('/things')
    assert r.status == HTTP_200
def test_get_remote_addr():
    """ Test using the get_remote_addr() key function as default
    """

    limiter = Limiter(
        key_func=get_remote_addr,
        default_limits=["10 per hour", "1 per second"]
    )

    @limiter.limit()
    class ThingsResource:
        def on_get(self, req, resp):
            resp.body = 'Hello world!'

    app = API(middleware=limiter.middleware)
    app.add_route('/things', ThingsResource())

    client = testing.TestClient(app)
    r = client.simulate_get('/things')
    assert r.status == HTTP_200

    # due to the 1 second default limit on the default limiter
    r = client.simulate_get('/things')
    assert r.status == HTTP_429

    sleep(1)
    r = client.simulate_get('/things')
    assert r.status == HTTP_200
def test_dynamic_limits_on_method2():
    """ Test using the dynamic_limits param of the method decorators to change the limit per user

    Overwriting the default limits from the class level decortor
    """

    limiter = Limiter(key_func=get_remote_addr,
                      default_limits=["10 per hour", "1 per second"])

    @limiter.limit()
    class ThingsResource:
        @limiter.limit(dynamic_limits=lambda req, resp, resource,
                       req_succeeded: '5/second'
                       if req.get_header('APIUSER') == 'admin' else '2/second')
        def on_get(self, req, resp):
            resp.body = 'Hello world!'

        def on_post(self, req, resp):
            resp.body = 'Hello world!'

    app = API(middleware=limiter.middleware)
    app.add_route('/things', ThingsResource())

    client = testing.TestClient(app)

    ####
    # 'normal' user - errors after more than 2 calls per sec
    r = client.simulate_get('/things')
    assert r.status == HTTP_200
    r = client.simulate_get('/things')
    assert r.status == HTTP_200

    # due to the 2/second limit
    r = client.simulate_get('/things')
    assert r.status == HTTP_429

    #########
    # 'admin' user should be able to make 5 calls
    admin_header = {"APIUSER": "******"}
    for i in range(5):
        r = client.simulate_get('/things', headers=admin_header)
        assert r.status == HTTP_200

    # at the 6th hit even the admin user will error:
    r = client.simulate_get('/things', headers=admin_header)
    assert r.status == HTTP_429

    sleep(1)
    r = client.simulate_get('/things', headers=admin_header)
    assert r.status == HTTP_200

    ########
    # the on_post() method gets the default limit - 1 per second
    r = client.simulate_post('/things', headers=admin_header)
    assert r.status == HTTP_200

    r = client.simulate_post('/things', headers=admin_header)
    assert r.status == HTTP_429
def test_default_dynamic_limits():
    """ Test using the default_dynamic_limits option to change the limit per user
    """

    limiter = Limiter(key_func=get_remote_addr,
                      default_dynamic_limits=lambda req, resp, resource,
                      req_succeeded: '5/second'
                      if req.get_header('APIUSER') == 'admin' else '2/second')

    @limiter.limit()
    class ThingsResource:
        def on_get(self, req, resp):
            resp.body = 'Hello world!'

    app = API(middleware=limiter.middleware)
    app.add_route('/things', ThingsResource())

    client = testing.TestClient(app)

    ####
    # 'normal' user - errors after more than 2 calls per sec
    r = client.simulate_get('/things')
    assert r.status == HTTP_200
    r = client.simulate_get('/things')
    assert r.status == HTTP_200

    # due to the 2/second limit
    r = client.simulate_get('/things')
    assert r.status == HTTP_429

    #########
    # 'admin' user should be able to make 5 calls
    admin_header = {"APIUSER": "******"}
    for i in range(5):
        r = client.simulate_get('/things', headers=admin_header)
        assert r.status == HTTP_200

    # at the 6th hit even the admin user will error:
    r = client.simulate_get('/things', headers=admin_header)
    assert r.status == HTTP_429

    sleep(1)
    r = client.simulate_get('/things', headers=admin_header)
    assert r.status == HTTP_200
def test_empy_limits():
    """ Test incorrect setup - empty limits!

    In this case there are NO limits applied
    """

    limiter = Limiter(key_func=get_remote_addr)

    @limiter.limit()
    class ThingsResource:
        def on_get(self, req, resp):
            resp.body = 'Hello world!'

    app = API(middleware=limiter.middleware)
    app.add_route('/things', ThingsResource())

    client = testing.TestClient(app)
    for i in range(5):
        r = client.simulate_get('/things')
        assert r.status == HTTP_200
Exemple #8
0
def test_deduct_when_http200_as_method_decorator():
    """ Test using the deduct_when option on a method decorator to deduct only when the response is 200
    """

    limiter = Limiter(
        key_func=get_remote_addr,
        default_limits=["10 per hour", "1 per second"]
    )

    @limiter.limit()
    class ThingsResource:
        def on_get(self, req, resp):
            resp.body = 'Hello world!'

        @limiter.limit(deduct_when=lambda req, resp, resource, req_succeeded: resp.status == HTTP_200)
        def on_post(self, req, resp):
            resp.body = 'Hello world!'
            resp.status = HTTP_500

    app = API(middleware=limiter.middleware)
    app.add_route('/things', ThingsResource())

    client = testing.TestClient(app)

    # this is a 500 response, so it should NOT count!
    r = client.simulate_post('/things')
    assert r.status == HTTP_500

    r = client.simulate_get('/things')
    assert r.status == HTTP_200

    # due to the 1 second default limit on the default limiter
    r = client.simulate_get('/things')
    assert r.status == HTTP_429

    sleep(1)
    r = client.simulate_get('/things')
    assert r.status == HTTP_200
def test_reverse_proxies():
    """ Test using a custom key_func - one which you would use to handle reverse proxies
    """

    def get_access_route_addr(req, resp, resource, params) -> str:
        """ Returns the remote address from the access_routes list discounting 1 reverse proxy
        """
        return req.access_route[-3]

    limiter = Limiter(
        key_func=get_access_route_addr,
        default_limits=["10 per hour", "1 per second"]
    )

    @limiter.limit()
    class ThingsResource:
        def on_get(self, req, resp):
            resp.body = 'Hello world!'

    # two different source IPs through 1 reverse proxy:
    ip1_header = {"X-FORWARDED-FOR": "10.0.0.1, 1.2.3.4"}
    ip2_header = {"X-FORWARDED-FOR": "10.0.0.2, 1.2.3.4"}

    app = API(middleware=limiter.middleware)
    app.add_route('/things', ThingsResource())

    client = testing.TestClient(app)
    r = client.simulate_get('/things', headers=ip1_header)
    assert r.status == HTTP_200

    # the same IP is denied
    r = client.simulate_get('/things', headers=ip1_header)
    assert r.status == HTTP_429

    # but a different IP can still access it
    r = client.simulate_get('/things', headers=ip2_header)
    assert r.status == HTTP_200
Exemple #10
0
    @property
    def middleware(self):
        return middlewares.FalconCacheMiddleware(self.cache, self.config)


app_cache = Cache(
    config={
        'CACHE_TYPE': 'redis',
        'CACHE_EVICTION_STRATEGY': 'time-based',
        'CACHE_KEY_PREFIX': 'falcon-cache',
        'CACHE_REDIS_URL': settings.REDIS_URL,
    })  # https://falcon-caching.readthedocs.io/en/stable/

limiter = Limiter(key_func=get_limiter_key,
                  default_limits=settings.FALCON_LIMITER_DEFAULT_LIMITS,
                  config={
                      'RATELIMIT_KEY_PREFIX': 'falcon-limiter',
                      'RATELIMIT_STORAGE_URL': settings.REDIS_URL,
                  })


def get_api_app():
    from mcod.routes import routes

    os.environ.setdefault("COMPONENT", "api")

    _middlewares = [
        middlewares.ContentTypeMiddleware(),
        middlewares.DebugMiddleware(),
        middlewares.LocaleMiddleware(),
        middlewares.ApiVersionMiddleware(),
        middlewares.CounterMiddleware(),
def limiter(request):
    """ Create a basic limiter
    """
    limiter = Limiter(key_func=get_remote_addr,
                      default_limits=["10 per hour", "1 per second"])
    return limiter