Skip to content

Commit

Permalink
Add allow_unauthenticated_access traitlet and @allow_unauthenticated
Browse files Browse the repository at this point in the history
  • Loading branch information
krassowski committed Feb 10, 2024
1 parent 8c963a1 commit 3f65147
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 0 deletions.
27 changes: 27 additions & 0 deletions jupyter_server/auth/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,30 @@ async def inner(self, *args, **kwargs):
return cast(FuncT, wrapper(method))

return cast(FuncT, wrapper)


def allow_unauthenticated(method: FuncT) -> FuncT:
"""A decorator for tornado.web.RequestHandler methods
that allows any user to make the following request.
Selectively disables the 'authentication' layer of REST API which
is active when `ServerApp.allow_unauthenticated_access = False`.
To be used exclusively on endpoints which may be considered public,
for example the logic page handler.
.. versionadded:: 2.13
Parameters
----------
method : bound callable
the endpoint method to remove authentication from.
"""

@wraps(method)
async def wrapper(self, *args, **kwargs):
return method(self, *args, **kwargs)

setattr(wrapper, "__allow_unauthenticated", True)

return cast(FuncT, wrapper)
4 changes: 4 additions & 0 deletions jupyter_server/auth/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from tornado.escape import url_escape

from ..base.handlers import JupyterHandler
from .decorator import allow_unauthenticated
from .security import passwd_check, set_password


Expand Down Expand Up @@ -73,6 +74,7 @@ def _redirect_safe(self, url, default=None):
url = default
self.redirect(url)

@allow_unauthenticated
def get(self):
"""Get the login form."""
if self.current_user:
Expand All @@ -81,6 +83,7 @@ def get(self):
else:
self._render()

@allow_unauthenticated
def post(self):
"""Post a login."""
user = self.current_user = self.identity_provider.process_login_form(self)
Expand Down Expand Up @@ -110,6 +113,7 @@ def passwd_check(self, a, b):
"""Check a passwd."""
return passwd_check(a, b)

@allow_unauthenticated
def post(self):
"""Post a login form."""
typed_password = self.get_argument("password", default="")
Expand Down
2 changes: 2 additions & 0 deletions jupyter_server/auth/logout.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
from ..base.handlers import JupyterHandler
from .decorator import allow_unauthenticated


class LogoutHandler(JupyterHandler):
"""An auth logout handler."""

@allow_unauthenticated
def get(self):
"""Handle a logout."""
self.identity_provider.clear_login_cookie(self)
Expand Down
10 changes: 10 additions & 0 deletions jupyter_server/base/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,16 @@ async def prepare(self) -> Awaitable[None] | None: # type:ignore[override]
self.set_cors_headers()
if self.request.method not in {"GET", "HEAD", "OPTIONS"}:
self.check_xsrf_cookie()

if not self.settings.get("allow_unauthenticated_access", False):
if not self.request.method:
raise HTTPError(403)
method = getattr(self, self.request.method.lower())
if not getattr(method, "__allow_unauthenticated", False):
# reuse `web.authenticated` logic, which redirects to the login
# page on GET and HEAD and otherwise raises 403
return web.authenticated(lambda _method: None)(self)

return super().prepare()

# ---------------------------------------------------------------
Expand Down
15 changes: 15 additions & 0 deletions jupyter_server/serverapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,21 @@ def _deprecated_password_config(self, change: t.Any) -> None:
""",
)

allow_unauthenticated_access = Bool(
True,
config=True,
help="""Allow requests unauthenticated access to endpoints without authentication rules.
When set to `True` (default in jupyter-server 2.0, subject to change
in the future), any request to an endpoint without an authentication rule
(either `@tornado.web.authenticated`, or `@allow_unauthenticated`)
will be permitted, regardless of whether user has logged in or not.
When set to `False`, logging in will be required for access to each endpoint,
excluding the endpoints marked with `@allow_unauthenticated` decorator.
""",
)

allow_remote_access = Bool(
config=True,
help="""Allow requests where the Host header doesn't point to a local server
Expand Down

0 comments on commit 3f65147

Please sign in to comment.