Fantomas’side

Weblog open-source

Stop aux spams sur django.contrib.comments

Depuis quelques jours, les formulaires de commentaires fournis par l'application django.contrib.comments dans la distribution de Django, sont devenus vulnérables aux spams.

L'application comments contient par défauts plusieurs sécurités pour éviter le spamming, comme un honeypot et la vérification du temps de saisie du commentaires.

Malgré cela les équipes de spammer sont très réactives, et coder un bot spécifique pour les sites django utilisant ce système de commentaires, rend tous ces sites vulnérables.

En parallèle de cela, dans la version 1.1b de Django, l'application comments acquiert une nouvelle fonctionnalité, qu'est la modération automatique. L'envois de mails en cas de commentaires, et aussi prise en charge.

J'en ai donc profité pour développer une protection contre le spam plus efficace basée sur Akismet à partir de cette nouvelle fonctionnalité.

"""Moderator of Entry comments
   Based on Akismet for checking spams
   Need to override the default Moderator,
   for getting request in parameters."""
from django.conf import settings
from django.utils.encoding import smart_str
from django.contrib.sites.models import Site
from django.db.models import signals
from django.contrib import comments
from django.contrib.comments.signals import comment_will_be_posted
from django.contrib.comments.moderation import Moderator
from django.contrib.comments.moderation import CommentModerator

from django.conf.settings import MAIL_COMMENT
from django.conf.settings import AKISMET_COMMENT

AKISMET_API_KEY = getattr(settings, 'AKISMET_API_KEY', '')

class EntryCommentModerator(CommentModerator):
    """Moderate the comment of Entry"""
    email_notification = MAIL_COMMENT
    enable_field = 'comment_enabled'

    def email(self, comment, content_object):
        if comment.is_public:
            super(EntryCommentModerator, self).email(comment, content_object)
           
    def moderate_akismet(self, comment, content_object, request):
        """Need to pass Akismet test"""
        if not AKISMET_COMMENT:
            return False

        from akismet import Akismet
        from akismet import APIKeyError

        akismet = Akismet(key=AKISMET_API_KEY,
                          blog_url='http://%s/' % Site.objects.get_current().domain)
        if akismet.verify_key():
            akismet_data = {'user_ip': request.META.get('REMOTE_ADDR', ''),
                            'user_agent': request.META.get('HTTP_USER_AGENT', ''),
                            'referrer': request.META.get('HTTP_REFERER', 'unknown'),
                            'permalink': content_object.get_absolute_url(),
                            'comment_type': 'comment',
                            'comment_author': comment.userinfo.get('name', ''),
                            'comment_author_email': comment.userinfo.get('email', ''),
                            'comment_author_url': comment.userinfo.get('url', '')}
            return akismet.comment_check(smart_str(comment.comment),
                                         data=akismet_data,
                                         build_data=True)
        raise APIKeyError("Your Akismet API key is invalid.")


class EntryModerator(Moderator):
    """Moderator for Entry"""

    def connect(self):
        comment_will_be_posted.connect(self.pre_save_moderation, sender=comments.get_model())
        signals.post_save.connect(self.post_save_moderation, sender=comments.get_model())
       

    def pre_save_moderation(self, sender, comment, request, **kwargs):
        """Overriding the existing method by passing request to allow"""
        super(EntryModerator, self).pre_save_moderation(sender, comment, **kwargs)

        moderation_class = self._registry[comment.content_type.model_class()]
        if moderation_class.moderate_akismet(comment, comment.content_object, request):
            comment.is_public = False



moderator = EntryModerator()

Il suffit juste d'installer le module Akismet pour Python, et cela devrais fonctionner en définissant sa clef d'API Akismet.

Bon par contre, on remarque de suite qu'il est dommage de devoir écrire une nouvelle classe Moderator, pour pouvoir récupérer et passer le context, c'est peu pratique en effet, surtout que comments peux le gérer par défaut avec le signal comment_will_be_posted.

Mais à priori cela risque de changer si on en regarde le ticket suivant, prévu pour la version finale de Django 1.1 : Ticket 10878, projet Django

Donc une réécriture du code sera certainement nécessaire à la sortie de cette version, qui commence à se faire attendre....

Pour plus d'informations : Documentation sur la modération