mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-08 10:34:10 +00:00
Add models, remove cookie auth
get_current_user returns a User model instead of a dict. using cookies for Hub auth is deprecated, so removed that option and refactored get_current_user
This commit is contained in:
@@ -4,83 +4,55 @@ from fastapi import HTTPException
|
|||||||
from fastapi import Security
|
from fastapi import Security
|
||||||
from fastapi import status
|
from fastapi import status
|
||||||
from fastapi.security import OAuth2AuthorizationCodeBearer
|
from fastapi.security import OAuth2AuthorizationCodeBearer
|
||||||
from fastapi.security.api_key import APIKeyCookie
|
|
||||||
from fastapi.security.api_key import APIKeyQuery
|
from fastapi.security.api_key import APIKeyQuery
|
||||||
|
|
||||||
from .client import get_client
|
from .client import get_client
|
||||||
|
from .models import User
|
||||||
|
|
||||||
### For authenticated endpoints, we want to get auth from one of three ways
|
### Endpoints can require authentication using Depends(get_current_user)
|
||||||
### 1. "token" in the url params
|
### get_current_user will look for a token in url params or
|
||||||
### 2. "jupyterhub-services" cookie (or config via env)
|
### Authorization: bearer token (header).
|
||||||
### 3. Authorization Bearer header (with oauth to Hub support)
|
### Hub technically supports cookie auth too, but it is deprecated so
|
||||||
|
### not being included here.
|
||||||
auth_by_param = APIKeyQuery(name="token", auto_error=False)
|
auth_by_param = APIKeyQuery(name="token", auto_error=False)
|
||||||
|
|
||||||
COOKIE_NAME = os.getenv("JUPYTERHUB_COOKIE_NAME", "jupyterhub-services")
|
auth_url = os.environ["PUBLIC_HOST"] + "/hub/api/oauth2/authorize"
|
||||||
auth_by_cookie = APIKeyCookie(name=COOKIE_NAME, auto_error=False)
|
|
||||||
|
|
||||||
if "PUBLIC_HOST" in os.environ:
|
|
||||||
### When running in Docker or maybe other infrastructure,
|
|
||||||
### JUPYTERHUB_API_URL is "http://jupyterhub" but we need to give
|
|
||||||
### clients (swagger webpage) a link to the public url
|
|
||||||
auth_url = os.environ["PUBLIC_HOST"] + "/hub/api/oauth2/authorize"
|
|
||||||
else:
|
|
||||||
auth_url = os.environ["JUPYTERHUB_API_URL"] + "/oauth2/authorize"
|
|
||||||
auth_by_header = OAuth2AuthorizationCodeBearer(
|
auth_by_header = OAuth2AuthorizationCodeBearer(
|
||||||
authorizationUrl=auth_url, tokenUrl="get_token", auto_error=False
|
authorizationUrl=auth_url, tokenUrl="get_token", auto_error=False
|
||||||
)
|
)
|
||||||
### ^^ For Oauth in the Swagger webpage, we set the authorizationUrl
|
### ^^ The flow for OAuth2 in Swagger is that the "authorize" button
|
||||||
### to the Hub /oauth2/authorize endpoint, so browser does a GET there and
|
### will redirect user (browser) to "auth_url", which is the Hub login page.
|
||||||
### receives a 'code' in return. Then the browser does a POST to our
|
### After logging in, the browser will POST to our internal /get_token endpoint
|
||||||
### internal /get_token endpoint with that code, and our server subsequently
|
### with the auth code. That endpoint POST's to Hub /oauth2/token with
|
||||||
### POSTs to Hub /oauth2/token to get/return an acecss_token.
|
### our client_secret (JUPYTERHUB_API_TOKEN) and that code to get an
|
||||||
### The reason for the double POST is that the client (swagger ui) doesn't have
|
### access_token, which it returns to browser, which places in Authorization header.
|
||||||
### the client_secret (JUPYTERHUB_API_TOKEN), but our server does.
|
|
||||||
|
|
||||||
### For consideration: build a pydantic User model
|
### For consideration: optimize performance with a cache instead of
|
||||||
### instead of just returning the dict from Hub api?
|
|
||||||
### Also: optimize performance with a cache instead of
|
|
||||||
### always hitting the Hub api?
|
### always hitting the Hub api?
|
||||||
async def get_current_user(
|
async def get_current_user(
|
||||||
auth_by_cookie: str = Security(auth_by_cookie),
|
|
||||||
auth_by_param: str = Security(auth_by_param),
|
auth_by_param: str = Security(auth_by_param),
|
||||||
auth_by_header: str = Security(auth_by_header),
|
auth_by_header: str = Security(auth_by_header),
|
||||||
):
|
):
|
||||||
### Note all three Security functions are auto_error=False,
|
|
||||||
### meaning if the scheme (header/cookie/param) isn't present
|
|
||||||
### then they return None.
|
|
||||||
### The cookie can be tricky. Navigating to the Hub login
|
|
||||||
### page but not logging in still sets a cookie, but
|
|
||||||
### Hub API returns a 404 if you query that cookie
|
|
||||||
user = None
|
|
||||||
if auth_by_param is not None or auth_by_header is not None:
|
|
||||||
token = auth_by_param or auth_by_header
|
token = auth_by_param or auth_by_header
|
||||||
|
if token is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Must login with token parameter or Authorization bearer header",
|
||||||
|
)
|
||||||
|
|
||||||
async with get_client() as client:
|
async with get_client() as client:
|
||||||
endpoint = "/authorizations/token/%s" % token
|
endpoint = "/authorizations/token/%s" % token
|
||||||
resp = await client.get(endpoint)
|
resp = await client.get(endpoint)
|
||||||
if resp.is_error:
|
if resp.is_error:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
resp.status_code,
|
status.HTTP_400_BAD_REQUEST,
|
||||||
detail={
|
detail={
|
||||||
"msg": "Error getting user info from token",
|
"msg": "Error getting user info from token",
|
||||||
"request_url": str(resp.request.url),
|
"request_url": str(resp.request.url),
|
||||||
"token": token,
|
"token": token,
|
||||||
|
"response_code": resp.status_code,
|
||||||
"hub_response": resp.json(),
|
"hub_response": resp.json(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else:
|
user = User(**resp.json())
|
||||||
user = resp.json()
|
|
||||||
|
|
||||||
elif auth_by_cookie is not None:
|
|
||||||
async with get_client() as client:
|
|
||||||
endpoint = "/authorizations/cookie/%s/%s" % (COOKIE_NAME, auth_by_cookie)
|
|
||||||
resp = await client.get(endpoint)
|
|
||||||
if not resp.is_error:
|
|
||||||
user = resp.json()
|
|
||||||
|
|
||||||
if user is None:
|
|
||||||
raise HTTPException(
|
|
||||||
status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Must login with token parameter, cookie, or header",
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return user
|
return user
|
||||||
|
Reference in New Issue
Block a user