Coverage for cc_modules/cc_response.py: 58%

12 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-15 14:23 +0100

1""" 

2camcops_server/cc_modules/cc_response.py 

3 

4=============================================================================== 

5 

6 Copyright (C) 2012, University of Cambridge, Department of Psychiatry. 

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

8 

9 This file is part of CamCOPS. 

10 

11 CamCOPS is free software: you can redistribute it and/or modify 

12 it under the terms of the GNU General Public License as published by 

13 the Free Software Foundation, either version 3 of the License, or 

14 (at your option) any later version. 

15 

16 CamCOPS is distributed in the hope that it will be useful, 

17 but WITHOUT ANY WARRANTY; without even the implied warranty of 

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

19 GNU General Public License for more details. 

20 

21 You should have received a copy of the GNU General Public License 

22 along with CamCOPS. If not, see <https://www.gnu.org/licenses/>. 

23 

24=============================================================================== 

25 

26**Implements a Pyramid Response object customized for CamCOPS.** 

27 

28""" 

29 

30from typing import Any, TYPE_CHECKING 

31 

32from pyramid.response import Response 

33 

34from camcops_server.cc_modules.cc_baseconstants import ( 

35 DEFORM_SUPPORTS_CSP_NONCE, 

36) 

37 

38if TYPE_CHECKING: 

39 from camcops_server.cc_modules.cc_request import CamcopsRequest 

40 

41 

42class CamcopsResponse(Response): 

43 """ 

44 Response class, inheriting from Pyramid's response. 

45 

46 We do this mainly to set the HTTP ``Content-Security-Policy`` header to 

47 match the nonce set by the 

48 :class:``camcops_server.cc_modules.cc_request.CamcopsRequest``. 

49 

50 However, once this class exists, it may as well set all the standard 

51 headers, rather than using additional middleware. 

52 """ 

53 

54 def __init__( 

55 self, camcops_request: "CamcopsRequest", **kwargs: Any 

56 ) -> 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 ( 

115 # The secure policy: 

116 "default-src 'self' data:; " 

117 "object-src 'none'; " 

118 "child-src 'self'; " 

119 f"style-src 'nonce-{nonce}' 'self'; " 

120 # ... meaning: allow inline CSS only if it is 

121 # tagged with this nonce, via <style nonce="XXX">, 

122 # or if it comes from our site ('self'). See 

123 # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src # noqa E501 

124 # And similarly for scripts: 

125 f"script-src 'nonce-{nonce}' 'self'; " 

126 # ... "unsafe-eval" is currently required by 

127 # deform.js, in addSequenceItem(). Deform stores 

128 # prototype code and then clones it when you add a 

129 # sequence item; this involves evaluation. 

130 "frame-ancestors 'none'; " 

131 "upgrade-insecure-requests; " 

132 "block-all-mixed-content" 

133 ) 

134 if DEFORM_SUPPORTS_CSP_NONCE 

135 else ( 

136 # The less secure policy, for Deform: 

137 "default-src 'self' data:; " 

138 "object-src 'none'; " 

139 "child-src 'self'; " 

140 "style-src 'self' 'unsafe-inline'; " 

141 "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " 

142 "frame-ancestors 'none'; " 

143 "upgrade-insecure-requests; " 

144 "block-all-mixed-content" 

145 ) 

146 ), 

147 ), 

148 # ------------------------------------------------------------- 

149 # Strict-Transport-Security: Enforce HTTPS through the client. 

150 # ------------------------------------------------------------- 

151 # - In part this is by e.g. telling Google (and thus Chrome) 

152 # that your site always uses HTTPS, to prevent HTTP-based 

153 # spoofing. 

154 # - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security # noqa 

155 # - Advice is at 

156 # https://blog.qualys.com/vulnerabilities-research/2016/03/28/the-importance-of-a-proper-http-strict-transport-security-implementation-on-your-web-server # noqa 

157 ("Strict-Transport-Security", "max-age=31536000"), # = 1 year 

158 # ------------------------------------------------------------- 

159 # X-Content-Type-Options: Opt out of MIME type sniffing 

160 # ------------------------------------------------------------- 

161 # - Recommended by ZAP penetration testing. 

162 # ... otherwise, the error is "X-Content-Type-Options Header 

163 # Missing" 

164 # - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options # noqa 

165 ("X-Content-Type-Options", "nosniff"), 

166 # ------------------------------------------------------------- 

167 # X-Frame-Options: Prevent rendering within a frame 

168 # ------------------------------------------------------------- 

169 # - Recommended by ZAP penetration testing. 

170 # ... otherwise, the error is "X-Frame-Options Header Not 

171 # Set" 

172 # - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options # noqa 

173 ("X-Frame-Options", "DENY"), 

174 # ------------------------------------------------------------- 

175 # X-XSS-Protection: Check for cross-site scripting attacks 

176 # ------------------------------------------------------------- 

177 # - Recommended by Falanx penetration testing. 

178 # - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection # noqa 

179 ("X-XSS-Protection", "1"), 

180 ] 

181 ) 

182 

183 

184def camcops_response_factory(request: "CamcopsRequest") -> Response: 

185 """ 

186 Factory function to make a response object. 

187 """ 

188 return CamcopsResponse(camcops_request=request)