Skip to content

Commit

Permalink
implement scope-based auth, require jupyterhub 3
Browse files Browse the repository at this point in the history
users must have e.g. `access:services!service=kbatch` scope
  • Loading branch information
minrk committed Sep 9, 2024
1 parent 8c13e1b commit 3c520ff
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 14 deletions.
34 changes: 23 additions & 11 deletions kbatch-proxy/kbatch_proxy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class UserOut(BaseModel):


# ----------------------------------------------------------------------------
# Jupyterhub configuration
# JupyterHub configuration
# TODO: make auth pluggable

auth = jupyterhub.services.auth.HubAuth(
Expand All @@ -121,17 +121,29 @@ class UserOut(BaseModel):


async def get_current_user(request: Request) -> User:
# cookie = request.cookies.get(auth.cookie_name)
cookie = None # TODO: jupyterhub 2.0 compat
token = request.headers.get(auth.auth_header_name)

if cookie:
user = auth.user_for_cookie(cookie)
elif token:
token = token.removeprefix("token ").removeprefix("Token ")
if not auth.access_scopes:
raise RuntimeError(
"JupyterHub OAuth scopes for access to kbatch not defined. "
"Set $JUPYTERHUB_OAUTH_SCOPES and/or $JUPYTERHUB_SERVICE_NAME."
)
user = None
auth_header = request.headers.get(auth.auth_header_name)
if auth_header:
scheme, *rest = auth_header.split(None, 1)
token = ""
if scheme.lower() in {"bearer", "token"} and rest:
token = rest[0]
user = auth.user_for_token(token)
else:
user = None
if user and not auth.check_scopes(auth.access_scopes, user):
msg = (
"Not allowing request with scopes:"
f" {user['scopes']}. Needs scope(s): {auth.access_scopes}"
)
logger.warning(f"{msg} (user={user['name']})")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=msg,
)

if user:
return User(**user, api_token=token)
Expand Down
2 changes: 1 addition & 1 deletion kbatch-proxy/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ install_requires =
escapism
fastapi
httpx
jupyterhub
jupyterhub>=3
kubernetes
pydantic>=2,<3
pydantic-settings
Expand Down
21 changes: 19 additions & 2 deletions kbatch-proxy/tests/test_app.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import sys
import pytest
import pathlib
Expand All @@ -15,9 +16,20 @@
def mock_hub_auth(mocker):
def side_effect(token):
if token == "abc":
return {"name": "testuser", "groups": ["testgroup"]}
return {
"name": "testuser",
"groups": ["testgroup"],
"scopes": ["access:services"],
}
elif token == "def":
return {
"name": "testuser2",
"groups": [],
"scopes": ["access:servers!user=testuser2"],
}

mocker.patch("kbatch_proxy.main.auth.user_for_token", side_effect=side_effect)
mocker.patch.dict(os.environ, {"JUPYTERHUB_SERVICE_NAME": "kbatch"})


def test_read_main():
Expand All @@ -34,8 +46,13 @@ def test_authorized():
response = client.get("/authorized", headers={"Authorization": "Token not-a-token"})
assert response.status_code == 401

response = client.get("/authorized", headers={"Authorization": "Token abc"})
response = client.get("/authorized", headers={"Authorization": "token abc"})
assert response.status_code == 200
response = client.get("/authorized", headers={"Authorization": "Bearer abc"})
assert response.status_code == 200

response = client.get("/authorized", headers={"Authorization": "Bearer def"})
assert response.status_code == 403


def test_loads_profile():
Expand Down

0 comments on commit 3c520ff

Please sign in to comment.