3d8f88b61e
ContentSecurityPolicyMiddleware ставит заголовок CSP при CSP_ENABLED=True. По умолчанию выключен; CSP_REPORT_ONLY=True — только отчёты, ничего не блокирует (для сбора нарушений перед боевым режимом). Политика (дефолт под текущий стек): default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self'; script/style-src с 'unsafe-inline' (пока есть инлайн onclick/<script> — Фаза 2: вынести и перейти на nonce) + CDN (jsdelivr/unpkg/cdnjs) + тайлы карт. CSP_EXTRA_HOSTS — внешние хосты (напр. домен OnlyOffice Document Server). htmx CSP-чистый (allowEval=false, нет hx-on) — 'unsafe-eval' не нужен. Тесты: выкл по умолчанию, Report-Only/enforce заголовки, extra-hosts. 795 ок. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
55 lines
2.2 KiB
Python
55 lines
2.2 KiB
Python
from datetime import date
|
|
|
|
from django.test import TestCase, override_settings
|
|
|
|
from apps.employees.models import Employee
|
|
from core.test_utils import login_2fa
|
|
|
|
|
|
def _user():
|
|
from django.contrib.auth.models import Group
|
|
emp = Employee.objects.create(
|
|
username='csp.user', email='csp.user@company.local',
|
|
first_name='CSP', last_name='Тест', hire_date=date(2026, 1, 1),
|
|
user_location_type='office', mattermost_sync_enabled=False, ad_sync_enabled=False,
|
|
)
|
|
emp.set_unusable_password()
|
|
emp.groups.add(Group.objects.get_or_create(name='Сотрудники (тест)')[0])
|
|
emp.set_password('x')
|
|
emp.save()
|
|
return emp
|
|
|
|
|
|
class CspMiddlewareTests(TestCase):
|
|
def setUp(self):
|
|
self.user = _user()
|
|
login_2fa(self.client, self.user)
|
|
|
|
def test_off_by_default(self):
|
|
resp = self.client.get('/dashboard/', HTTP_HX_REQUEST='true')
|
|
self.assertNotIn('Content-Security-Policy', resp)
|
|
self.assertNotIn('Content-Security-Policy-Report-Only', resp)
|
|
|
|
@override_settings(CSP_ENABLED=True, CSP_REPORT_ONLY=True)
|
|
def test_report_only_header(self):
|
|
resp = self.client.get('/dashboard/', HTTP_HX_REQUEST='true')
|
|
self.assertIn('Content-Security-Policy-Report-Only', resp)
|
|
self.assertNotIn('Content-Security-Policy', resp)
|
|
policy = resp['Content-Security-Policy-Report-Only']
|
|
for part in ("default-src 'self'", "object-src 'none'",
|
|
"frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'"):
|
|
self.assertIn(part, policy)
|
|
|
|
@override_settings(CSP_ENABLED=True, CSP_REPORT_ONLY=False)
|
|
def test_enforced_header(self):
|
|
resp = self.client.get('/dashboard/', HTTP_HX_REQUEST='true')
|
|
self.assertIn('Content-Security-Policy', resp)
|
|
self.assertIn("script-src 'self'", resp['Content-Security-Policy'])
|
|
|
|
@override_settings(CSP_ENABLED=True, CSP_REPORT_ONLY=True,
|
|
CSP_EXTRA_HOSTS=['https://docs.example.com'])
|
|
def test_extra_hosts_included(self):
|
|
resp = self.client.get('/dashboard/', HTTP_HX_REQUEST='true')
|
|
policy = resp['Content-Security-Policy-Report-Only']
|
|
self.assertIn('https://docs.example.com', policy) # хост OnlyOffice DS
|