1. from django.http import HttpResponse
    
  2. from django.test import RequestFactory, SimpleTestCase
    
  3. from django.test.utils import override_settings
    
  4. 
    
  5. 
    
  6. class SecurityMiddlewareTest(SimpleTestCase):
    
  7.     def middleware(self, *args, **kwargs):
    
  8.         from django.middleware.security import SecurityMiddleware
    
  9. 
    
  10.         return SecurityMiddleware(self.response(*args, **kwargs))
    
  11. 
    
  12.     @property
    
  13.     def secure_request_kwargs(self):
    
  14.         return {"wsgi.url_scheme": "https"}
    
  15. 
    
  16.     def response(self, *args, headers=None, **kwargs):
    
  17.         def get_response(req):
    
  18.             response = HttpResponse(*args, **kwargs)
    
  19.             if headers:
    
  20.                 for k, v in headers.items():
    
  21.                     response.headers[k] = v
    
  22.             return response
    
  23. 
    
  24.         return get_response
    
  25. 
    
  26.     def process_response(self, *args, secure=False, request=None, **kwargs):
    
  27.         request_kwargs = {}
    
  28.         if secure:
    
  29.             request_kwargs.update(self.secure_request_kwargs)
    
  30.         if request is None:
    
  31.             request = self.request.get("/some/url", **request_kwargs)
    
  32.         ret = self.middleware(*args, **kwargs).process_request(request)
    
  33.         if ret:
    
  34.             return ret
    
  35.         return self.middleware(*args, **kwargs)(request)
    
  36. 
    
  37.     request = RequestFactory()
    
  38. 
    
  39.     def process_request(self, method, *args, secure=False, **kwargs):
    
  40.         if secure:
    
  41.             kwargs.update(self.secure_request_kwargs)
    
  42.         req = getattr(self.request, method.lower())(*args, **kwargs)
    
  43.         return self.middleware().process_request(req)
    
  44. 
    
  45.     @override_settings(SECURE_HSTS_SECONDS=3600)
    
  46.     def test_sts_on(self):
    
  47.         """
    
  48.         With SECURE_HSTS_SECONDS=3600, the middleware adds
    
  49.         "Strict-Transport-Security: max-age=3600" to the response.
    
  50.         """
    
  51.         self.assertEqual(
    
  52.             self.process_response(secure=True).headers["Strict-Transport-Security"],
    
  53.             "max-age=3600",
    
  54.         )
    
  55. 
    
  56.     @override_settings(SECURE_HSTS_SECONDS=3600)
    
  57.     def test_sts_already_present(self):
    
  58.         """
    
  59.         The middleware will not override a "Strict-Transport-Security" header
    
  60.         already present in the response.
    
  61.         """
    
  62.         response = self.process_response(
    
  63.             secure=True, headers={"Strict-Transport-Security": "max-age=7200"}
    
  64.         )
    
  65.         self.assertEqual(response.headers["Strict-Transport-Security"], "max-age=7200")
    
  66. 
    
  67.     @override_settings(SECURE_HSTS_SECONDS=3600)
    
  68.     def test_sts_only_if_secure(self):
    
  69.         """
    
  70.         The "Strict-Transport-Security" header is not added to responses going
    
  71.         over an insecure connection.
    
  72.         """
    
  73.         self.assertNotIn(
    
  74.             "Strict-Transport-Security",
    
  75.             self.process_response(secure=False).headers,
    
  76.         )
    
  77. 
    
  78.     @override_settings(SECURE_HSTS_SECONDS=0)
    
  79.     def test_sts_off(self):
    
  80.         """
    
  81.         With SECURE_HSTS_SECONDS=0, the middleware does not add a
    
  82.         "Strict-Transport-Security" header to the response.
    
  83.         """
    
  84.         self.assertNotIn(
    
  85.             "Strict-Transport-Security",
    
  86.             self.process_response(secure=True).headers,
    
  87.         )
    
  88. 
    
  89.     @override_settings(SECURE_HSTS_SECONDS=600, SECURE_HSTS_INCLUDE_SUBDOMAINS=True)
    
  90.     def test_sts_include_subdomains(self):
    
  91.         """
    
  92.         With SECURE_HSTS_SECONDS non-zero and SECURE_HSTS_INCLUDE_SUBDOMAINS
    
  93.         True, the middleware adds a "Strict-Transport-Security" header with the
    
  94.         "includeSubDomains" directive to the response.
    
  95.         """
    
  96.         response = self.process_response(secure=True)
    
  97.         self.assertEqual(
    
  98.             response.headers["Strict-Transport-Security"],
    
  99.             "max-age=600; includeSubDomains",
    
  100.         )
    
  101. 
    
  102.     @override_settings(SECURE_HSTS_SECONDS=600, SECURE_HSTS_INCLUDE_SUBDOMAINS=False)
    
  103.     def test_sts_no_include_subdomains(self):
    
  104.         """
    
  105.         With SECURE_HSTS_SECONDS non-zero and SECURE_HSTS_INCLUDE_SUBDOMAINS
    
  106.         False, the middleware adds a "Strict-Transport-Security" header without
    
  107.         the "includeSubDomains" directive to the response.
    
  108.         """
    
  109.         response = self.process_response(secure=True)
    
  110.         self.assertEqual(response.headers["Strict-Transport-Security"], "max-age=600")
    
  111. 
    
  112.     @override_settings(SECURE_HSTS_SECONDS=10886400, SECURE_HSTS_PRELOAD=True)
    
  113.     def test_sts_preload(self):
    
  114.         """
    
  115.         With SECURE_HSTS_SECONDS non-zero and SECURE_HSTS_PRELOAD True, the
    
  116.         middleware adds a "Strict-Transport-Security" header with the "preload"
    
  117.         directive to the response.
    
  118.         """
    
  119.         response = self.process_response(secure=True)
    
  120.         self.assertEqual(
    
  121.             response.headers["Strict-Transport-Security"],
    
  122.             "max-age=10886400; preload",
    
  123.         )
    
  124. 
    
  125.     @override_settings(
    
  126.         SECURE_HSTS_SECONDS=10886400,
    
  127.         SECURE_HSTS_INCLUDE_SUBDOMAINS=True,
    
  128.         SECURE_HSTS_PRELOAD=True,
    
  129.     )
    
  130.     def test_sts_subdomains_and_preload(self):
    
  131.         """
    
  132.         With SECURE_HSTS_SECONDS non-zero, SECURE_HSTS_INCLUDE_SUBDOMAINS and
    
  133.         SECURE_HSTS_PRELOAD True, the middleware adds a "Strict-Transport-Security"
    
  134.         header containing both the "includeSubDomains" and "preload" directives
    
  135.         to the response.
    
  136.         """
    
  137.         response = self.process_response(secure=True)
    
  138.         self.assertEqual(
    
  139.             response.headers["Strict-Transport-Security"],
    
  140.             "max-age=10886400; includeSubDomains; preload",
    
  141.         )
    
  142. 
    
  143.     @override_settings(SECURE_HSTS_SECONDS=10886400, SECURE_HSTS_PRELOAD=False)
    
  144.     def test_sts_no_preload(self):
    
  145.         """
    
  146.         With SECURE_HSTS_SECONDS non-zero and SECURE_HSTS_PRELOAD
    
  147.         False, the middleware adds a "Strict-Transport-Security" header without
    
  148.         the "preload" directive to the response.
    
  149.         """
    
  150.         response = self.process_response(secure=True)
    
  151.         self.assertEqual(
    
  152.             response.headers["Strict-Transport-Security"],
    
  153.             "max-age=10886400",
    
  154.         )
    
  155. 
    
  156.     @override_settings(SECURE_CONTENT_TYPE_NOSNIFF=True)
    
  157.     def test_content_type_on(self):
    
  158.         """
    
  159.         With SECURE_CONTENT_TYPE_NOSNIFF set to True, the middleware adds
    
  160.         "X-Content-Type-Options: nosniff" header to the response.
    
  161.         """
    
  162.         self.assertEqual(
    
  163.             self.process_response().headers["X-Content-Type-Options"],
    
  164.             "nosniff",
    
  165.         )
    
  166. 
    
  167.     @override_settings(SECURE_CONTENT_TYPE_NOSNIFF=True)
    
  168.     def test_content_type_already_present(self):
    
  169.         """
    
  170.         The middleware will not override an "X-Content-Type-Options" header
    
  171.         already present in the response.
    
  172.         """
    
  173.         response = self.process_response(
    
  174.             secure=True, headers={"X-Content-Type-Options": "foo"}
    
  175.         )
    
  176.         self.assertEqual(response.headers["X-Content-Type-Options"], "foo")
    
  177. 
    
  178.     @override_settings(SECURE_CONTENT_TYPE_NOSNIFF=False)
    
  179.     def test_content_type_off(self):
    
  180.         """
    
  181.         With SECURE_CONTENT_TYPE_NOSNIFF False, the middleware does not add an
    
  182.         "X-Content-Type-Options" header to the response.
    
  183.         """
    
  184.         self.assertNotIn("X-Content-Type-Options", self.process_response().headers)
    
  185. 
    
  186.     @override_settings(SECURE_SSL_REDIRECT=True)
    
  187.     def test_ssl_redirect_on(self):
    
  188.         """
    
  189.         With SECURE_SSL_REDIRECT True, the middleware redirects any non-secure
    
  190.         requests to the https:// version of the same URL.
    
  191.         """
    
  192.         ret = self.process_request("get", "/some/url?query=string")
    
  193.         self.assertEqual(ret.status_code, 301)
    
  194.         self.assertEqual(ret["Location"], "https://testserver/some/url?query=string")
    
  195. 
    
  196.     @override_settings(SECURE_SSL_REDIRECT=True)
    
  197.     def test_no_redirect_ssl(self):
    
  198.         """
    
  199.         The middleware does not redirect secure requests.
    
  200.         """
    
  201.         ret = self.process_request("get", "/some/url", secure=True)
    
  202.         self.assertIsNone(ret)
    
  203. 
    
  204.     @override_settings(SECURE_SSL_REDIRECT=True, SECURE_REDIRECT_EXEMPT=["^insecure/"])
    
  205.     def test_redirect_exempt(self):
    
  206.         """
    
  207.         The middleware does not redirect requests with URL path matching an
    
  208.         exempt pattern.
    
  209.         """
    
  210.         ret = self.process_request("get", "/insecure/page")
    
  211.         self.assertIsNone(ret)
    
  212. 
    
  213.     @override_settings(SECURE_SSL_REDIRECT=True, SECURE_SSL_HOST="secure.example.com")
    
  214.     def test_redirect_ssl_host(self):
    
  215.         """
    
  216.         The middleware redirects to SECURE_SSL_HOST if given.
    
  217.         """
    
  218.         ret = self.process_request("get", "/some/url")
    
  219.         self.assertEqual(ret.status_code, 301)
    
  220.         self.assertEqual(ret["Location"], "https://secure.example.com/some/url")
    
  221. 
    
  222.     @override_settings(SECURE_SSL_REDIRECT=False)
    
  223.     def test_ssl_redirect_off(self):
    
  224.         """
    
  225.         With SECURE_SSL_REDIRECT False, the middleware does not redirect.
    
  226.         """
    
  227.         ret = self.process_request("get", "/some/url")
    
  228.         self.assertIsNone(ret)
    
  229. 
    
  230.     @override_settings(SECURE_REFERRER_POLICY=None)
    
  231.     def test_referrer_policy_off(self):
    
  232.         """
    
  233.         With SECURE_REFERRER_POLICY set to None, the middleware does not add a
    
  234.         "Referrer-Policy" header to the response.
    
  235.         """
    
  236.         self.assertNotIn("Referrer-Policy", self.process_response().headers)
    
  237. 
    
  238.     def test_referrer_policy_on(self):
    
  239.         """
    
  240.         With SECURE_REFERRER_POLICY set to a valid value, the middleware adds a
    
  241.         "Referrer-Policy" header to the response.
    
  242.         """
    
  243.         tests = (
    
  244.             ("strict-origin", "strict-origin"),
    
  245.             ("strict-origin,origin", "strict-origin,origin"),
    
  246.             ("strict-origin, origin", "strict-origin,origin"),
    
  247.             (["strict-origin", "origin"], "strict-origin,origin"),
    
  248.             (("strict-origin", "origin"), "strict-origin,origin"),
    
  249.         )
    
  250.         for value, expected in tests:
    
  251.             with self.subTest(value=value), override_settings(
    
  252.                 SECURE_REFERRER_POLICY=value
    
  253.             ):
    
  254.                 self.assertEqual(
    
  255.                     self.process_response().headers["Referrer-Policy"],
    
  256.                     expected,
    
  257.                 )
    
  258. 
    
  259.     @override_settings(SECURE_REFERRER_POLICY="strict-origin")
    
  260.     def test_referrer_policy_already_present(self):
    
  261.         """
    
  262.         The middleware will not override a "Referrer-Policy" header already
    
  263.         present in the response.
    
  264.         """
    
  265.         response = self.process_response(headers={"Referrer-Policy": "unsafe-url"})
    
  266.         self.assertEqual(response.headers["Referrer-Policy"], "unsafe-url")
    
  267. 
    
  268.     @override_settings(SECURE_CROSS_ORIGIN_OPENER_POLICY=None)
    
  269.     def test_coop_off(self):
    
  270.         """
    
  271.         With SECURE_CROSS_ORIGIN_OPENER_POLICY set to None, the middleware does
    
  272.         not add a "Cross-Origin-Opener-Policy" header to the response.
    
  273.         """
    
  274.         self.assertNotIn("Cross-Origin-Opener-Policy", self.process_response())
    
  275. 
    
  276.     def test_coop_default(self):
    
  277.         """SECURE_CROSS_ORIGIN_OPENER_POLICY defaults to same-origin."""
    
  278.         self.assertEqual(
    
  279.             self.process_response().headers["Cross-Origin-Opener-Policy"],
    
  280.             "same-origin",
    
  281.         )
    
  282. 
    
  283.     def test_coop_on(self):
    
  284.         """
    
  285.         With SECURE_CROSS_ORIGIN_OPENER_POLICY set to a valid value, the
    
  286.         middleware adds a "Cross-Origin_Opener-Policy" header to the response.
    
  287.         """
    
  288.         tests = ["same-origin", "same-origin-allow-popups", "unsafe-none"]
    
  289.         for value in tests:
    
  290.             with self.subTest(value=value), override_settings(
    
  291.                 SECURE_CROSS_ORIGIN_OPENER_POLICY=value,
    
  292.             ):
    
  293.                 self.assertEqual(
    
  294.                     self.process_response().headers["Cross-Origin-Opener-Policy"],
    
  295.                     value,
    
  296.                 )
    
  297. 
    
  298.     @override_settings(SECURE_CROSS_ORIGIN_OPENER_POLICY="unsafe-none")
    
  299.     def test_coop_already_present(self):
    
  300.         """
    
  301.         The middleware doesn't override a "Cross-Origin-Opener-Policy" header
    
  302.         already present in the response.
    
  303.         """
    
  304.         response = self.process_response(
    
  305.             headers={"Cross-Origin-Opener-Policy": "same-origin"}
    
  306.         )
    
  307.         self.assertEqual(response.headers["Cross-Origin-Opener-Policy"], "same-origin")