Coverage for webapp/handlers.py: 79%

47 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-27 22:07 +0000

1from flask import render_template, session 

2from webapp.config import SENTRY_DSN 

3 

4from canonicalwebteam.exceptions import ( 

5 StoreApiError, 

6 StoreApiResourceNotFound, 

7 StoreApiResponseDecodeError, 

8 StoreApiResponseError, 

9 StoreApiResponseErrorList, 

10 StoreApiTimeoutError, 

11 StoreApiConnectionError, 

12) 

13 

14from canonicalwebteam import image_template 

15 

16from webapp import authentication, helpers 

17 

18CSP = { 

19 "default-src": ["'self'"], 

20 "img-src": [ 

21 "'self'", 

22 "data: blob:", 

23 # This is needed to allow images from 

24 # https://www.google.*/ads/ga-audiences to load. 

25 "*", 

26 ], 

27 "script-src-elem": [ 

28 "'self'", 

29 "assets.ubuntu.com", 

30 "www.googletagmanager.com", 

31 "*.crazyegg.com", 

32 "w.usabilla.com", 

33 # This is necessary for Google Tag Manager to function properly. 

34 "'unsafe-inline'", 

35 ], 

36 "font-src": [ 

37 "'self'", 

38 "assets.ubuntu.com", 

39 ], 

40 "script-src": [ 

41 "'self'", 

42 "blob:", 

43 "'unsafe-eval'", 

44 "'unsafe-hashes'", 

45 ], 

46 "connect-src": [ 

47 "'self'", 

48 "sentry.is.canonical.com", 

49 "*.crazyegg.com", 

50 "analytics.google.com", 

51 "www.google-analytics.com", 

52 "stats.g.doubleclick.net", 

53 ], 

54 "frame-src": [ 

55 "'self'", 

56 "td.doubleclick.net", 

57 ], 

58 "style-src": [ 

59 "'self'", 

60 "'unsafe-inline'", 

61 ], 

62} 

63 

64 

65def charmhub_utility_processor(): 

66 """ 

67 This defines the set of properties and functions that will be added 

68 to the default context for processing templates. All these items 

69 can be used in all templates 

70 """ 

71 if authentication.is_authenticated(session): 

72 account = session["account"] 

73 else: 

74 account = None 

75 return { 

76 "schedule_banner": helpers.schedule_banner, 

77 "account": account, 

78 "image": image_template, 

79 "SENTRY_DSN": SENTRY_DSN, 

80 } 

81 

82 

83def set_handlers(app): 

84 @app.context_processor 

85 def utility_processor(): 

86 return charmhub_utility_processor() 

87 

88 # Error handlers 

89 # === 

90 @app.errorhandler(StoreApiTimeoutError) 

91 def handle_store_api_timeout(e): 

92 status_code = 504 

93 return ( 

94 render_template( 

95 "500.html", error_message=str(e), status_code=status_code 

96 ), 

97 status_code, 

98 ) 

99 

100 @app.errorhandler(StoreApiResourceNotFound) 

101 def handle_store_api_circuit_breaker_exception(e): 

102 return render_template("404.html", message=str(e)), 404 

103 

104 @app.errorhandler(StoreApiResponseErrorList) 

105 def handle_store_api_error_list(e): 

106 if e.status_code == 404: 

107 return render_template("404.html", message="Entity not found"), 404 

108 

109 status_code = 502 

110 if e.errors: 

111 errors = ", ".join([e.get("message") for e in e.errors]) 

112 return ( 

113 render_template( 

114 "500.html", error_message=errors, status_code=status_code 

115 ), 

116 status_code, 

117 ) 

118 

119 return ( 

120 render_template("500.html", status_code=status_code), 

121 status_code, 

122 ) 

123 

124 @app.errorhandler(StoreApiResponseDecodeError) 

125 @app.errorhandler(StoreApiResponseError) 

126 @app.errorhandler(StoreApiConnectionError) 

127 @app.errorhandler(StoreApiError) 

128 def handle_store_api_error(e): 

129 status_code = 502 

130 return ( 

131 render_template( 

132 "500.html", error_message=str(e), status_code=status_code 

133 ), 

134 status_code, 

135 ) 

136 

137 @app.after_request 

138 def add_headers(response): 

139 """ 

140 Security headers to add to all requests 

141 - Content-Security-Policy: Restrict resources (e.g., JavaScript, CSS, 

142 Images) and URLs 

143 - Referrer-Policy: Limit referrer data for security while preserving 

144 full referrer for same-origin requests 

145 - Cross-Origin-Embedder-Policy: allows embedding cross-origin 

146 resources without credentials 

147 - Cross-Origin-Opener-Policy: enable the page to open pop-ups while 

148 maintaining same-origin policy 

149 - Cross-Origin-Resource-Policy: allowing cross-origin requests to 

150 access the resource 

151 - X-Permitted-Cross-Domain-Policies: disallows cross-domain access to 

152 resources 

153 """ 

154 response.headers["Content-Security-Policy"] = helpers.get_csp_as_str( 

155 CSP 

156 ) 

157 response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin" 

158 response.headers["Cross-Origin-Embedder-Policy"] = "credentialless" 

159 response.headers["Cross-Origin-Opener-Policy"] = ( 

160 "same-origin-allow-popups" 

161 ) 

162 response.headers["Cross-Origin-Resource-Policy"] = "cross-origin" 

163 response.headers["X-Permitted-Cross-Domain-Policies"] = "none" 

164 return response