Coverage for webapp/handlers.py: 79%
47 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-27 22:07 +0000
« 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
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 authentication, 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 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 }
83def set_handlers(app):
84 @app.context_processor
85 def utility_processor():
86 return charmhub_utility_processor()
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 )
100 @app.errorhandler(StoreApiResourceNotFound)
101 def handle_store_api_circuit_breaker_exception(e):
102 return render_template("404.html", message=str(e)), 404
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
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 )
119 return (
120 render_template("500.html", status_code=status_code),
121 status_code,
122 )
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 )
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