Metadata-Version: 2.4
Name: django-blacklist-cache
Version: 1.0.0
Summary: Blacklist users and hosts in Django using the cache framework. Automatically blacklist rate-limited clients.
Home-page: https://github.com/diegoamferreira/django-blacklist-cache
Author: Diego Ferreira
Author-email: diego.ferreira@solyd.com.br
License: MIT
Keywords: django blacklist ratelimit firewall cache redis
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Security
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: Django
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# Django Blacklist Cache

Blacklist users and hosts in Django using the cache framework. Automatically blacklist rate-limited clients.

Fork of [django-blacklist](https://github.com/vsemionov/django-blacklist) that replaces database storage with Django's cache framework, making it ideal for serverless environments (AWS Lambda, Zappa) where database connections are expensive and ephemeral.


## Overview

Django Blacklist Cache allows you to automatically block clients that exceed request rate limits.
Instead of storing blacklist rules in the database, rules are stored in the configured cache backend (Redis, Memcached, etc.) with automatic expiration via TTL.

Key advantages over the database-backed approach:
- *No database connections* for blacklist operations
- *No migrations* to run
- *Instant propagation* across all application instances
- *Automatic expiration* of rules (no cleanup commands needed)
- *Graceful degradation* if the cache is unavailable, the site continues to work (no one gets blocked, but no errors either)


## Installation

To install the package, run:
```shell
$ pip install django-blacklist-cache
```

Add the `BlacklistMiddleware` middleware after `AuthenticationMiddleware`:
```python
MIDDLEWARE = [
    ...
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'blacklist.middleware.BlacklistMiddleware',
    ...
]
```

Configure a cache backend for the blacklist:
```python
CACHES = {
    'default': {
        ...
    },
    'blacklist': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': 'redis://localhost:6379/1',
    }
}

BLACKLIST_CACHE_ALIAS = 'blacklist'
```

That's it. No database migrations to run.
Add `'blacklist'` to `INSTALLED_APPS` only if you want to use the `blacklist_clear` management command.


## Why Use This Instead of Rate Limiting Alone?

[django-ratelimit](https://github.com/jsocol/django-ratelimit) blocks requests that exceed a rate within a time window (e.g., 50 requests/minute).
Once the window resets, the client can make requests again. This means an abusive client can keep hammering your server at the rate limit boundary forever -- 50 requests, wait a minute, 50 more, repeat.

Django Blacklist Cache adds a **ban period** on top of rate limiting.
When a client exceeds the rate limit, they are blocked for a configurable duration (e.g., 30 minutes) regardless of the rate-limit window resetting.

```
Without blacklist:
  Client â†’ 50 reqs â†’ blocked â†’ waits 1 min â†’ 50 reqs â†’ blocked â†’ waits 1 min â†’ ...

With blacklist:
  Client â†’ 50 reqs â†’ blocked for 30 min â†’ can't make ANY request for 30 min
```

This is especially important for:
- **Brute-force protection**: an attacker can't just wait for the rate-limit window to reset and try again
- **Resource protection**: abusive clients are fully cut off, freeing server resources
- **Escalating responses**: you can set longer ban durations for more sensitive endpoints


## Usage

### Automatic Blacklisting

Clients can be blacklisted automatically after exceeding a request rate limit.
This feature requires [django-ratelimit](https://github.com/jsocol/django-ratelimit).

First, rate-limit a view by applying the `@ratelimit` decorator. Make sure to set `block=False`.
Then, blacklist rate-limited clients by adding the `@blacklist_ratelimited` decorator. Specify the blacklist duration.
For example:
```python
from datetime import timedelta
from django_ratelimit.decorators import ratelimit
from blacklist.ratelimit import blacklist_ratelimited

@ratelimit(key='user_or_ip', rate='50/m', block=False)
@blacklist_ratelimited(timedelta(minutes=30))
def index(request):
    ...
```

Rules take effect immediately across all application instances.
If the request comes from an authenticated user, the rule will target that user.
Otherwise, it will target their IP address.

`@blacklist_ratelimited` accepts two arguments: `(duration, block=True)`.
* `duration` can be a `timedelta` object, or a tuple of two separate durations
(for user-based and IP-based rules).
* `block` specifies if the request should be blocked immediately, or passed to the view.

When a request is blocked due to a matching rule:
* Status 400 (bad request) is returned.
* An error template is rendered.
  You can specify a custom one (see *Settings* below), or use the one for status 400.
* A message is logged
  (warning from logger `blacklist.middleware` for custom templates, or error from logger `django.security` otherwise).


### Python API

You can manage blacklist rules programmatically:
```python
from datetime import timedelta
from blacklist import block_ip, block_user, unblock_ip, unblock_user, is_blocked_ip, is_blocked_user, clear_all

# Block
block_ip('203.0.113.45', duration=timedelta(hours=1))
block_user(42, duration=timedelta(minutes=30))

# Check
is_blocked_ip('203.0.113.45')  # True
is_blocked_user(42)             # True

# Unblock
unblock_ip('203.0.113.45')
unblock_user(42)

# Clear all rules (uses cache.clear() on the blacklist cache alias)
clear_all()
```

### Management Command

To use the management command, add `'blacklist'` to `INSTALLED_APPS`:
```python
INSTALLED_APPS = [
    ...
    'blacklist',
]
```

Clear all blacklist rules:
```shell
$ python manage.py blacklist_clear
```

Remove a specific IP or user:
```shell
$ python manage.py blacklist_clear --ip 203.0.113.45
$ python manage.py blacklist_clear --user 42
```


## Proxies and Client Addresses

By default, the client IP address is taken from the `REMOTE_ADDR` value of `request.META`.
If your application server is behind one or more reverse proxies,
this will usually be the address of the nearest proxy, and not the actual client address.
To properly blacklist clients by IP address,
you can configure Django Blacklist Cache to use addresses from another source (see `Settings` below).

To actually obtain the proxied client addresses,
you can use [django-ipware](https://github.com/un33k/django-ipware).
In this case, you can configure Django Blacklist Cache to obtain client addresses from your function,
which in turn calls `django-ipware` for the actual logic.

Alternatively, you can set `REMOTE_ADDR` from the `X-Forwarded-For` header in middleware,
installed before Django Blacklist Cache.
However, keep in mind that this header can be forged to bypass the rate limits.
To counter that, you can use the last address in that header (which should be set by your trusted reverse proxy).
If you are behind two proxies, use the second to last address, and so on.


## Settings

* `BLACKLIST_ENABLE` - whether blacklisted clients should be blocked,
  and rate-limited clients should be blacklisted; default: `True`
* `BLACKLIST_CACHE_ALIAS` - the Django cache alias to use for storing blacklist rules;
  default: `'default'`
* `BLACKLIST_RATELIMITED_ENABLE` - whether rate-limited clients should be automatically blacklisted;
  requires `BLACKLIST_ENABLE`; default: `True`
* `BLACKLIST_TEMPLATE` - name of a custom error template to render to blocked clients;
  its context will contain `request` and `exception`;
  set to `None` to use the template for status 400; default: `None`
* `BLACKLIST_LOGGING_ENABLE` - whether blocked requests should be logged
  (honored only if a custom error template is configured); default: `True`
* `BLACKLIST_ADDRESS_SOURCE` - the source of client addresses; can be a key in `request.META`,
  a callable that receives the request object, or the dotted string path to such a callable;
  default: `'REMOTE_ADDR'`


## Migrating from django-blacklist

This package is a fork of [django-blacklist](https://github.com/vsemionov/django-blacklist) that replaces database storage with Django's cache framework.
The Python import name (`blacklist`) is the same, so your existing decorator usage (`@blacklist_ratelimited`) works without code changes.

### Step 1: Replace the package

```shell
$ pip uninstall django-blacklist
$ pip install django-blacklist-cache
```

### Step 2: Update settings.py

```python
# Add a cache backend for the blacklist (recommended: dedicated alias)
CACHES = {
    'default': {
        ...
    },
    'blacklist': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': 'redis://localhost:6379/1',
    }
}

# Point the blacklist to your cache alias
BLACKLIST_CACHE_ALIAS = 'blacklist'

# Remove this setting (no longer used)
# BLACKLIST_RELOAD_PERIOD = 60  <-- delete this line
```

All other settings (`BLACKLIST_ENABLE`, `BLACKLIST_RATELIMITED_ENABLE`, `BLACKLIST_TEMPLATE`, `BLACKLIST_LOGGING_ENABLE`, `BLACKLIST_ADDRESS_SOURCE`) remain the same.

### Step 3: Clean up

- Remove any `trim_blacklist` cron jobs. Cache TTL handles expiration automatically.
- No database migrations needed. You can drop the `blacklist_rule` table if desired:
  ```sql
  DROP TABLE IF EXISTS blacklist_rule;
  ```

### What changed

| | django-blacklist | django-blacklist-cache |
|---|---|---|
| Storage | Database (PostgreSQL, etc.) | Django cache (Redis, Memcached, etc.) |
| Expiration | Manual (`trim_blacklist` command) | Automatic (cache TTL) |
| Propagation | Periodic reload (default: 60s) | Instant across all instances |
| DB connections | Required | None |
| Django Admin | Yes (CRUD for rules) | No |
| CIDR network rules | Yes | No (individual IPs only) |
| INSTALLED_APPS | Required | Only for management command |
| Migrations | Required | Not needed |
