|
41 | 41 | from tornado.httputil import url_concat
|
42 | 42 | from tornado.log import LogFormatter, access_log, app_log, gen_log
|
43 | 43 | from tornado.netutil import bind_sockets
|
| 44 | +from tornado.routing import Matcher, Rule |
44 | 45 |
|
45 | 46 | if not sys.platform.startswith("win"):
|
46 | 47 | from tornado.netutil import bind_unix_socket
|
@@ -280,8 +281,52 @@ def __init__(
|
280 | 281 | )
|
281 | 282 | handlers = self.init_handlers(default_services, settings)
|
282 | 283 |
|
| 284 | + undecorated_methods = [] |
| 285 | + for matcher, handler, *_ in handlers: |
| 286 | + undecorated_methods.extend(self._check_handler_auth(matcher, handler)) |
| 287 | + |
| 288 | + if undecorated_methods: |
| 289 | + message = ( |
| 290 | + "Core endpoints without @allow_unauthenticated, @ws_authenticated, nor @web.authenticated:\n" |
| 291 | + + "\n".join(undecorated_methods) |
| 292 | + ) |
| 293 | + if jupyter_app.allow_unauthenticated_access: |
| 294 | + warnings.warn( |
| 295 | + message, |
| 296 | + RuntimeWarning, |
| 297 | + stacklevel=2, |
| 298 | + ) |
| 299 | + else: |
| 300 | + raise Exception(message) |
| 301 | + |
283 | 302 | super().__init__(handlers, **settings)
|
284 | 303 |
|
| 304 | + def add_handlers(self, host_pattern, host_handlers): |
| 305 | + undecorated_methods = [] |
| 306 | + for rule in host_handlers: |
| 307 | + if isinstance(rule, Rule): |
| 308 | + matcher = rule.matcher |
| 309 | + handler = rule.target |
| 310 | + else: |
| 311 | + matcher, handler, *_ = rule |
| 312 | + undecorated_methods.extend(self._check_handler_auth(matcher, handler)) |
| 313 | + |
| 314 | + if undecorated_methods: |
| 315 | + message = ( |
| 316 | + "Extension endpoints without @allow_unauthenticated, @ws_authenticated, nor @web.authenticated:\n" |
| 317 | + + "\n".join(undecorated_methods) |
| 318 | + ) |
| 319 | + if self.settings["allow_unauthenticated_access"]: |
| 320 | + warnings.warn( |
| 321 | + message, |
| 322 | + RuntimeWarning, |
| 323 | + stacklevel=2, |
| 324 | + ) |
| 325 | + else: |
| 326 | + raise Exception(message) |
| 327 | + |
| 328 | + return super().add_handlers(host_pattern, host_handlers) |
| 329 | + |
285 | 330 | def init_settings(
|
286 | 331 | self,
|
287 | 332 | jupyter_app,
|
@@ -487,6 +532,21 @@ def last_activity(self):
|
487 | 532 | sources.extend(self.settings["last_activity_times"].values())
|
488 | 533 | return max(sources)
|
489 | 534 |
|
| 535 | + def _check_handler_auth(self, matcher: t.Union[str, Matcher], handler: web.RequestHandler): |
| 536 | + missing_authentication = [] |
| 537 | + for method_name in handler.SUPPORTED_METHODS: |
| 538 | + method = getattr(handler, method_name.lower()) |
| 539 | + is_unimplemented = method == web.RequestHandler._unimplemented_method |
| 540 | + is_allowlisted = hasattr(method, "__allow_unauthenticated") |
| 541 | + possibly_blocklisted = hasattr( |
| 542 | + method, "__wrapped__" |
| 543 | + ) # TODO: can we make web.auth leave a better footprint? |
| 544 | + if not is_unimplemented and not is_allowlisted and not possibly_blocklisted: |
| 545 | + missing_authentication.append( |
| 546 | + f"- {method_name} of {handler.__class__.__name__} registered for {matcher}" |
| 547 | + ) |
| 548 | + return missing_authentication |
| 549 | + |
490 | 550 |
|
491 | 551 | class JupyterPasswordApp(JupyterApp):
|
492 | 552 | """Set a password for the Jupyter server.
|
|
0 commit comments