Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
django-fast-ratelimit
Advanced tools
Django-fast-ratelimit provides a secure and fast ratelimit facility based on the django caching framework. It uses a "Fixed window counter"-algorithm based on: https://medium.com/figma-design/an-alternative-approach-to-rate-limiting-f8a06cf7c94c
pip install django-fast-ratelimit
Note: pip >= 19 is required
Note: version 5 renames package from ratelimit to django_fast_ratelimit
Apply everywhere where wanted in the django app without restrictions:
install companion library django-fast-iprestrict >= 0.6.0
Decorator:
import django_fast_ratelimit as ratelimit
@ratelimit.decorate(key="ip", rate="1/s")
def expensive_func(request):
# how many ratelimits request limiting
if request.ratelimit["request_limit"] > 0:
# reschedule with end of rate epoch
return request_waiting(request.ratelimit["end"])
or async
import django_fast_ratelimit as ratelimit
import asyncio
@ratelimit.decorate(key="ip", rate="1/s")
async def expensive_func(request):
# how many ratelimits request limiting
if request.ratelimit["request_limit"] > 0:
# reschedule with end of rate epoch
await asyncio.sleep(request.ratelimit["end"])
blocking Decorator (raises RatelimitError):
import django_fast_ratelimit as ratelimit
@ratelimit.decorate(key="ip", rate="1/s", block=True, decorate_name="ratelimit", methods=ratelimit.UNSAFE)
def expensive_func(request):
# how many ratelimits request limiting
if request.ratelimit["end"] > 0:
decorate View (requires group):
import django_fast_ratelimit as ratelimit
from django.views.generic import View
from django.utils.decorators import method_decorator
@method_decorator(ratelimit.decorate(
key="ip", rate="1/s", block=True, methods=ratelimit.SAFE, group="required"
), name="dispatch")
class FooView(View):
...
manual
import django_fast_ratelimit as ratelimit
def func(request):
ratelimit.get_ratelimit(key="ip", rate="1/s", request=request, group="123", action=ratelimit.Action.INCREASE)
# or only for GET
ratelimit.get_ratelimit(
key="ip", rate="1/s", request=request, group="123", methods="GET", action=ratelimit.Action.INCREASE
)
# also simple calls possible (note: key in bytes format)
ratelimit.get_ratelimit(
key=b"abc", rate="1/s", group="123", action=ratelimit.Action.INCREASE
)
# retrieve ratelimit
rlimit = ratelimit.get_ratelimit(
key="ip", rate="1/s", request=request, group="123"
)
# reset (clears internal counter)
counter_before_reset = rlimit.reset()
# reset epoch (resets to the start of request/epoch)
counter_before_reset = rlimit.reset(request)
# decrease counter by arbitary amount
rlimit.reset(19)
# increase counter by arbitary amount
rlimit.reset(-19)
# check constraints of rate
r = ratelimit.parse_rate("1/s") # returns tuple (amount, period)
assert(r[1]==1) # assert period is 1 second
# for simple naming use o2g (object to group)
ratelimit.get_ratelimit(
key=b"abc", rate=r, group=ratelimit.o2g(func), action=ratelimit.Action.INCREASE
)
manual async
import django_fast_ratelimit as ratelimit
async def func(request):
# retrieve ratelimit
rlimit = await ratelimit.aget_ratelimit(
key="ip", rate="1/s", request=request, group="123"
)
# reset (clears internal counter)
await rlimit.areset()
# reset epoch (resets to the start of request/epoch)
await rlimit.areset(request)
# decrease counter by arbitary amount
await rlimit.areset(19)
# increase counter by arbitary amount
await rlimit.reset(-19)
returns the dataclass Ratelimit
or raises ratelimit.Disabled
in case of the count in the rate is zero
Fields
Functions:
Note: decorate_object with name=None behaves like check (except return value), the same applies for adecorate_object
arguments:
why only async methods have wait? It doesn't really block (only the userlandthread). In contrast to its sync equivalent it doesn't block the webserver significantly
Example: decorate_object
import ratelimit
class Foo():
pass
r = get_ratelimit(
group="foo",
rate="1/s",
key=b"foo",
action=ratelimit.Action.INCREASE,
)
# manual way
foo = r.decorate_object(Foo(), name="ratelimit")
if not foo.ratelimit.check():
raise ratelimit.RatelimitExceeded("custom message", ratelimit=r)
else:
pass
# do cool stuff
# simplified
foo2 = r.decorate_object(Foo(), block=True)
# artistic (no point in doing so)
r.decorate_object(Foo(), name="ratelimit_is_cool").ratelimit_is_cool.check(block=True)
# like check with instance of Foo() as return value
foo3 r.decorate_object(Foo(), name=None, wait=True)
# decorate function
@r.decorate_object(block=True)
def fn():
pass
# of course also this works
@r.decorate_object
def fn():
pass
same as get_ratelimit
but supports async methods and has an optional parameter:
wait
, which suspends the execution (via asyncio.sleep
) for the time specified in rate (second argument).
This is only possible in async mode, as it would block too much in sync mode.
All of ratelimit.get_ratelimit except request. group is here optional (except for decorations with method_decorator (no access to wrapped function)). Also supports:
why only async methods have wait? It doesn't really block (only the userlandthread). In contrast to its sync equivalent it doesn't block the webserver significantly
inverts a collection, useful for http methods
get the RATELIMIT_TRUSTED_PROXIES
parsed as set
note: this function uses a cached subfunction. If you change this setting while testing you may have to call:
ratelimit._get_RATELIMIT_TRUSTED_PROXY.cache_clear()
get client ip from request, using RATELIMIT_TRUSTED_PROXIES
and forwarded headers
import ratelimit
ratelimit.get_ip(request)
auto generate group names for method/function as input, see tests/test_decorators for more info
Example:
import ratelimit
class O2gView(View):
def get(self, request, *args, **kwargs):
request.ratelimit2 = ratelimit.get_ratelimit(
group=ratelimit.o2g(self.get),
rate="1/s",
key=b"o2gtest",
action=ratelimit.Action.INCREASE,
)
if request.ratelimit2.request_limit > 0:
return HttpResponse(status=400)
return HttpResponse()
Raised when the ratelimit was exceeded
Exception, required keyword argument is ratelimit with the ratelimit. The next arguments are passed to the underlying standard exception class for e.g. customizing the error message
Stronger variant of RatelimitExceeded. Used for cases where limit is 0 and there is no way to pass the ratelimit. It is a shortcut for disabling api.
Note: it is weaker than the setting RATELIMIT_ENABLED
Note: it isn't a subclass from RatelimitExceeded because APIs should be able to differ both cases
Note: in contrast to RatelimitExceeded it is raised in (a)get_ratelimit and when using decorate, the view function isn't called.
for libraries. In case of async return protected asyncified function otherwise call library directly
jitter:
import ratelimit
import asyncio
import secrets
async def foo()
r = await ratelimit.aget_ratelimit(
group="foo",
rate="1/s",
key=b"foo",
action=ratelimit.Action.INCREASE,
)
# 100ms jitter
await asyncio.sleep(secrets.randbelow(100) / 100)
# raise when limit reached, wait until full second jitter is eliminated in raise case as end was created before the jitter
await r.acheck(wait=True, block=True)
See in methods which methods are available. Here some of them:
static
: use static key defined by argument, if no argument was specified default to b"static", the argument is automatically converted to bytes
Note: the conversion for non-bytes objects is str(obj).encode("utf8")
Note: it is also possible to specify a bytes key to provide a static argument
ip
: use ip address as key, argument: [netmask ipv4/]netmask ipv6
user
: authenticated user primary key or b""
user_or_ip
: use autenticated user primary key as key. If not autenticated fallback to ip, also with netmask argument.
user_and_ip
: same like user_or_ip except that the ip matching also applies for authenticated users
ip_exempt_user
: same like user_or_ip except that authenticated users are exempted or the inversion (true). Takes 0-2 arguments (netmask and invert (true,false))
has the special argument true, which cause the inversion of the user check (false is also possible but without effect). The behavior for multiple true,false is unspecified. Setting to true exempts requests without a user
When specified with reset actions: reset the ip key with the reset action when a user was found. If inverted the inversion is the case.
Note: true,false must be strings, True (bool) is used by netmask
user_or_ip_exempt
:
user with fallback ip. Exempts for user specs.
Note: when permissions, user_ok and staff_ok are not specified exempts for superuser only. Same behavior for reset like ip_exempt_user
With following parameters (either list or comma seperated):
When resetting the key is only resetted if exempted (higher privilege). The invert stuff applies here too (see ip_exempt_user
).
get
: generate key from multiple sources, input can be multiple input args or a dict with options
RATELIMIT_TESTCLIENT_FALLBACK
: in case instead of a client ip a testclient is detected map to the fallback. Set to "invalid" to fail. Default ::1RATELIMIT_GROUP_HASH
: hash function which is used for the group hash (default: md5)RATELIMIT_KEY_HASH
: hash function which is used as default for the key hash, can be overridden with hash_algo (default: md5)RATELIMIT_ENABLED
disable ratelimit (e.g. for tests) (default: enabled)RATELIMIT_ENABLE
deprecated old name of RATELIMIT_ENABLEDRATELIMIT_KEY_PREFIX
: internal prefix for the hash keys (so you don't have to create a new cache). Defaults to "frl:".RATELIMIT_DEFAULT_CACHE
: default cache to use, defaults to "default" and can be overridden by cache parameterRATELIMIT_TRUSTED_PROXIES
: "all" for allowing all ip addresses to provide forward informations, or an iterable with proxy ips (will be transformed to a set). Note there is a special ip: "unix" for unix sockets. Default: ["unix"]
Used headers are: Forwarded
, X-Forwarded-For
in version 9.0.0: ip_exempt_superuser and ip_exempt_privileged are replaced by user_or_ip_exempt
in version 8.0.0: rate is the 4th argument of a key function, I need it for django-fast-iprestrict
in version 7.3.0: rate is now optional (when having an appropiate key (function))
in version 7.2.0: RATELIMIT_ENABLE
is renamed to RATELIMIT_ENABLED
, the old setting is still available, note: in tests where this settings are changed dynamically you may have to import _get_RATELIMIT_ENABLED and clear the cache, in most cases this isn't neccessary
in version 7.0.0 method, group and key functions take an additional parameter: action
in version 6.0.0 some small new restrictions are introduced for key functions as string
in version 5.0.0 the package was renamed to django_fast_ratelimit for having an unique namespace. Reason, we have now a companion library: django-fast-iprestrict Sorry for the big breaking change.
in version 4.0.0 most parameters were made keyword only (helps finding bugs).
in version 3.0.0 the name parameter of (a)decorate_object was changed to ratelimit
in version 2.0.0 the parameter raise_on_limit
was removed and replaced by check(block=True)
in version 1.0.0 the parameter include_reset
was removed
in version 1.2.0 reset_epoch calls return the counter before reset instead of the count after
FAQs
Fast ratelimit implementation with django caches
We found that django-fast-ratelimit demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.