-
-
Notifications
You must be signed in to change notification settings - Fork 156
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2622 from Sarthak5598/ip_blocker
add ability to block ip addresses that are submitting spam #1938
- Loading branch information
Showing
5 changed files
with
274 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import ipaddress | ||
|
||
from django.core.cache import cache | ||
from django.http import HttpResponseForbidden | ||
from user_agents import parse | ||
|
||
from website.models import IP, Blocked | ||
|
||
|
||
class IPRestrictMiddleware: | ||
""" | ||
Middleware to restrict access based on client IP addresses and user agents. | ||
""" | ||
|
||
def __init__(self, get_response): | ||
self.get_response = get_response | ||
|
||
def blocked_ips(self): | ||
""" | ||
Retrieve blocked IP addresses from cache or database. | ||
""" | ||
blocked_ips = cache.get("blocked_ips") | ||
if blocked_ips is None: | ||
blocked_addresses = Blocked.objects.values_list("address", flat=True) | ||
blocked_ips = set(filter(None, blocked_addresses)) | ||
cache.set("blocked_ips", blocked_ips, timeout=86400) | ||
return blocked_ips | ||
|
||
def ip_in_ips(self, ip, blocked_ips): | ||
if blocked_ips is None: | ||
return False | ||
return ip in blocked_ips | ||
|
||
def blocked_ip_network(self): | ||
""" | ||
Retrieve blocked IP networks from cache or database. | ||
""" | ||
blocked_ip_network = cache.get("blocked_ip_network") | ||
if blocked_ip_network is None: | ||
blocked_network = Blocked.objects.values_list("ip_network", flat=True) | ||
blocked_ip_network = [ | ||
ipaddress.ip_network(range_str, strict=False) | ||
for range_str in filter(None, blocked_network) | ||
] | ||
cache.set("blocked_ip_network", blocked_ip_network, timeout=86400) | ||
return blocked_ip_network or [] | ||
|
||
def ip_in_range(self, ip, blocked_ip_network): | ||
""" | ||
Check if the IP address is within any of the blocked IP networks. | ||
""" | ||
if not blocked_ip_network: | ||
return False | ||
ip_obj = ipaddress.ip_address(ip) | ||
return any(ip_obj in ip_range for ip_range in blocked_ip_network if ip_range) | ||
|
||
def blocked_agents(self): | ||
""" | ||
Retrieve blocked user agents from cache or database. | ||
""" | ||
blocked_agents = cache.get("blocked_agents") | ||
if blocked_agents is None or blocked_agents == []: | ||
blocked_user_agents = Blocked.objects.values_list("user_agent_string", flat=True) | ||
if blocked_user_agents: | ||
blocked_agents = set(blocked_user_agents) | ||
cache.set("blocked_agents", blocked_agents, timeout=86400) | ||
return blocked_agents | ||
else: | ||
return None | ||
return blocked_agents | ||
|
||
def is_user_agent_blocked(self, user_agent, blocked_agents): | ||
""" | ||
Check if the user agent is in the list of blocked user agents. | ||
""" | ||
user_agent_str = str(user_agent).strip() | ||
|
||
if not blocked_agents: | ||
return False | ||
blocked_agents = [str(agent).strip() for agent in blocked_agents if str(agent).strip()] | ||
|
||
for blocked_agent in blocked_agents: | ||
blocked_agent_str = str(blocked_agent).strip() | ||
if blocked_agent_str.lower() in user_agent_str.lower(): | ||
return True | ||
|
||
return False | ||
|
||
def delete_all_info(self): | ||
Blocked.objects.all().delete() | ||
cache.delete("blocked_ips") | ||
cache.delete("blocked_ip_network") | ||
cache.delete("blocked_agents") | ||
|
||
def __call__(self, request): | ||
""" | ||
Process the request and restrict access based on IP address and user agent. | ||
""" | ||
ip = request.META.get("REMOTE_ADDR") | ||
agent = request.META.get("HTTP_USER_AGENT", "") | ||
user_agent = parse(agent) | ||
# If you want to clear everything use this | ||
# self.delete_all_info() | ||
|
||
if ( | ||
self.ip_in_ips(ip, self.blocked_ips()) | ||
or self.ip_in_range(ip, self.blocked_ip_network()) | ||
or self.is_user_agent_blocked(user_agent, self.blocked_agents()) | ||
): | ||
if self.ip_in_ips(ip, self.blocked_ips()) or self.ip_in_range( | ||
ip, self.blocked_ip_network() | ||
): | ||
return HttpResponseForbidden( | ||
"Your IP address is restricted from accessing this site." | ||
) | ||
if self.is_user_agent_blocked(user_agent, self.blocked_agents()): | ||
return HttpResponseForbidden( | ||
"Your user agent is restricted from accessing this site." | ||
) | ||
|
||
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") | ||
if x_forwarded_for: | ||
ip = x_forwarded_for.split(",")[0].strip() | ||
else: | ||
ip = request.META.get("REMOTE_ADDR") | ||
|
||
if ip: | ||
ip_record, created = IP.objects.get_or_create( | ||
address=ip, defaults={"agent": parse(agent), "count": 1, "path": request.path} | ||
) | ||
if not created: | ||
ip_record.agent = parse(agent) | ||
ip_record.count += 1 | ||
ip_record.path = request.path | ||
ip_record.save() | ||
|
||
return self.get_response(request) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Generated by Django 5.1 on 2024-08-13 08:55 | ||
|
||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("website", "0128_userprofile_discounted_hourly_rate_and_more"), | ||
] | ||
|
||
operations = [ | ||
migrations.CreateModel( | ||
name="Blocked", | ||
fields=[ | ||
( | ||
"id", | ||
models.AutoField( | ||
auto_created=True, | ||
primary_key=True, | ||
serialize=False, | ||
verbose_name="ID", | ||
), | ||
), | ||
("address", models.GenericIPAddressField(blank=True, null=True)), | ||
( | ||
"reason_for_block", | ||
models.TextField(blank=True, max_length=255, null=True), | ||
), | ||
("ip_network", models.GenericIPAddressField(blank=True, null=True)), | ||
( | ||
"user_agent_string", | ||
models.CharField(blank=True, default="", max_length=255, null=True), | ||
), | ||
("count", models.IntegerField(default=1)), | ||
("created", models.DateField(blank=True, null=True)), | ||
], | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters