From b54622437248d1b50b3ecec11a681b2867a91f09 Mon Sep 17 00:00:00 2001 From: William Boman Date: Tue, 10 Apr 2018 15:21:37 +0200 Subject: [PATCH] send signals when blocking username or ip (#114) --- README.md | 20 ++++++++++++++++++++ defender/signals.py | 17 +++++++++++++++++ defender/tests.py | 26 ++++++++++++++++++++++++++ defender/utils.py | 3 +++ 4 files changed, 66 insertions(+) create mode 100644 defender/signals.py diff --git a/README.md b/README.md index b36eefc..2ae3c37 100644 --- a/README.md +++ b/README.md @@ -442,6 +442,26 @@ class BasicAuthenticationDefender(BasicAuthentication): To make it works add `BasicAuthenticationDefender` to `DEFAULT_AUTHENTICATION_CLASSES` above all other authentication methods in your `settings.py`. + +Django Signals +-------------------- + +`django-defender` will send signals when blocking a username or an IP address. To set up signal receiver functions: + +```python +from django.dispatch import receiver +from defender import signals + +@receiver(signals.username_block) +def username_blocked(username, **kwargs): + print("%s was blocked!" % username) + +@receiver(signals.ip_block) +def ip_blocked(ip_address, **kwargs): + print("%s was blocked!" % ip_address) + +``` + Running Tests ============= diff --git a/defender/signals.py b/defender/signals.py new file mode 100644 index 0000000..4aaaec3 --- /dev/null +++ b/defender/signals.py @@ -0,0 +1,17 @@ +from django.dispatch import Signal + +username_block = Signal(providing_args=['username']) +ip_block = Signal(providing_args=['ip_address']) + +class BlockSignal: + """ + Providing a sender is mandatory when sending signals, hence + this empty sender class. + """ + pass + +def send_username_block_signal(username): + username_block.send(sender=BlockSignal, username=username) + +def send_ip_block_signal(ip_address): + ip_block.send(sender=BlockSignal, ip_address=ip_address) diff --git a/defender/tests.py b/defender/tests.py index 59b708a..11f2d81 100644 --- a/defender/tests.py +++ b/defender/tests.py @@ -23,6 +23,7 @@ except ImportError: from . import utils from . import config +from .signals import ip_block as ip_block_signal, username_block as username_block_signal from .connection import parse_redis_url, get_redis_connection from .decorators import watch_login from .models import AccessAttempt @@ -875,6 +876,31 @@ class AccessAttemptTest(DefenderTestCase): self.assertEqual(data_out, []) +class SignalTest(DefenderTestCase): + """ Test that signals are properly sent when blocking usernames and IPs. + """ + + def test_should_send_signal_when_blocking_ip(self): + self.blocked_ip = None + + def handler(sender, ip_address, **kwargs): + self.blocked_ip = ip_address + + ip_block_signal.connect(handler) + utils.block_ip('8.8.8.8') + self.assertEqual(self.blocked_ip, '8.8.8.8') + + def test_should_send_signal_when_blocking_username(self): + self.blocked_username = None + + def handler(sender, username, **kwargs): + self.blocked_username = username + + username_block_signal.connect(handler) + utils.block_username('richard_hendricks') + self.assertEqual(self.blocked_username, 'richard_hendricks') + + class DefenderTestCaseTest(DefenderTestCase): """ Make sure that we're cleaning the cache between tests """ key = 'test_key' diff --git a/defender/utils.py b/defender/utils.py index 43476ae..b94dba8 100644 --- a/defender/utils.py +++ b/defender/utils.py @@ -9,6 +9,7 @@ from django.core.exceptions import ValidationError from .connection import get_redis_connection from . import config from .data import store_login_attempt +from .signals import send_username_block_signal, send_ip_block_signal REDIS_SERVER = get_redis_connection() @@ -171,6 +172,7 @@ def block_ip(ip_address): REDIS_SERVER.set(key, 'blocked', config.COOLOFF_TIME) else: REDIS_SERVER.set(key, 'blocked') + send_ip_block_signal(ip_address) def block_username(username): @@ -186,6 +188,7 @@ def block_username(username): REDIS_SERVER.set(key, 'blocked', config.COOLOFF_TIME) else: REDIS_SERVER.set(key, 'blocked') + send_username_block_signal(username) def record_failed_attempt(ip_address, username):