Coverage for cc_modules/cc_response.py: 62%
13 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-08 23:14 +0000
1#!/usr/bin/env python
3"""
4camcops_server/cc_modules/cc_response.py
6===============================================================================
8 Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
9 Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
11 This file is part of CamCOPS.
13 CamCOPS is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
18 CamCOPS is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
26===============================================================================
28**Implements a Pyramid Response object customized for CamCOPS.**
30"""
32from typing import TYPE_CHECKING
34from pyramid.response import Response
36from camcops_server.cc_modules.cc_baseconstants import (
37 DEFORM_SUPPORTS_CSP_NONCE,
38)
40if TYPE_CHECKING:
41 from camcops_server.cc_modules.cc_request import CamcopsRequest
44class CamcopsResponse(Response):
45 """
46 Response class, inheriting from Pyramid's response.
48 We do this mainly to set the HTTP ``Content-Security-Policy`` header to
49 match the nonce set by the
50 :class:``camcops_server.cc_modules.cc_request.CamcopsRequest``.
52 However, once this class exists, it may as well set all the standard
53 headers, rather than using additional middleware.
54 """
56 def __init__(self, camcops_request: "CamcopsRequest", **kwargs) -> None:
57 super().__init__(**kwargs)
58 nonce = camcops_request.nonce
59 self.headers.update(
60 [
61 # List of key, value tuples:
62 # -------------------------------------------------------------
63 # Cache-Control: Caching
64 # -------------------------------------------------------------
65 # NOT THIS:
66 # ("Cache-Control", "no-cache, no-store, must-revalidate"),
67 # or we get a ZAP error "Incomplete or No Cache-control and
68 # Pragma HTTP Header Set".
69 #
70 # Note that Pragma is HTTP/1.0 and cache-control is HTTP/1.1,
71 # so you don't have to do both.
72 #
73 # BUT that prevents caching of images (e.g. logos), and we
74 # don't want that.
75 #
76 # The Pyramid @view_config decorator (or add_view function)
77 # takes an "http_cache" parameter, as per
78 # https://pyramid-pt-br.readthedocs.io/en/latest/api/config.html#pyramid.config.Configurator.add_view # noqa
79 # and it looks like viewderivers.py implements this. The
80 # default does not set the HTTP "cache-control" header,
81 # explained at
82 # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control # noqa
83 # The basic options are:
84 # - 0: do not cache
85 # - integer_seconds, or datetime.timedelta: lifespan
86 # - tuple (lifespan, dictionary_of_extra_cache_control_details)
87 # - tuple (None, dictionary_of_extra_cache_control_details)
88 # We now set http_cache for all our views via view_config, as
89 # well as the equivalent of using cache_max_age for
90 # add_static_view().
91 #
92 # However, this (as an additional Cache-Control header -- as
93 # well as any "cache, it's static" or "don't cache" header)
94 # sorts out any ZAP complaints:
95 ("Cache-Control", 'no-cache="Set-Cookie, Set-Cookie2"'),
96 # -------------------------------------------------------------
97 # Content-Security-Policy: Control resources that are permitted
98 # to load, to mitigate against cross-site scripting attacks
99 # -------------------------------------------------------------
100 # - Content-Security-Policy.
101 # - Recommended by Falanx penetration testing.
102 # - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy # noqa
103 # - Defaults from https://owasp.org/www-project-secure-headers/
104 # - Re scripts: see https://csper.io/blog/no-more-unsafe-inline
105 # - Re nonces (and in general): see
106 # https://stackoverflow.com/questions/42922784/what-s-the-purpose-of-the-html-nonce-attribute-for-script-and-style-elements # noqa
107 # - Note that multiple CSP headers combine to produce the most
108 # restrictive and can get confusing; best to use one. See
109 # https://chrisguitarguy.com/2019/07/05/working-with-multiple-content-security-policy-headers/ # noqa
110 (
111 "Content-Security-Policy",
112 # A single string:
113 (
114 # The secure policy:
115 "default-src 'self' data:; "
116 "object-src 'none'; "
117 "child-src 'self'; "
118 f"style-src 'nonce-{nonce}' 'self'; "
119 # ... meaning: allow inline CSS only if it is tagged
120 # with this nonce, via <style nonce="XXX">, or if it
121 # comes from our site ('self'). See
122 # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src # noqa
123 # And similarly for scripts:
124 f"script-src 'nonce-{nonce}' 'self'; "
125 # ... "unsafe-eval" is currently required by deform.js,
126 # in addSequenceItem(). Deform stores prototype code
127 # and then clones it when you add a sequence item; this
128 # involves evaluation.
129 "frame-ancestors 'none'; "
130 "upgrade-insecure-requests; "
131 "block-all-mixed-content"
132 )
133 if DEFORM_SUPPORTS_CSP_NONCE
134 else (
135 # The less secure policy, for Deform:
136 "default-src 'self' data:; "
137 "object-src 'none'; "
138 "child-src 'self'; "
139 "style-src 'self' 'unsafe-inline'; "
140 "script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
141 "frame-ancestors 'none'; "
142 "upgrade-insecure-requests; "
143 "block-all-mixed-content"
144 ),
145 ),
146 # -------------------------------------------------------------
147 # Strict-Transport-Security: Enforce HTTPS through the client.
148 # -------------------------------------------------------------
149 # - In part this is by e.g. telling Google (and thus Chrome)
150 # that your site always uses HTTPS, to prevent HTTP-based
151 # spoofing.
152 # - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security # noqa
153 # - Advice is at
154 # https://blog.qualys.com/vulnerabilities-research/2016/03/28/the-importance-of-a-proper-http-strict-transport-security-implementation-on-your-web-server # noqa
155 ("Strict-Transport-Security", "max-age=31536000"), # = 1 year
156 # -------------------------------------------------------------
157 # X-Content-Type-Options: Opt out of MIME type sniffing
158 # -------------------------------------------------------------
159 # - Recommended by ZAP penetration testing.
160 # ... otherwise, the error is "X-Content-Type-Options Header
161 # Missing"
162 # - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options # noqa
163 ("X-Content-Type-Options", "nosniff"),
164 # -------------------------------------------------------------
165 # X-Frame-Options: Prevent rendering within a frame
166 # -------------------------------------------------------------
167 # - Recommended by ZAP penetration testing.
168 # ... otherwise, the error is "X-Frame-Options Header Not
169 # Set"
170 # - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options # noqa
171 ("X-Frame-Options", "DENY"),
172 # -------------------------------------------------------------
173 # X-XSS-Protection: Check for cross-site scripting attacks
174 # -------------------------------------------------------------
175 # - Recommended by Falanx penetration testing.
176 # - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection # noqa
177 ("X-XSS-Protection", "1"),
178 ]
179 )
182def camcops_response_factory(request: "CamcopsRequest") -> Response:
183 """
184 Factory function to make a response object.
185 """
186 return CamcopsResponse(camcops_request=request)