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'], '/')