Subject: [PATCH] pgarchives: Allow use of IP ranges for SEARCH_CLIENTS

From: Célestin Matte <[email-masked]>
Date: Mon, 25 Oct 2021 17:24:39 +0200

Hello,

It's only possible to define single IP addresses in the SEARCH_CLIENTS directive (which defines who can use the search API). This patch allows to use IP ranges such as 0.0.0.0/0.
That said, now that I've understood that SEARCH_CLIENTS should contain pgweb server's IP address and not the end user's one, I'm not entirely sure this patch is useful.
It's up for discussion, then.

Cheers,
-- 
Célestin Matte

---

From: Célestin Matte <[email-masked]>
Date: Mon, 25 Oct 2021 17:25:37 +0200

Attached
-- 
Célestin Matte

From 250d3734ff35b3af9adf51cf3840f957cb682790 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=A9lestin=20Matte?= <[email-masked]>
Date: Thu, 21 Oct 2021 21:50:38 +0200
Subject: [PATCH] Allow use of IP ranges for SEARCH_CLIENTS

---
 django/archives/mailarchives/views.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/django/archives/mailarchives/views.py b/django/archives/mailarchives/views.py
index f711ce4..885f808 100644
--- a/django/archives/mailarchives/views.py
+++ b/django/archives/mailarchives/views.py
@@ -20,6 +20,7 @@ import email.parser
 import email.policy
 from io import BytesIO
 from urllib.parse import quote
+import ipaddress
 
 import json
 
@@ -709,7 +710,12 @@ def search(request):
         return HttpResponseForbidden('Not public archives')
 
     # Only certain hosts are allowed to call the search API
-    if not request.META['REMOTE_ADDR'] in settings.SEARCH_CLIENTS:
+    allowed = False
+    for ip_range in settings.SEARCH_CLIENTS:
+        if ipaddress.ip_address(request.META['REMOTE_ADDR']) in ipaddress.ip_network(ip_range):
+            allowed = True
+            break
+    if not allowed:
         return HttpResponseForbidden('Invalid host')
 
     curs = connection.cursor()
-- 
2.33.1

---

From: Magnus Hagander <[email-masked]>
Date: Wed, 27 Oct 2021 15:42:00 +0200

> Attached

LGTM. Applied, thanks!

-- 
 Magnus Hagander
 Me: https://www.hagander.net/ <http://www.hagander.net/>
 Work: https://www.redpill-linpro.com/ <http://www.redpill-linpro.com/>

---

From: Célestin Matte <[email-masked]>
Date: Thu, 4 Nov 2021 17:40:49 +0100

It makes sense to do the same thing for API_CLIENTS, a similar variable. See patch attached.
-- 
Célestin Matte

From 4ce0343af7db3f05640f6820b3d0999d27a3adb7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=A9lestin=20Matte?= <[email-masked]>
Date: Thu, 4 Nov 2021 17:38:36 +0100
Subject: [PATCH] Allow use of IP ranges for API_CLIENTS

---
 django/archives/mailarchives/api.py | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/django/archives/mailarchives/api.py b/django/archives/mailarchives/api.py
index 8909dac..a6b2536 100644
--- a/django/archives/mailarchives/api.py
+++ b/django/archives/mailarchives/api.py
@@ -1,6 +1,7 @@
 from django.http import HttpResponse, HttpResponseForbidden
 from django.shortcuts import get_object_or_404
 from django.conf import settings
+import ipaddress
 
 from .views import cache
 from .models import Message, List
@@ -8,12 +9,19 @@ from .models import Message, List
 import json

+def is_host_allowed(request):
+    for ip_range in settings.API_CLIENTS:
+        if ipaddress.ip_address(request.META['REMOTE_ADDR']) in ipaddress.ip_network(ip_range):
+            return True
+    return False
+
+
 @cache(hours=4)
 def listinfo(request):
     if not settings.PUBLIC_ARCHIVES:
         return HttpResponseForbidden('No API access on private archives for now')
 
-    if not request.META['REMOTE_ADDR'] in settings.API_CLIENTS:
+    if not is_host_allowed(request):
         return HttpResponseForbidden('Invalid host')
 
     resp = HttpResponse(content_type='application/json')
@@ -33,7 +41,7 @@ def latest(request, listname):
     if not settings.PUBLIC_ARCHIVES:
         return HttpResponseForbidden('No API access on private archives for now')
 
-    if not request.META['REMOTE_ADDR'] in settings.API_CLIENTS:
+    if not is_host_allowed(request):
         return HttpResponseForbidden('Invalid host')
 
     # Return the latest <n> messages on this list.
@@ -94,7 +102,7 @@ def thread(request, msgid):
     if not settings.PUBLIC_ARCHIVES:
         return HttpResponseForbidden('No API access on private archives for now')
 
-    if not request.META['REMOTE_ADDR'] in settings.API_CLIENTS:
+    if not is_host_allowed(request):
         return HttpResponseForbidden('Invalid host')
 
     # Return metadata about a single thread. A list of all the emails
-- 
2.33.1

---

From: Magnus Hagander <[email-masked]>
Date: Tue, 30 Nov 2021 21:04:00 +0100

> It makes sense to do the same thing for API_CLIENTS, a similar variable.
Yup, seems reasonable -- I missed that as well in the first submission.

Applied, thanks!