mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-15 20:48:02 +00:00
280 lines
11 KiB
Python
280 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Test authentication models for ivatar
|
|
"""
|
|
|
|
import os
|
|
import django
|
|
from django.test import TestCase
|
|
from django.contrib.auth.models import User
|
|
from django.utils import timezone
|
|
|
|
os.environ["DJANGO_SETTINGS_MODULE"] = "ivatar.settings"
|
|
django.setup()
|
|
|
|
from ivatar.ivataraccount.auth_models import AuthToken, BruteForceAttempt
|
|
from ivatar.utils import random_string
|
|
|
|
|
|
class AuthTokenTestCase(TestCase):
|
|
"""
|
|
Test cases for AuthToken model
|
|
"""
|
|
|
|
def setUp(self):
|
|
"""Set up test data"""
|
|
self.username = random_string()
|
|
self.password = random_string()
|
|
self.email = f"{random_string()}@{random_string()}.com"
|
|
self.user = User.objects.create_user(
|
|
username=self.username, password=self.password, email=self.email
|
|
)
|
|
|
|
def test_auth_token_creation(self):
|
|
"""Test basic AuthToken creation"""
|
|
token = AuthToken.objects.create(
|
|
token="test-token-123", user=self.user, ip_address="127.0.0.1"
|
|
)
|
|
|
|
self.assertEqual(token.user, self.user)
|
|
self.assertEqual(token.token, "test-token-123")
|
|
self.assertEqual(token.ip_address, "127.0.0.1")
|
|
self.assertTrue(token.is_active)
|
|
self.assertIsNotNone(token.created_at)
|
|
self.assertIsNotNone(token.expires_at)
|
|
|
|
def test_auth_token_auto_expiration(self):
|
|
"""Test that expiration is set automatically"""
|
|
token = AuthToken.objects.create(token="test-token-456", user=self.user)
|
|
|
|
# Should be set to 1 hour from now
|
|
expected_expiry = timezone.now() + timezone.timedelta(hours=1)
|
|
time_diff = abs((token.expires_at - expected_expiry).total_seconds())
|
|
self.assertLess(time_diff, 5) # Within 5 seconds
|
|
|
|
def test_auth_token_is_expired(self):
|
|
"""Test token expiration checking"""
|
|
# Create expired token
|
|
past_time = timezone.now() - timezone.timedelta(hours=2)
|
|
token = AuthToken.objects.create(token="expired-token", user=self.user)
|
|
token.expires_at = past_time
|
|
token.save()
|
|
|
|
self.assertTrue(token.is_expired())
|
|
|
|
# Create valid token
|
|
future_time = timezone.now() + timezone.timedelta(hours=1)
|
|
valid_token = AuthToken.objects.create(token="valid-token", user=self.user)
|
|
valid_token.expires_at = future_time
|
|
valid_token.save()
|
|
|
|
self.assertFalse(valid_token.is_expired())
|
|
|
|
def test_auth_token_string_representation(self):
|
|
"""Test string representation of AuthToken"""
|
|
token = AuthToken.objects.create(token="test-token-789", user=self.user)
|
|
|
|
expected_str = f"Token for {self.user.username} (expires: {token.expires_at})"
|
|
self.assertEqual(str(token), expected_str)
|
|
|
|
def test_auth_token_unique_constraint(self):
|
|
"""Test that tokens must be unique"""
|
|
AuthToken.objects.create(token="unique-token", user=self.user)
|
|
|
|
# Try to create another token with the same value
|
|
with self.assertRaises(Exception): # IntegrityError
|
|
AuthToken.objects.create(token="unique-token", user=self.user)
|
|
|
|
|
|
class BruteForceAttemptTestCase(TestCase):
|
|
"""
|
|
Test cases for BruteForceAttempt model
|
|
"""
|
|
|
|
def setUp(self):
|
|
"""Set up test data"""
|
|
self.username = random_string()
|
|
self.password = random_string()
|
|
self.email = f"{random_string()}@{random_string()}.com"
|
|
self.user = User.objects.create_user(
|
|
username=self.username, password=self.password, email=self.email
|
|
)
|
|
self.ip_address = "127.0.0.1"
|
|
self.user_agent = "Mozilla/5.0 Test Browser"
|
|
|
|
def test_brute_force_attempt_creation_authenticated_user(self):
|
|
"""Test BruteForceAttempt creation for authenticated user"""
|
|
attempt = BruteForceAttempt.objects.create(
|
|
user=self.user, ip_address=self.ip_address, user_agent=self.user_agent
|
|
)
|
|
|
|
self.assertEqual(attempt.user, self.user)
|
|
self.assertEqual(attempt.ip_address, self.ip_address)
|
|
self.assertEqual(attempt.user_agent, self.user_agent)
|
|
self.assertEqual(attempt.attempt_count, 1)
|
|
self.assertFalse(attempt.is_blocked)
|
|
self.assertIsNotNone(attempt.first_attempt)
|
|
self.assertIsNotNone(attempt.last_attempt)
|
|
|
|
def test_brute_force_attempt_creation_unauthenticated_user(self):
|
|
"""Test BruteForceAttempt creation for unauthenticated user"""
|
|
username = random_string()
|
|
attempt = BruteForceAttempt.objects.create(
|
|
username=username, ip_address=self.ip_address, user_agent=self.user_agent
|
|
)
|
|
|
|
self.assertIsNone(attempt.user)
|
|
self.assertEqual(attempt.username, username)
|
|
self.assertEqual(attempt.ip_address, self.ip_address)
|
|
self.assertEqual(attempt.user_agent, self.user_agent)
|
|
self.assertEqual(attempt.attempt_count, 1)
|
|
self.assertFalse(attempt.is_blocked)
|
|
|
|
def test_brute_force_attempt_save_updates_last_attempt(self):
|
|
"""Test that save() updates last_attempt timestamp"""
|
|
attempt = BruteForceAttempt.objects.create(
|
|
user=self.user, ip_address=self.ip_address
|
|
)
|
|
|
|
original_last_attempt = attempt.last_attempt
|
|
|
|
# Wait a moment and save again
|
|
import time
|
|
|
|
time.sleep(0.1)
|
|
attempt.save()
|
|
|
|
self.assertGreater(attempt.last_attempt, original_last_attempt)
|
|
|
|
def test_should_block_within_time_window(self):
|
|
"""Test should_block() method within time window"""
|
|
attempt = BruteForceAttempt.objects.create(user=self.user, attempt_count=3)
|
|
|
|
# Should not block with 3 attempts (below threshold of 5)
|
|
self.assertFalse(attempt.should_block(max_attempts=5, time_window_minutes=15))
|
|
|
|
# Should block with 6 attempts (above threshold)
|
|
attempt.attempt_count = 6
|
|
attempt.save()
|
|
self.assertTrue(attempt.should_block(max_attempts=5, time_window_minutes=15))
|
|
|
|
def test_should_block_outside_time_window(self):
|
|
"""Test should_block() resets when outside time window"""
|
|
# Create attempt with old timestamp
|
|
old_time = timezone.now() - timezone.timedelta(minutes=20)
|
|
attempt = BruteForceAttempt.objects.create(
|
|
user=self.user, attempt_count=10 # Above threshold
|
|
)
|
|
attempt.first_attempt = old_time
|
|
attempt.save()
|
|
|
|
# Should reset and not block (outside 15-minute window)
|
|
self.assertFalse(attempt.should_block(max_attempts=5, time_window_minutes=15))
|
|
|
|
# Reset the attempt count using the new method
|
|
attempt.reset_if_expired(time_window_minutes=15)
|
|
|
|
# Should have reset attempt count
|
|
attempt.refresh_from_db()
|
|
self.assertEqual(attempt.attempt_count, 0)
|
|
self.assertFalse(attempt.is_blocked)
|
|
|
|
def test_should_block_when_explicitly_blocked(self):
|
|
"""Test should_block() when is_blocked is True"""
|
|
attempt = BruteForceAttempt.objects.create(
|
|
user=self.user, attempt_count=2, is_blocked=True
|
|
)
|
|
|
|
# Should block regardless of attempt count
|
|
self.assertTrue(attempt.should_block(max_attempts=5, time_window_minutes=15))
|
|
|
|
def test_increment_attempt(self):
|
|
"""Test increment_attempt() method"""
|
|
attempt = BruteForceAttempt.objects.create(user=self.user, attempt_count=3)
|
|
|
|
attempt.increment_attempt()
|
|
|
|
self.assertEqual(attempt.attempt_count, 4)
|
|
self.assertFalse(attempt.is_blocked) # Still below threshold
|
|
|
|
# Increment to threshold
|
|
attempt.increment_attempt()
|
|
|
|
self.assertEqual(attempt.attempt_count, 5)
|
|
self.assertTrue(attempt.is_blocked) # Should be blocked now
|
|
|
|
def test_increment_attempt_blocks_at_threshold(self):
|
|
"""Test that increment_attempt() blocks at threshold"""
|
|
attempt = BruteForceAttempt.objects.create(user=self.user, attempt_count=4)
|
|
|
|
attempt.increment_attempt()
|
|
|
|
self.assertEqual(attempt.attempt_count, 5)
|
|
self.assertTrue(attempt.is_blocked)
|
|
|
|
def test_brute_force_attempt_string_representation(self):
|
|
"""Test string representation of BruteForceAttempt"""
|
|
# Test with user
|
|
attempt_with_user = BruteForceAttempt.objects.create(
|
|
user=self.user, attempt_count=3
|
|
)
|
|
expected_str_user = f"Brute force attempt for {self.user.username} (3 attempts)"
|
|
self.assertEqual(str(attempt_with_user), expected_str_user)
|
|
|
|
# Test with username only
|
|
test_username = random_string()
|
|
attempt_with_username = BruteForceAttempt.objects.create(
|
|
username=test_username, ip_address=self.ip_address, attempt_count=2
|
|
)
|
|
expected_str_username = f"Brute force attempt for {test_username} (2 attempts)"
|
|
self.assertEqual(str(attempt_with_username), expected_str_username)
|
|
|
|
# Test with IP only
|
|
attempt_with_ip = BruteForceAttempt.objects.create(
|
|
ip_address=self.ip_address, attempt_count=1
|
|
)
|
|
expected_str_ip = f"Brute force attempt for {self.ip_address} (1 attempts)"
|
|
self.assertEqual(str(attempt_with_ip), expected_str_ip)
|
|
|
|
def test_unique_together_constraint(self):
|
|
"""Test unique_together constraint"""
|
|
test_username = random_string()
|
|
# Create first attempt
|
|
BruteForceAttempt.objects.create(
|
|
user=self.user, username=test_username, ip_address=self.ip_address
|
|
)
|
|
|
|
# Try to create duplicate (should fail)
|
|
with self.assertRaises(Exception): # IntegrityError
|
|
BruteForceAttempt.objects.create(
|
|
user=self.user, username=test_username, ip_address=self.ip_address
|
|
)
|
|
|
|
def test_get_or_create_behavior(self):
|
|
"""Test get_or_create behavior for brute force attempts"""
|
|
# First call should create
|
|
attempt, created = BruteForceAttempt.objects.get_or_create(
|
|
user=self.user, defaults={"attempt_count": 0}
|
|
)
|
|
self.assertTrue(created)
|
|
self.assertEqual(attempt.attempt_count, 0)
|
|
|
|
# Second call should get existing
|
|
attempt2, created2 = BruteForceAttempt.objects.get_or_create(
|
|
user=self.user, defaults={"attempt_count": 0}
|
|
)
|
|
self.assertFalse(created2)
|
|
self.assertEqual(attempt.id, attempt2.id)
|
|
self.assertEqual(attempt2.attempt_count, 0)
|
|
|
|
def test_custom_max_attempts_and_time_window(self):
|
|
"""Test custom max_attempts and time_window parameters"""
|
|
attempt = BruteForceAttempt.objects.create(user=self.user, attempt_count=2)
|
|
|
|
# Test with custom parameters
|
|
self.assertFalse(attempt.should_block(max_attempts=3, time_window_minutes=30))
|
|
|
|
attempt.attempt_count = 3
|
|
attempt.save()
|
|
self.assertTrue(attempt.should_block(max_attempts=3, time_window_minutes=30))
|