Skip to content

Commit

Permalink
Merge pull request #726 from ae-utbm/honeypot
Browse files Browse the repository at this point in the history
better honeypot logging
  • Loading branch information
imperosol authored Jul 22, 2024
2 parents 002d8f8 + 2c8f18d commit 811e5a5
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 9 deletions.
17 changes: 14 additions & 3 deletions core/templatetags/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,23 @@ def __init__(self, environment: Environment) -> None:

def parse(self, parser: Parser) -> nodes.Output:
lineno = parser.stream.expect("name:render_honeypot_field").lineno
key = nodes.Name("render_honeypot_field", "load", lineno=lineno)
if parser.stream.current.type != "block_end":
field_name = parser.parse_expression()
else:
field_name = nodes.Const(None)
call = self.call_method(
"_render",
[nodes.Name("render_honeypot_field", "load", lineno=lineno)],
[key, field_name],
lineno=lineno,
)
return nodes.Output([nodes.MarkSafe(call)])

def _render(self, render_honeypot_field: Callable[[str | None], str]):
return render_to_string("honeypot/honeypot_field.html", render_honeypot_field())
def _render(
self,
render_honeypot_field: Callable[[str | None], str],
field_name: str | None = None,
):
return render_to_string(
"honeypot/honeypot_field.html", render_honeypot_field(field_name=field_name)
)
14 changes: 14 additions & 0 deletions core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import PIL
from django.conf import settings
from django.core.files.base import ContentFile
from django.http import HttpRequest
from django.utils import timezone
from PIL import ExifTags
from PIL.Image import Resampling
Expand Down Expand Up @@ -297,3 +298,16 @@ def bbcode_to_markdown(text):
new_text.append(line)

return "\n".join(new_text)


def get_client_ip(request: HttpRequest) -> str | None:
headers = (
"X_FORWARDED_FOR", # Common header for proixes
"FORWARDED", # Standard header defined by RFC 7239.
"REMOTE_ADDR", # Default IP Address (direct connection)
)
for header in headers:
if (ip := request.META.get(header)) is not None:
return ip

return None
1 change: 1 addition & 0 deletions forum/templates/forum/reply.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
{% endif %}
<form action="" method="post" enctype="multipart/form-data">
{% csrf_token %}
{% render_honeypot_field settings.HONEYPOT_FIELD_NAME_FORUM %}
{{ form.as_p() }}
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
</form>
Expand Down
21 changes: 19 additions & 2 deletions forum/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#

import pytest
from django.conf import settings
from django.test import Client
from django.urls import reverse
from pytest_django.asserts import assertRedirects
Expand All @@ -24,13 +25,14 @@

@pytest.mark.django_db
class TestTopicCreation:
def test_topic_creation_success(self, client: Client):
def test_topic_creation_ok(self, client: Client):
user: User = User.objects.get(username="root")
forum = Forum.objects.get(name="AE")
client.force_login(user)
payload = {
"title": "Hello IT.",
"message": "Have you tried turning it off and on again ?",
settings.HONEYPOT_FIELD_NAME_FORUM: settings.HONEYPOT_VALUE,
}
assert not ForumTopic.objects.filter(_title=payload["title"]).exists()
response = client.post(reverse("forum:new_topic", args=str(forum.id)), payload)
Expand All @@ -46,13 +48,28 @@ def test_topic_creation_success(self, client: Client):
assert topic
assert topic.last_message.message == payload["message"]

def test_topic_creation_failure(self, client: Client):
def test_topic_creation_honeypot_fail(self, client: Client):
user: User = User.objects.get(username="root")
forum = Forum.objects.get(name="AE")
client.force_login(user)
payload = {
"title": "You shall",
"message": "Not pass !",
settings.HONEYPOT_FIELD_NAME_FORUM: settings.HONEYPOT_VALUE + "random",
}
assert not ForumTopic.objects.filter(_title=payload["title"]).exists()
response = client.post(reverse("forum:new_topic", args=str(forum.id)), payload)
assert response.status_code == 200
assert not ForumTopic.objects.filter(_title=payload["title"]).exists()

def test_topic_creation_fail(self, client: Client):
user: User = User.objects.get(username="krophil")
forum = Forum.objects.get(name="AE")
client.force_login(user)
payload = {
"title": "You shall",
"message": "Not pass !",
settings.HONEYPOT_FIELD_NAME_FORUM: settings.HONEYPOT_VALUE,
}
assert not ForumTopic.objects.filter(_title=payload["title"]).exists()
response = client.post(reverse("forum:new_topic", args=str(forum.id)), payload)
Expand Down
12 changes: 12 additions & 0 deletions forum/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#
#
import math
from functools import partial

from ajax_select import make_ajax_field
from django import forms
Expand All @@ -32,11 +33,13 @@
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy
from django.utils import html, timezone
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView, RedirectView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from haystack.query import RelatedSearchQuerySet
from honeypot.decorators import check_honeypot

from core.views import (
CanCreateMixin,
Expand Down Expand Up @@ -242,6 +245,9 @@ class Meta:
title = forms.CharField(required=True, label=_("Title"))


@method_decorator(
partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
)
class ForumTopicCreateView(CanCreateMixin, CreateView):
model = ForumMessage
form_class = TopicForm
Expand Down Expand Up @@ -331,6 +337,9 @@ def get_redirect_url(self, *args, **kwargs):
return self.object.get_url()


@method_decorator(
partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
)
class ForumMessageEditView(CanEditMixin, UpdateView):
model = ForumMessage
form_class = forms.modelform_factory(
Expand Down Expand Up @@ -381,6 +390,9 @@ def get_redirect_url(self, *args, **kwargs):
return self.object.get_absolute_url()


@method_decorator(
partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
)
class ForumMessageCreateView(CanCreateMixin, CreateView):
model = ForumMessage
form_class = forms.modelform_factory(
Expand Down
13 changes: 9 additions & 4 deletions sith/honeypot.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import logging
from time import localtime, strftime
from typing import Any

from django.http import HttpResponse
from django.test.client import WSGIRequest
from django.http import HttpRequest, HttpResponse

from core.utils import get_client_ip


def custom_honeypot_error(
request: WSGIRequest, context: dict[str, Any]
request: HttpRequest, context: dict[str, Any]
) -> HttpResponse:
logging.warning(f"HoneyPot blocked user with ip {request.META.get('REMOTE_ADDR')}")
logging.warning(
f"[{strftime('%c', localtime())}] "
f"HoneyPot blocked user with ip {get_client_ip(request)}"
)
return HttpResponse("Upon reading this, the http client was enlightened.")
1 change: 1 addition & 0 deletions sith/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@
HONEYPOT_FIELD_NAME = "body2"
HONEYPOT_VALUE = "content"
HONEYPOT_RESPONDER = custom_honeypot_error # Make honeypot errors less suspicious
HONEYPOT_FIELD_NAME_FORUM = "message2" # Only used on forum


# Email
Expand Down

0 comments on commit 811e5a5

Please sign in to comment.