Files
workdock-platform/backend/workflows/tests/test_security_hardening.py
Md Bayazid Bostame 5fab01d57a
Some checks failed
CI / python-validation (push) Has been cancelled
CI / docker-release-gate (push) Has been cancelled
i18n / compile-translations (push) Has been cancelled
fix: refresh auth freshness during active sessions
2026-04-01 17:50:53 +02:00

116 lines
4.9 KiB
Python

from django.contrib.auth import get_user_model
from django.test import Client, TestCase, override_settings
from workflows.checks import security_settings_check
@override_settings(DEBUG=True)
class RateLimitMiddlewareTests(TestCase):
@override_settings(RATE_LIMIT_LOGIN_LIMIT=2, RATE_LIMIT_LOGIN_WINDOW=60)
def test_login_is_rate_limited(self):
client = Client(REMOTE_ADDR='10.10.10.10')
for _ in range(2):
response = client.post('/accounts/login/', {'username': 'x', 'password': 'y'}, HTTP_HOST='localhost')
self.assertNotEqual(response.status_code, 429)
response = client.post('/accounts/login/', {'username': 'x', 'password': 'y'}, HTTP_HOST='localhost')
self.assertEqual(response.status_code, 429)
self.assertIn('Retry-After', response)
@override_settings(RATE_LIMIT_PASSWORD_RESET_LIMIT=1, RATE_LIMIT_PASSWORD_RESET_WINDOW=60)
def test_password_reset_is_rate_limited(self):
client = Client(REMOTE_ADDR='10.10.10.20')
response = client.post('/accounts/password_reset/', {'email': 'nobody@example.com'}, HTTP_HOST='localhost')
self.assertNotEqual(response.status_code, 429)
response = client.post('/accounts/password_reset/', {'email': 'nobody@example.com'}, HTTP_HOST='localhost')
self.assertEqual(response.status_code, 429)
@override_settings(RATE_LIMIT_ADMIN_ACTION_LIMIT=1, RATE_LIMIT_ADMIN_ACTION_WINDOW=60)
def test_sensitive_admin_posts_are_rate_limited(self):
client = Client(REMOTE_ADDR='10.10.10.30')
response = client.post('/admin-tools/branding/save/', {'portal_title': 'A'}, HTTP_HOST='localhost')
self.assertNotEqual(response.status_code, 429)
response = client.post('/admin-tools/branding/save/', {'portal_title': 'B'}, HTTP_HOST='localhost')
self.assertEqual(response.status_code, 429)
@override_settings(RATE_LIMIT_LOGIN_LIMIT=1, RATE_LIMIT_LOGIN_WINDOW=60)
def test_get_requests_are_not_rate_limited(self):
client = Client(REMOTE_ADDR='10.10.10.40')
first = client.get('/accounts/login/', HTTP_HOST='localhost')
second = client.get('/accounts/login/', HTTP_HOST='localhost')
self.assertEqual(first.status_code, 200)
self.assertEqual(second.status_code, 200)
class SecurityChecksTests(TestCase):
@override_settings(
DEBUG=False,
SECRET_KEY='unsafe-dev-key',
ALLOWED_HOSTS=[],
SESSION_COOKIE_SECURE=False,
CSRF_COOKIE_SECURE=False,
SECURE_SSL_REDIRECT=False,
RUN_SECURITY_CHECKS_DURING_TESTS=True,
)
def test_security_checks_report_production_issues(self):
issues = security_settings_check(None)
ids = {issue.id for issue in issues}
self.assertIn('workdock.E001', ids)
self.assertIn('workdock.E002', ids)
self.assertIn('workdock.E003', ids)
self.assertIn('workdock.E004', ids)
self.assertIn('workdock.W001', ids)
@override_settings(DEBUG=True)
class AuthSessionHardeningTests(TestCase):
def setUp(self):
self.user = get_user_model().objects.create_user(username='security-user', password='secret-12345')
@override_settings(SESSION_IDLE_TIMEOUT_SECONDS=60)
def test_idle_session_forces_relogin(self):
client = Client(REMOTE_ADDR='10.10.10.50')
client.force_login(self.user)
session = client.session
session['last_activity_ts'] = 1
session['auth_fresh_ts'] = 1
session.save()
response = client.get('/', HTTP_HOST='localhost')
self.assertEqual(response.status_code, 302)
self.assertIn('/accounts/login/', response['Location'])
@override_settings(SENSITIVE_ACTION_REAUTH_SECONDS=60)
def test_stale_sensitive_post_forces_relogin(self):
client = Client(REMOTE_ADDR='10.10.10.60')
client.force_login(self.user)
session = client.session
session['last_activity_ts'] = 9999999999
session['auth_fresh_ts'] = 1
session.save()
response = client.post('/admin-tools/branding/save/', {'portal_title': 'Blocked'}, HTTP_HOST='localhost')
self.assertEqual(response.status_code, 302)
self.assertIn('/accounts/login/', response['Location'])
@override_settings(SENSITIVE_ACTION_REAUTH_SECONDS=60)
def test_recent_get_refreshes_fresh_auth_for_sensitive_post(self):
client = Client(REMOTE_ADDR='10.10.10.61')
client.force_login(self.user)
session = client.session
session['last_activity_ts'] = 9999999999
session['auth_fresh_ts'] = 1
session.save()
home_response = client.get('/', HTTP_HOST='localhost')
self.assertEqual(home_response.status_code, 200)
session = client.session
self.assertGreater(session['auth_fresh_ts'], 1)
response = client.post('/admin-tools/branding/save/', {'portal_title': 'Blocked'}, HTTP_HOST='localhost')
self.assertEqual(response.status_code, 302)
self.assertEqual(response['Location'], '/')