From: Michael Tremer Date: Tue, 30 Dec 2025 11:27:24 +0000 (+0000) Subject: dnsbl: Implement submitting a report X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=240ad1197e468ce0f1ecee2ed27cc9b57e721c8f;p=ipfire.org.git dnsbl: Implement submitting a report Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index f13fff09..a6f3ff7b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -185,7 +185,9 @@ templates_blog_modulesdir = $(templates_blogdir)/modules templates_dnsbl_DATA = \ src/templates/dnsbl/index.html \ - src/templates/dnsbl/list.html + src/templates/dnsbl/list.html \ + src/templates/dnsbl/report.html \ + src/templates/dnsbl/report-submitted.html templates_dnsbldir = $(templatesdir)/dnsbl diff --git a/src/backend/dnsbl.py b/src/backend/dnsbl.py index 6244106a..8ce3274a 100644 --- a/src/backend/dnsbl.py +++ b/src/backend/dnsbl.py @@ -7,6 +7,7 @@ import pydantic import tornado.httpclient import urllib.parse +from . import accounts from . import base from .misc import Object from .decorators import * @@ -15,15 +16,32 @@ from .decorators import * log = logging.getLogger(__name__) class DNSBL(Object): - async def _fetch(self, path, **kwargs): + async def _fetch(self, path, headers=None, body=None, **kwargs): + if headers is None: + headers = {} + url = urllib.parse.urljoin( #"https://api.dnsbl.ipfire.org", "http://dnsbl01.haj.ipfire.org:8000", path, ) + # Authenticate + headers |= { + "X-API-Key" : self.backend.settings.get("dnsbl-api-key", ""), + } + + # Serialize any content + if body: + headers |= { + "Content-Type" : "application/json", + } + + # Serialize the body + body = json.dumps(body) + # Send the request response = await self.backend.http_client.fetch( - url, **kwargs, + url, headers=headers, body=body, **kwargs, ) # Decode the response @@ -115,6 +133,31 @@ class List(Model): return [Source(self._backend, **data) for data in response] + # Report! + async def report(self, name, reported_by, comment=None, block=True): + """ + Submits a report + """ + # Only submit the UID of the user + if isinstance(reported_by, accounts.Account): + reported_by = reported_by.uid + + # Compose the request body + body = { + "name" : name, + "reported_by" : reported_by, + "comment" : comment, + "block" : block + } + + # Submit the report + response = await self._backend.dnsbl._fetch( + "/lists/%s/reports" % self.slug, method="POST", body=body, + ) + + # Return the report + return Report(self._backend, **response) + class Source(Model): """ @@ -161,3 +204,35 @@ class Source(Model): return self.name < other.name return NotImplemented + + +class Report(Model): + """ + Represents a report + """ + # ID + id : int + + # Name + name : str + + # Reported At + reported_at : datetime.datetime + + # Reported By + reported_by : str + + # Closed At + closed_at : datetime.datetime | None = None + + # Closed By + closed_by : str | None = None + + # Comment + comment : str = "" + + # Block? + block : bool = True + + # Accepted? + accepted : bool = False diff --git a/src/templates/base.html b/src/templates/base.html index 0e3f687c..211ae8d6 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -101,6 +101,12 @@ {{ _("How To Use?") }} + + {{ _("Report") }} + + {# Location #} {% elif request.path.startswith("/location") %} + +{% end block %} diff --git a/src/templates/dnsbl/report.html b/src/templates/dnsbl/report.html new file mode 100644 index 00000000..93a623c6 --- /dev/null +++ b/src/templates/dnsbl/report.html @@ -0,0 +1,162 @@ +{% extends "../base.html" %} + +{% block title %} + {{ _("IPFire DNSBL") }} {{ _("Report A Domain") }} +{% end block %} + +{% block head %} + {% module OpenGraph( + title=_("Report A Domain"), + description=_("Help Us To Improve IPFire DNSBL"), + ) %} +{% end block %} + +{% block container %} +
+
+
+

+ {{ _("Help Us To Improve IPFire DNSBL") }} +

+ +

+ {{ _("Report anything that we have missed") }} +

+
+
+
+ +
+
+ {# Show a note to users that are not logged in #} + {% if not current_user %} +
+ {{ _("Please log in to submit a report") }} + +
+ + To keep the IPFire DNSBL accurate and trustworthy, domain reports + can only be submitted by logged-in users. + This helps us prevent spam and abuse, and ensures that every report + comes from a real, accountable community member. +
+ {% end %} + +
+ {% raw xsrf_form_html() %} + +
+ {# List #} +
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+ + {# Name #} +
+
+ +
+ +
+
+

+ +

+
+
+
+ + {# Block? #} +
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+ + {# Comment #} +
+
+ +
+ +
+
+

+ +

+ +

+ Please provide any additional context that may help us review this domain. + For example, where you encountered it or why you believe it should be (de-)listed. +

+ +

+ By submitting your report, you grant the IPFire Project the right to store, + process, and publish this information as part of its security services, + including the IPFire DNSBL under the terms of the respective list. + Submissions may be reviewed, modified, or removed at our discretion. +

+
+
+
+ + {# Submit! #} +
+
+ +
+ +
+
+
+ +
+
+
+
+
+
+
+
+{% end block %} diff --git a/src/web/__init__.py b/src/web/__init__.py index 0a50a39d..725c038b 100644 --- a/src/web/__init__.py +++ b/src/web/__init__.py @@ -216,6 +216,7 @@ class Application(tornado.web.Application): # DNSBL (r"/dnsbl/?", dnsbl.IndexHandler), (r"/dnsbl/lists/(\w+)", dnsbl.ListHandler), + (r"/dnsbl/report", dnsbl.ReportHandler), # Single-Sign-On for Discourse (r"/sso/discourse", auth.SSODiscourse), diff --git a/src/web/dnsbl.py b/src/web/dnsbl.py index a8a4e21d..12dd9eac 100644 --- a/src/web/dnsbl.py +++ b/src/web/dnsbl.py @@ -4,7 +4,15 @@ import tornado.web from . import base from . import ui_modules -class IndexHandler(base.AnalyticsMixin, base.BaseHandler): +class BaseHandler(base.BaseHandler): + async def get_list(self, *args, **kwargs): + slug = self.get_argument(*args, **kwargs) + + # Fetch the list + return await self.backend.dnsbl.get_list(slug) + + +class IndexHandler(base.AnalyticsMixin, BaseHandler): async def get(self): # Fetch all lists lists = await self.backend.dnsbl.get_lists() @@ -13,7 +21,7 @@ class IndexHandler(base.AnalyticsMixin, base.BaseHandler): self.render("dnsbl/index.html", lists=lists) -class ListHandler(base.AnalyticsMixin, base.BaseHandler): +class ListHandler(base.AnalyticsMixin, BaseHandler): async def get(self, slug): # Fetch the list list = await self.backend.dnsbl.get_list(slug) @@ -27,6 +35,32 @@ class ListHandler(base.AnalyticsMixin, base.BaseHandler): self.render("dnsbl/list.html", list=list, sources=sources) +class ReportHandler(base.AnalyticsMixin, BaseHandler): + async def get(self): + # Fetch all lists + lists = await self.backend.dnsbl.get_lists() + + # Render the page + self.render("dnsbl/report.html", lists=lists) + + @tornado.web.authenticated + #@base.ratelimit(minutes=60, requests=10) + async def post(self): + # Fetch the list + list = await self.get_list("list") + + # Create the report + report = await list.report( + name = self.get_argument("name"), + reported_by = self.current_user, + comment = self.get_argument("comment", ""), + block = self.get_argument("block", "off") == "on", + ) + + # Render a result page + self.render("dnsbl/report-submitted.html", report=report) + + class ListsModule(ui_modules.UIModule): def render(self, lists): return self.render_string("dnsbl/modules/lists.html", lists=lists)