Coverage for webapp/handlers.py: 73%
45 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-17 22:07 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-17 22:07 +0000
1from flask import render_template
2from webapp.config import SENTRY_DSN
4from canonicalwebteam.exceptions import (
5 StoreApiError,
6 StoreApiResourceNotFound,
7 StoreApiResponseDecodeError,
8 StoreApiResponseError,
9 StoreApiResponseErrorList,
10 StoreApiTimeoutError,
11 StoreApiConnectionError,
12)
14from canonicalwebteam import image_template
16from webapp import helpers
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}
65def rock_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 """
72 account = None
73 return {
74 "schedule_banner": helpers.schedule_banner,
75 "account": account,
76 "image": image_template,
77 "SENTRY_DSN": SENTRY_DSN,
78 }
81def set_handlers(app):
82 @app.context_processor
83 def utility_processor():
84 return rock_utility_processor()
86 # Error handlers
87 # ===
88 @app.errorhandler(StoreApiTimeoutError)
89 def handle_store_api_timeout(e):
90 status_code = 504
91 return (
92 render_template(
93 "500.html", error_message=str(e), status_code=status_code
94 ),
95 status_code,
96 )
98 @app.errorhandler(StoreApiResourceNotFound)
99 def handle_store_api_circuit_breaker_exception(e):
100 return render_template("404.html", message=str(e)), 404
102 @app.errorhandler(StoreApiResponseErrorList)
103 def handle_store_api_error_list(e):
104 if e.status_code == 404:
105 return render_template("404.html", message="Entity not found"), 404
107 status_code = 502
108 if e.errors:
109 errors = ", ".join([e.get("message") for e in e.errors])
110 return (
111 render_template(
112 "500.html", error_message=errors, status_code=status_code
113 ),
114 status_code,
115 )
117 return (
118 render_template("500.html", status_code=status_code),
119 status_code,
120 )
122 @app.errorhandler(StoreApiResponseDecodeError)
123 @app.errorhandler(StoreApiResponseError)
124 @app.errorhandler(StoreApiConnectionError)
125 @app.errorhandler(StoreApiError)
126 def handle_store_api_error(e):
127 status_code = 502
128 return (
129 render_template(
130 "500.html", error_message=str(e), status_code=status_code
131 ),
132 status_code,
133 )
135 @app.after_request
136 def add_headers(response):
137 """
138 Security headers to add to all requests
139 - Content-Security-Policy: Restrict resources (e.g., JavaScript, CSS,
140 Images) and URLs
141 - Referrer-Policy: Limit referrer data for security while preserving
142 full referrer for same-origin requests
143 - Cross-Origin-Embedder-Policy: allows embedding cross-origin
144 resources without credentials
145 - Cross-Origin-Opener-Policy: enable the page to open pop-ups while
146 maintaining same-origin policy
147 - Cross-Origin-Resource-Policy: allowing cross-origin requests to
148 access the resource
149 - X-Permitted-Cross-Domain-Policies: disallows cross-domain access to
150 resources
151 """
152 response.headers["Content-Security-Policy"] = helpers.get_csp_as_str(
153 CSP
154 )
155 response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
156 response.headers["Cross-Origin-Embedder-Policy"] = "credentialless"
157 response.headers["Cross-Origin-Opener-Policy"] = (
158 "same-origin-allow-popups"
159 )
160 response.headers["Cross-Origin-Resource-Policy"] = "cross-origin"
161 response.headers["X-Permitted-Cross-Domain-Policies"] = "none"
162 return response