Skip to content

Commit

Permalink
Improve and test tornado.web.authenticated heuristic
Browse files Browse the repository at this point in the history
  • Loading branch information
krassowski committed Feb 13, 2024
1 parent 0facfec commit 7b9b0a8
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 2 deletions.
22 changes: 20 additions & 2 deletions jupyter_server/serverapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,15 +538,33 @@ def _check_handler_auth(
method = getattr(handler, method_name.lower())
is_unimplemented = method == web.RequestHandler._unimplemented_method
is_allowlisted = hasattr(method, "__allow_unauthenticated")
# TODO: modify `tornado.web.authenticated` upstream?
is_blocklisted = method.__code__.co_qualname.startswith("authenticated")
is_blocklisted = _has_tornado_web_authenticated(method)
if not is_unimplemented and not is_allowlisted and not is_blocklisted:
missing_authentication.append(
f"- {method_name} of {handler.__name__} registered for {matcher}"
)
return missing_authentication


def _has_tornado_web_authenticated(method: t.Callable) -> bool:
"""Check if given method was decorated with @web.authenticated.
Note: it is ok if we reject on @authorized @web.authenticated
because the correct order is @web.authenticated @authorized.
"""
if not hasattr(method, "__wrapped__"):
return False
if not hasattr(method, "__code__"):
return False
code = method.__code__
if hasattr(code, "co_qualname"):
# new in 3.11
return code.co_qualname.startswith("authenticated")
elif hasattr(code, "co_filename"):
return code.co_filename.replace("\\", "/").endswith("tornado/web.py")
return False


class JupyterPasswordApp(JupyterApp):
"""Set a password for the Jupyter server.
Expand Down
22 changes: 22 additions & 0 deletions tests/test_serverapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@

import pytest
from jupyter_core.application import NoStart
from tornado import web
from traitlets import TraitError
from traitlets.config import Config
from traitlets.tests.utils import check_help_all_output

from jupyter_server.auth.decorator import allow_unauthenticated, authorized
from jupyter_server.auth.security import passwd_check
from jupyter_server.serverapp import (
JupyterPasswordApp,
JupyterServerListApp,
ServerApp,
ServerWebApplication,
_has_tornado_web_authenticated,
list_running_servers,
random_ports,
)
Expand Down Expand Up @@ -637,3 +640,22 @@ def test_immutable_cache_trait():
serverapp.init_configurables()
serverapp.init_webapp()
assert serverapp.web_app.settings["static_immutable_cache"] == ["/test/immutable"]


def test():
pass


@pytest.mark.parametrize(
"method, expected",
[
[test, False],
[allow_unauthenticated(test), False],
[authorized(test), False],
[web.authenticated(test), True],
[web.authenticated(authorized(test)), True],
[authorized(web.authenticated(test)), False], # wrong order!
],
)
def test_tornado_authentication_detection(method, expected):
assert _has_tornado_web_authenticated(method) == expected

0 comments on commit 7b9b0a8

Please sign in to comment.