diff --git a/jupyter_server/base/handlers.py b/jupyter_server/base/handlers.py index 4bf26df177..945a3096e5 100644 --- a/jupyter_server/base/handlers.py +++ b/jupyter_server/base/handlers.py @@ -67,6 +67,10 @@ def log(): class AuthenticatedHandler(web.RequestHandler): """A RequestHandler with an authenticated user.""" + @property + def base_url(self) -> str: + return self.settings.get("base_url", "/") + @property def content_security_policy(self): """The default Content-Security-Policy header @@ -288,10 +292,6 @@ def mathjax_url(self): def mathjax_config(self): return self.settings.get("mathjax_config", "TeX-AMS-MML_HTMLorMML-full,Safe") - @property - def base_url(self) -> str: - return self.settings.get("base_url", "/") - @property def default_url(self): return self.settings.get("default_url", "") @@ -830,12 +830,12 @@ def head(self, path): @web.authenticated @authorized - def get(self, path): + def get(self, path, **kwargs): if os.path.splitext(path)[1] == ".ipynb" or self.get_argument("download", None): name = path.rsplit("/", 1)[-1] self.set_attachment_header(name) - return web.StaticFileHandler.get(self, path) + return web.StaticFileHandler.get(self, path, **kwargs) def get_content_type(self): assert self.absolute_path is not None @@ -879,7 +879,7 @@ def validate_absolute_path(self, root, absolute_path): return abs_path -def json_errors(method): +def json_errors(method): # pragma: no cover """Decorate methods with this to return GitHub style JSON errors. This should be used on any JSON API on any handler method that can raise HTTPErrors. diff --git a/tests/test_handlers.py b/tests/test_handlers.py new file mode 100644 index 0000000000..bb4fcf0ce8 --- /dev/null +++ b/tests/test_handlers.py @@ -0,0 +1,125 @@ +"""Test Base Handlers""" +import os +import warnings +from unittest.mock import MagicMock + +from tornado.httpserver import HTTPRequest +from tornado.httputil import HTTPHeaders + +from jupyter_server.auth import AllowAllAuthorizer, IdentityProvider +from jupyter_server.base.handlers import ( + APIHandler, + APIVersionHandler, + AuthenticatedFileHandler, + AuthenticatedHandler, + FilesRedirectHandler, + JupyterHandler, + RedirectWithParams, +) +from jupyter_server.serverapp import ServerApp + + +def test_authenticated_handler(jp_serverapp): + app: ServerApp = jp_serverapp + request = HTTPRequest("OPTIONS") + request.connection = MagicMock() + handler = AuthenticatedHandler(app.web_app, request) + for key in list(handler.settings): + del handler.settings[key] + handler.settings["headers"] = {"Content-Security-Policy": "foo"} + + assert handler.content_security_policy == "foo" + assert handler.skip_check_origin() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + assert handler.login_handler == handler.identity_provider.login_handler_class + assert isinstance(handler.authorizer, AllowAllAuthorizer) + assert isinstance(handler.identity_provider, IdentityProvider) + + +def test_jupyter_handler(jp_serverapp): + app: ServerApp = jp_serverapp + headers = HTTPHeaders({"Origin": "foo"}) + request = HTTPRequest("OPTIONS", headers=headers) + request.connection = MagicMock() + handler = JupyterHandler(app.web_app, request) + for key in list(handler.settings): + del handler.settings[key] + handler.settings["mathjax_url"] = "foo" + handler.settings["mathjax_config"] = "bar" + assert handler.mathjax_url == "/foo" + assert handler.mathjax_config == "bar" + handler.settings["terminal_manager"] = "fizz" + assert handler.terminal_manager == "fizz" + handler.settings["allow_origin"] = True + handler.set_cors_headers() + handler.settings["allow_origin"] = False + handler.settings["allow_origin_pat"] = "foo" + handler.settings["allow_credentials"] = True + handler.set_cors_headers() + assert handler.check_referer() is True + + +def test_api_handler(jp_serverapp): + app: ServerApp = jp_serverapp + headers = HTTPHeaders({"Origin": "foo"}) + request = HTTPRequest("OPTIONS", headers=headers) + request.connection = MagicMock() + handler = APIHandler(app.web_app, request) + for key in list(handler.settings): + del handler.settings[key] + handler.options() + + +async def test_authenticated_file_handler(jp_serverapp, tmpdir): + app: ServerApp = jp_serverapp + headers = HTTPHeaders({"Origin": "foo"}) + request = HTTPRequest("HEAD", headers=headers) + request.connection = MagicMock() + test_file = tmpdir / "foo" + with open(test_file, "w") as fid: + fid.write("hello") + + handler = AuthenticatedFileHandler(app.web_app, request, path=str(tmpdir)) + for key in list(handler.settings): + del handler.settings[key] + handler.check_xsrf_cookie = MagicMock() # type:ignore + handler._jupyter_current_user = "foo" # type:ignore + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + await handler.head("foo") + assert handler.get_status() == 200 + + +async def test_api_version_handler(jp_serverapp): + app: ServerApp = jp_serverapp + request = HTTPRequest("GET") + request.connection = MagicMock() + handler = APIVersionHandler(app.web_app, request) + handler._transforms = [] + handler.get() + assert handler.get_status() == 200 + + +async def test_files_redirect_handler(jp_serverapp): + app: ServerApp = jp_serverapp + request = HTTPRequest("GET") + request.connection = MagicMock() + test_file = os.path.join(app.contents_manager.root_dir, "foo") + with open(test_file, "w") as fid: + fid.write("hello") + handler = FilesRedirectHandler(app.web_app, request) + handler._transforms = [] + await handler.get("foo") + assert handler.get_status() == 302 + + +def test_redirect_with_params(jp_serverapp): + app: ServerApp = jp_serverapp + request = HTTPRequest("GET") + request.connection = MagicMock() + request.query = "foo" + handler = RedirectWithParams(app.web_app, request, url="foo") + handler._transforms = [] + handler.get() + assert handler.get_status() == 301