Skip to content
This repository has been archived by the owner on Jan 27, 2021. It is now read-only.

Commit

Permalink
Merge pull request #1 from orsinium/clear
Browse files Browse the repository at this point in the history
Tests, some service features
  • Loading branch information
orsinium authored Apr 5, 2018
2 parents 85e897e + 7553e3e commit c06c718
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 11 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
__pycache__
build/
dist/
.tox
26 changes: 26 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
language: python
python:
- "2.7"
- "3.4"
- "3.6"
sudo: false
env:
- DJANGO=1.8
- DJANGO=1.11
- DJANGO=2.0
- DJANGO=master
matrix:
fast_finish: true
exclude:
- { python: "2.7", env: DJANGO=2.0 }
- { python: "2.7", env: DJANGO=master }
- { python: "3.4", env: DJANGO=master }
services:
- redis-server
install:
- if [[ $DJANGO == 'master' ]]; then pip install https://github.com/django/django/archive/master.tar.gz; fi
- if [[ $DJANGO != 'master' ]]; then pip install django==$DJANGO; fi
- pip install redis
- pip install -e .
script:
- cd example && python manage.py test
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

![DjBrut logo](logo.png)

[![Build Status](https://travis-ci.org/orsinium/django-bruteforce-protection.svg?branch=master)](https://travis-ci.org/orsinium/django-bruteforce-protection) [![PyPI version](https://img.shields.io/pypi/v/djbrut.svg)](https://pypi.python.org/pypi/djbrut) [![Status](https://img.shields.io/pypi/status/djbrut.svg)](https://pypi.python.org/pypi/djbrut) [![Code size](https://img.shields.io/github/languages/code-size/orsinium/django-bruteforce-protection.svg)](https://github.com/orsinium/django-bruteforce-protection) [![License](https://img.shields.io/pypi/l/djbrut.svg)](LICENSE)

DjBrut -- simple brutforce protection for Django project.

Default checkers:
Expand Down
20 changes: 19 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
DjBrut
======

.. figure:: logo.png
:alt: DjBrut logo

DjBrut logo

|Build Status| |PyPI version| |Status| |Code size| |License|

DjBrut -- simple brutforce protection for Django project.

Default checkers:
Expand All @@ -17,7 +24,7 @@ Installation

::

sudo pip3 install djbrut
pip install djbrut

Usage
-----
Expand Down Expand Up @@ -69,3 +76,14 @@ keys.

You can see `default settings <djbrut/default_settings.py>`__ for more
params such as custom error message.

.. |Build Status| image:: https://travis-ci.org/orsinium/django-bruteforce-protection.svg?branch=master
:target: https://travis-ci.org/orsinium/django-bruteforce-protection
.. |PyPI version| image:: https://img.shields.io/pypi/v/djbrut.svg
:target: https://pypi.python.org/pypi/djbrut
.. |Status| image:: https://img.shields.io/pypi/status/djbrut.svg
:target: https://pypi.python.org/pypi/djbrut
.. |Code size| image:: https://img.shields.io/github/languages/code-size/orsinium/django-bruteforce-protection.svg
:target: https://github.com/orsinium/django-bruteforce-protection
.. |License| image:: https://img.shields.io/pypi/l/djbrut.svg
:target: LICENSE
6 changes: 3 additions & 3 deletions djbrut/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

from .core import Attempt # noQA
from . import checkers # noQA
from .utils import Rule # noQA
from .core import Attempt, clear # noQA
from . import checkers # noQA
from .utils import Rule # noQA
33 changes: 28 additions & 5 deletions djbrut/checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ def get_attempts(self):
data = self.connection.get(self.key)
if not data:
return 0

# drop expired keys
ttl = self.connection.ttl(self.key)
if ttl < 0:
self.connection.delete(self.key)
return 0

return int(data)

def log(self):
Expand Down Expand Up @@ -100,6 +107,16 @@ def check(self):
# compare with limit
return count < self.limit

@classmethod
def clear(cls, connection, rule='*', value='*'):
template = cls.key_template.format(
rule=rule,
checker=cls.name,
value=value
)
for key in connection.keys(template):
connection.delete(key)


class UserChecker(BaseChecker):
name = 'user'
Expand All @@ -108,13 +125,19 @@ def get_value(self, request, user=None, **kwargs):
if not user:
if not request:
return
if not getattr(request, 'user'):
return
if not getattr(request, 'user'):
if not getattr(request, 'user', None):
return
user = request.user
if not user.is_authenticated():
return

# user.is_authenticated is callable for old Django and bool for Django 2.0
is_auth = user.is_authenticated
if type(is_auth) is bool:
if not is_auth:
return
else:
if not is_auth():
return

return user.pk


Expand Down
14 changes: 14 additions & 0 deletions djbrut/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
checker.settings = settings


__all__ = ['Attempt', 'clear']


class Attempt(object):
"""Check request by all possible checkers for specified rule type
"""
Expand Down Expand Up @@ -41,3 +44,14 @@ def check(self, incr=True):
if incr:
self.incr()
return True


def clear(rule='*', value='*'):
"""Drop all counters from Redis
"""
for checker in settings.BRUTEFORCE_CHECKERS:
checker.clear(
connection=Attempt.connection,
rule=rule,
value=value
)
1 change: 1 addition & 0 deletions example/project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'project',
]

MIDDLEWARE = [
Expand Down
46 changes: 46 additions & 0 deletions example/project/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# coding=utf-8
from django.test import TestCase, Client, RequestFactory
from djbrut import Attempt, clear


class DjBrutTest(TestCase):
def setUp(self):
clear()

def test_view(self):
client = Client()

# first 5 requests is ok
for _i in range(5):
response = client.get('/')
self.assertEqual(response.content, b'ok')

# 6st requests is failed
response = client.get('/')
self.assertEqual(response.content, b'Too many requests (ip). Maximum 5 per 1 minutes.')

def test_timelimit(self):
factory = RequestFactory()
request = factory.get('/')
attempt = Attempt('index', request)
attempt.check()
for key in attempt.connection.keys('*:ip:*:int'):
self.assertLessEqual(attempt.connection.ttl(key), 60)

def test_check(self):
factory = RequestFactory()
request = factory.get('/')
for _i in range(5):
attempt = Attempt('index', request)
self.assertTrue(attempt.check())
attempt = Attempt('index', request)
self.assertFalse(attempt.check())

def test_default_rule(self):
factory = RequestFactory()
request = factory.get('/')
for _i in range(10):
attempt = Attempt('lorem ipsum', request)
self.assertTrue(attempt.check())
attempt = Attempt('lorem ipsum', request)
self.assertFalse(attempt.check())
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='djbrut',
version='0.8.1',
version='1.0.0',

author='orsinium',
author_email='[email protected]',
Expand All @@ -22,7 +22,7 @@

license='GNU Lesser General Public License v3.0',
classifiers=[
'Development Status :: 4 - Beta',
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
'Framework :: Django',
'Intended Audience :: Developers',
Expand Down
30 changes: 30 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[tox]
envlist =
py{2,3}-django{18,111},
py3-django{20,master},

[testenv]
commands =
python manage.py test
changedir = example
envdir = {toxworkdir}/venvs/{envname}
setenv =
PYTHONDONTWRITEBYTECODE=1
PYTHONWARNINGS=once
deps =
django18: Django>=1.8,<1.9
django111: Django>=1.11,<2.0
django20: Django>=2.0,<2.1
djangomaster: https://github.com/django/django/archive/master.tar.gz
redis

[testenv:flake8]
max-line-length = 120
skip_install = True
deps =
flake8
commands =
flake8 {toxinidir}/djbrut

[flake8]
max-line-length = 120

0 comments on commit c06c718

Please sign in to comment.