mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-25 18:44:23 +00:00
commit
811e5a5ad1
@ -47,12 +47,23 @@ class HoneypotExtension(Extension):
|
|||||||
|
|
||||||
def parse(self, parser: Parser) -> nodes.Output:
|
def parse(self, parser: Parser) -> nodes.Output:
|
||||||
lineno = parser.stream.expect("name:render_honeypot_field").lineno
|
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(
|
call = self.call_method(
|
||||||
"_render",
|
"_render",
|
||||||
[nodes.Name("render_honeypot_field", "load", lineno=lineno)],
|
[key, field_name],
|
||||||
lineno=lineno,
|
lineno=lineno,
|
||||||
)
|
)
|
||||||
return nodes.Output([nodes.MarkSafe(call)])
|
return nodes.Output([nodes.MarkSafe(call)])
|
||||||
|
|
||||||
def _render(self, render_honeypot_field: Callable[[str | None], str]):
|
def _render(
|
||||||
return render_to_string("honeypot/honeypot_field.html", render_honeypot_field())
|
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)
|
||||||
|
)
|
||||||
|
@ -25,6 +25,7 @@ from typing import Optional
|
|||||||
import PIL
|
import PIL
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
from django.http import HttpRequest
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from PIL import ExifTags
|
from PIL import ExifTags
|
||||||
from PIL.Image import Resampling
|
from PIL.Image import Resampling
|
||||||
@ -297,3 +298,16 @@ def bbcode_to_markdown(text):
|
|||||||
new_text.append(line)
|
new_text.append(line)
|
||||||
|
|
||||||
return "\n".join(new_text)
|
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
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<form action="" method="post" enctype="multipart/form-data">
|
<form action="" method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
{% render_honeypot_field settings.HONEYPOT_FIELD_NAME_FORUM %}
|
||||||
{{ form.as_p() }}
|
{{ form.as_p() }}
|
||||||
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
|
<p><input type="submit" value="{% trans %}Save{% endtrans %}" /></p>
|
||||||
</form>
|
</form>
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from django.conf import settings
|
||||||
from django.test import Client
|
from django.test import Client
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from pytest_django.asserts import assertRedirects
|
from pytest_django.asserts import assertRedirects
|
||||||
@ -24,13 +25,14 @@ from forum.models import Forum, ForumMessage, ForumTopic
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
class TestTopicCreation:
|
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")
|
user: User = User.objects.get(username="root")
|
||||||
forum = Forum.objects.get(name="AE")
|
forum = Forum.objects.get(name="AE")
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
payload = {
|
payload = {
|
||||||
"title": "Hello IT.",
|
"title": "Hello IT.",
|
||||||
"message": "Have you tried turning it off and on again ?",
|
"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()
|
assert not ForumTopic.objects.filter(_title=payload["title"]).exists()
|
||||||
response = client.post(reverse("forum:new_topic", args=str(forum.id)), payload)
|
response = client.post(reverse("forum:new_topic", args=str(forum.id)), payload)
|
||||||
@ -46,13 +48,28 @@ class TestTopicCreation:
|
|||||||
assert topic
|
assert topic
|
||||||
assert topic.last_message.message == payload["message"]
|
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")
|
user: User = User.objects.get(username="krophil")
|
||||||
forum = Forum.objects.get(name="AE")
|
forum = Forum.objects.get(name="AE")
|
||||||
client.force_login(user)
|
client.force_login(user)
|
||||||
payload = {
|
payload = {
|
||||||
"title": "You shall",
|
"title": "You shall",
|
||||||
"message": "Not pass !",
|
"message": "Not pass !",
|
||||||
|
settings.HONEYPOT_FIELD_NAME_FORUM: settings.HONEYPOT_VALUE,
|
||||||
}
|
}
|
||||||
assert not ForumTopic.objects.filter(_title=payload["title"]).exists()
|
assert not ForumTopic.objects.filter(_title=payload["title"]).exists()
|
||||||
response = client.post(reverse("forum:new_topic", args=str(forum.id)), payload)
|
response = client.post(reverse("forum:new_topic", args=str(forum.id)), payload)
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
import math
|
import math
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from ajax_select import make_ajax_field
|
from ajax_select import make_ajax_field
|
||||||
from django import forms
|
from django import forms
|
||||||
@ -32,11 +33,13 @@ from django.db import IntegrityError
|
|||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils import html, timezone
|
from django.utils import html, timezone
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import DetailView, ListView, RedirectView
|
from django.views.generic import DetailView, ListView, RedirectView
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||||
from haystack.query import RelatedSearchQuerySet
|
from haystack.query import RelatedSearchQuerySet
|
||||||
|
from honeypot.decorators import check_honeypot
|
||||||
|
|
||||||
from core.views import (
|
from core.views import (
|
||||||
CanCreateMixin,
|
CanCreateMixin,
|
||||||
@ -242,6 +245,9 @@ class TopicForm(forms.ModelForm):
|
|||||||
title = forms.CharField(required=True, label=_("Title"))
|
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):
|
class ForumTopicCreateView(CanCreateMixin, CreateView):
|
||||||
model = ForumMessage
|
model = ForumMessage
|
||||||
form_class = TopicForm
|
form_class = TopicForm
|
||||||
@ -331,6 +337,9 @@ class ForumMessageView(SingleObjectMixin, RedirectView):
|
|||||||
return self.object.get_url()
|
return self.object.get_url()
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(
|
||||||
|
partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
|
||||||
|
)
|
||||||
class ForumMessageEditView(CanEditMixin, UpdateView):
|
class ForumMessageEditView(CanEditMixin, UpdateView):
|
||||||
model = ForumMessage
|
model = ForumMessage
|
||||||
form_class = forms.modelform_factory(
|
form_class = forms.modelform_factory(
|
||||||
@ -381,6 +390,9 @@ class ForumMessageUndeleteView(SingleObjectMixin, RedirectView):
|
|||||||
return self.object.get_absolute_url()
|
return self.object.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(
|
||||||
|
partial(check_honeypot, field_name=settings.HONEYPOT_FIELD_NAME_FORUM), name="post"
|
||||||
|
)
|
||||||
class ForumMessageCreateView(CanCreateMixin, CreateView):
|
class ForumMessageCreateView(CanCreateMixin, CreateView):
|
||||||
model = ForumMessage
|
model = ForumMessage
|
||||||
form_class = forms.modelform_factory(
|
form_class = forms.modelform_factory(
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from time import localtime, strftime
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.test.client import WSGIRequest
|
|
||||||
|
from core.utils import get_client_ip
|
||||||
|
|
||||||
|
|
||||||
def custom_honeypot_error(
|
def custom_honeypot_error(
|
||||||
request: WSGIRequest, context: dict[str, Any]
|
request: HttpRequest, context: dict[str, Any]
|
||||||
) -> HttpResponse:
|
) -> 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.")
|
return HttpResponse("Upon reading this, the http client was enlightened.")
|
||||||
|
@ -287,6 +287,7 @@ REST_FRAMEWORK["UNAUTHENTICATED_USER"] = "core.models.AnonymousUser"
|
|||||||
HONEYPOT_FIELD_NAME = "body2"
|
HONEYPOT_FIELD_NAME = "body2"
|
||||||
HONEYPOT_VALUE = "content"
|
HONEYPOT_VALUE = "content"
|
||||||
HONEYPOT_RESPONDER = custom_honeypot_error # Make honeypot errors less suspicious
|
HONEYPOT_RESPONDER = custom_honeypot_error # Make honeypot errors less suspicious
|
||||||
|
HONEYPOT_FIELD_NAME_FORUM = "message2" # Only used on forum
|
||||||
|
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
|
Loading…
Reference in New Issue
Block a user