@@ -103,7 +103,7 @@ def send_thrift_exception(self, error_msg, iprot, oprot, otrans):
103
103
self .end_headers ()
104
104
self .wfile .write (result )
105
105
106
- def __check_session_cookie (self ):
106
+ def __check_session_header (self ):
107
107
"""
108
108
Check the CodeChecker privileged access cookie in the request headers.
109
109
@@ -115,9 +115,19 @@ def __check_session_cookie(self):
115
115
return None
116
116
117
117
session = None
118
+ # Check if the user has presented a bearer token for authentication.
119
+ token = self .headers .get ("Authorization" )
120
+ if token and token .startswith ("Bearer " ):
121
+ token = token .split ("Bearer " , 1 )[1 ]
122
+ session = self .server .manager .get_session (token )
123
+
118
124
# Check if the user has presented a privileged access cookie.
125
+ # This method is used by older command-line clients, and since cookies
126
+ # with an expiration date were purged when updating, the web interface
127
+ # should no longer have a valid access token as a cookie.
128
+ # DEPRECATED: Will be removed in a future version.
119
129
cookies = self .headers .get ("Cookie" )
120
- if cookies :
130
+ if not session and cookies :
121
131
split = cookies .split ("; " )
122
132
for cookie in split :
123
133
values = cookie .split ("=" )
@@ -126,13 +136,13 @@ def __check_session_cookie(self):
126
136
session = self .server .manager .get_session (values [1 ])
127
137
128
138
if session and session .is_alive :
129
- # If a valid session token was found and it can still be used,
139
+ # If a valid bearer token was found and it can still be used,
130
140
# mark that the user's last access to the server was the
131
141
# request that resulted in the execution of this function.
132
142
session .revalidate ()
133
143
return session
134
144
else :
135
- # If the user's access cookie is no longer usable (invalid),
145
+ # If the user's token is no longer usable (invalid),
136
146
# present an error.
137
147
client_host , client_port , is_ipv6 = \
138
148
RequestHandler ._get_client_host_port (self .client_address )
@@ -168,22 +178,46 @@ def __handle_liveness(self):
168
178
self .wfile .write (b'CODECHECKER_SERVER_IS_LIVE' )
169
179
170
180
def end_headers (self ):
171
- # Sending the authentication cookie
172
- # in every response if any.
173
- # This will update the the session cookie
174
- # on the clients to the newest.
175
- if self .auth_session :
176
- token = self .auth_session .token
177
- if token :
178
- self .send_header (
179
- "Set-Cookie" ,
180
- f"{ session_manager .SESSION_COOKIE_NAME } ={ token } ; Path=/" )
181
+ """
182
+ Headers in this section are based on the OWASP Secure Headers Project.
183
+ https://owasp.org/www-project-secure-headers/
184
+ They are adapted to not allow any cross-site requests.
185
+ """
186
+ if self .command in ['GET' , 'HEAD' , 'POST' ]:
187
+ self .send_header ('X-Frame-Options' , 'DENY' )
188
+ self .send_header ('X-XSS-Protection' , '1; mode=block' )
189
+ self .send_header ('X-Content-Type-Options' , 'nosniff' )
190
+ self .send_header ('Content-Security-Policy' ,
191
+ 'default-src \' self\' ; ' +
192
+ 'style-src \' unsafe-inline\' \' self\' ; ' +
193
+ 'script-src \' unsafe-inline\' \' self\' ; ' +
194
+ 'form-action \' self\' ; ' +
195
+ 'frame-ancestors \' none\' ; ' +
196
+ 'upgrade-insecure-requests; ' +
197
+ 'block-all-mixed-content' )
198
+ self .send_header ('Cross-Origin-Embedder-Policy' , 'require-corp' )
199
+ self .send_header ('Cross-Origin-Opener-Policy' , 'same-origin' )
200
+ self .send_header ('Cross-Origin-Resource-Policy' , 'same-origin' )
201
+ self .send_header ('Referrer-Policy' , 'no-referrer' )
181
202
203
+ if self .auth_session :
182
204
# Set the current user name in the header.
183
205
user_name = self .auth_session .user
184
206
if user_name :
185
207
self .send_header ("X-User" , user_name )
186
208
209
+ # If the user has a leftover session cookie, try removing it.
210
+ # Command-line clients ignore overwriting the cookie, but web clients
211
+ # will remove the cookie from the browser.
212
+ # DEPRECATED: Will be removed in a future version.
213
+ elif self .headers .get ('Cookie' ):
214
+ self .send_header ('Set-Cookie' ,
215
+ session_manager .SESSION_COOKIE_NAME + '=; ' +
216
+ 'Path=/; ' +
217
+ 'Max-Age=0; ' +
218
+ 'HttpOnly; ' +
219
+ 'SameSite=Strict' )
220
+
187
221
SimpleHTTPRequestHandler .end_headers (self )
188
222
189
223
@staticmethod
@@ -199,6 +233,23 @@ def _get_client_host_port(address):
199
233
200
234
raise IndexError ("Invalid address tuple given." )
201
235
236
+ def do_OPTIONS (self ): # pylint: disable=C0103
237
+ """
238
+ Handle OPTIONS requests.
239
+
240
+ Always returns 403 to indicate that no cross-site requests are allowed.
241
+ No CORS heeaders are allowed next to the 403 response.
242
+ """
243
+ client_host , client_port , is_ipv6 = \
244
+ RequestHandler ._get_client_host_port (self .client_address )
245
+
246
+ LOG .debug ("%s:%s -- [Anonymous] OPTIONS %s" ,
247
+ client_host if not is_ipv6 else '[' + client_host + ']' ,
248
+ client_port , self .path )
249
+
250
+ self .send_response (403 )
251
+ self .end_headers ()
252
+
202
253
def do_GET (self ):
203
254
""" Handles the SPA browser access (GET requests).
204
255
@@ -212,12 +263,14 @@ def do_GET(self):
212
263
"""
213
264
client_host , client_port , is_ipv6 = \
214
265
RequestHandler ._get_client_host_port (self .client_address )
215
- self .auth_session = self .__check_session_cookie ()
216
266
217
- username = self .auth_session .user if self .auth_session else 'Anonymous'
218
- LOG .debug ("%s:%s -- [%s] GET %s" ,
267
+ # GET requests are served from www_root.
268
+ self .directory = self .server .www_root
269
+
270
+ # Bearer tokens are not sent alongside GET requests.
271
+ LOG .debug ("%s:%s -- [Anonymous] GET %s" ,
219
272
client_host if not is_ipv6 else '[' + client_host + ']' ,
220
- client_port , username , self .path )
273
+ client_port , self .path )
221
274
222
275
if self .path == '/' :
223
276
self .path = 'index.html'
@@ -310,7 +363,7 @@ def do_POST(self):
310
363
311
364
client_host , client_port , is_ipv6 = \
312
365
RequestHandler ._get_client_host_port (self .client_address )
313
- self .auth_session = self .__check_session_cookie ()
366
+ self .auth_session = self .__check_session_header ()
314
367
auth_user = \
315
368
self .auth_session .user if self .auth_session else "Anonymous"
316
369
host_info = client_host if not is_ipv6 else '[' + client_host + ']'
0 commit comments