tests.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import random
  2. import string
  3. import time
  4. from django.test import TestCase
  5. from django.contrib.auth.models import User
  6. from django.core.urlresolvers import NoReverseMatch
  7. from django.core.urlresolvers import reverse
  8. from axes.decorators import COOLOFF_TIME
  9. from axes.decorators import FAILURE_LIMIT
  10. from axes.models import AccessLog
  11. from axes.signals import user_locked_out
  12. from axes.utils import reset
  13. class AccessAttemptTest(TestCase):
  14. """Test case using custom settings for testing
  15. """
  16. VALID_PASSWORD = 'valid-password'
  17. LOCKED_MESSAGE = 'Account locked: too many login attempts.'
  18. LOGIN_FORM_KEY = '<input type="submit" value="Log in" />'
  19. def _login(self, is_valid=False, user_agent='test-browser'):
  20. """Login a user. A valid credential is used when is_valid is True,
  21. otherwise it will use a random string to make a failed login.
  22. """
  23. try:
  24. admin_login = reverse('admin:login')
  25. except NoReverseMatch:
  26. admin_login = reverse('admin:index')
  27. if is_valid:
  28. # Use a valid username
  29. username = self.user.username
  30. else:
  31. # Generate a wrong random username
  32. chars = string.ascii_uppercase + string.digits
  33. username = ''.join(random.choice(chars) for x in range(10))
  34. response = self.client.post(admin_login, {
  35. 'username': username,
  36. 'password': self.VALID_PASSWORD,
  37. 'this_is_the_login_form': 1,
  38. }, HTTP_USER_AGENT=user_agent)
  39. return response
  40. def setUp(self):
  41. """Create a valid user for login
  42. """
  43. self.user = User.objects.create_superuser(
  44. username='valid-username',
  45. email='test@example.com',
  46. password=self.VALID_PASSWORD,
  47. )
  48. def test_failure_limit_once(self):
  49. """Tests the login lock trying to login one more time
  50. than failure limit
  51. """
  52. for i in range(1, FAILURE_LIMIT): # test until one try before the limit
  53. response = self._login()
  54. # Check if we are in the same login page
  55. self.assertContains(response, self.LOGIN_FORM_KEY)
  56. # So, we shouldn't have gotten a lock-out yet.
  57. # But we should get one now
  58. response = self._login()
  59. self.assertContains(response, self.LOCKED_MESSAGE)
  60. def test_failure_limit_many(self):
  61. """Tests the login lock trying to login a lot of times more
  62. than failure limit
  63. """
  64. for i in range(1, FAILURE_LIMIT):
  65. response = self._login()
  66. # Check if we are in the same login page
  67. self.assertContains(response, self.LOGIN_FORM_KEY)
  68. # So, we shouldn't have gotten a lock-out yet.
  69. # We should get a locked message each time we try again
  70. for i in range(0, random.randrange(1, FAILURE_LIMIT)):
  71. response = self._login()
  72. self.assertContains(response, self.LOCKED_MESSAGE)
  73. def test_valid_login(self):
  74. """Tests a valid login for a real username
  75. """
  76. response = self._login(is_valid=True)
  77. self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302)
  78. def test_valid_logout(self):
  79. """Tests a valid logout and make sure the logout_time is updated
  80. """
  81. response = self._login(is_valid=True)
  82. self.assertEquals(AccessLog.objects.latest('id').logout_time, None)
  83. response = self.client.get(reverse('admin:logout'))
  84. self.assertNotEquals(AccessLog.objects.latest('id').logout_time, None)
  85. self.assertContains(response, 'Logged out')
  86. def test_cooling_off(self):
  87. """Tests if the cooling time allows a user to login
  88. """
  89. self.test_failure_limit_once()
  90. # Wait for the cooling off period
  91. time.sleep(COOLOFF_TIME.total_seconds())
  92. # It should be possible to login again, make sure it is.
  93. self.test_valid_login()
  94. def test_cooling_off_for_trusted_user(self):
  95. """Test the cooling time for a trusted user
  96. """
  97. # Test successful login-logout, this makes the user trusted.
  98. self.test_valid_logout()
  99. # Try the cooling off time
  100. self.test_cooling_off()
  101. def test_long_user_agent_valid(self):
  102. """Tests if can handle a long user agent
  103. """
  104. long_user_agent = 'ie6' * 1024
  105. response = self._login(is_valid=True, user_agent=long_user_agent)
  106. self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302)
  107. def test_long_user_agent_not_valid(self):
  108. """Tests if can handle a long user agent with failure
  109. """
  110. long_user_agent = 'ie6' * 1024
  111. for i in range(0, FAILURE_LIMIT + 1):
  112. response = self._login(user_agent=long_user_agent)
  113. self.assertContains(response, self.LOCKED_MESSAGE)
  114. def test_reset_ip(self):
  115. """Tests if can reset an ip address
  116. """
  117. # Make a lockout
  118. self.test_failure_limit_once()
  119. # Reset the ip so we can try again
  120. reset(ip='127.0.0.1')
  121. # Make a login attempt again
  122. self.test_valid_login()
  123. def test_reset_all(self):
  124. """Tests if can reset all attempts
  125. """
  126. # Make a lockout
  127. self.test_failure_limit_once()
  128. # Reset all attempts so we can try again
  129. reset()
  130. # Make a login attempt again
  131. self.test_valid_login()
  132. def test_send_lockout_signal(self):
  133. """Test if the lockout signal is emitted
  134. """
  135. class Scope(object): pass # this "hack" is needed so we don't have to use global variables or python3 features
  136. scope = Scope()
  137. scope.signal_received = 0
  138. def signal_handler(request, username, ip_address, *args, **kwargs):
  139. scope.signal_received += 1
  140. self.assertIsNotNone(request)
  141. # Connect signal handler
  142. user_locked_out.connect(signal_handler)
  143. # Make a lockout
  144. self.test_failure_limit_once()
  145. self.assertEquals(scope.signal_received, 1)
  146. reset()
  147. # Make another lockout
  148. self.test_failure_limit_once()
  149. self.assertEquals(scope.signal_received, 2)