Source code for flux_restful.routers.depends
from typing import Generator, Optional
from fastapi import Depends, HTTPException, Request, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials, OAuth2PasswordBearer
from sqlalchemy.orm import Session
from flux_restful.auth import Principal, get_backend
from flux_restful.auth.base import is_system_user
from flux_restful.core.config import settings
from flux_restful.db.session import SessionLocal
login_url = f"{settings.api_version}/login/access-token"
# auto_error is off so the "none" backend can serve anonymous requests and so
# we control the 401 response (clients rely on the WWW-Authenticate header).
oauth2_scheme = OAuth2PasswordBearer(tokenUrl=login_url, auto_error=False)
basic_scheme = HTTPBasic(auto_error=False)
[docs]def get_db() -> Generator:
"""
Get the database in a context so we can then close it.
"""
try:
db = SessionLocal()
yield db
finally:
db.close()
[docs]def unauthorized(detail: str, scheme: str = "Bearer") -> HTTPException:
return HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=detail,
headers={"WWW-Authenticate": scheme},
)
def _accept(principal: Optional[Principal]) -> Principal:
"""
Final checks on an authenticated principal, whatever backend produced it.
"""
if principal is None:
raise unauthorized("Could not validate credentials")
if not principal.is_active:
raise HTTPException(status_code=400, detail="Inactive user")
# In multi-user mode the server becomes this user to run their jobs, so
# the name must be a real, unprivileged system account (FLUX_MIN_UID).
# Backends may check earlier for a better message; this is authoritative.
if settings.flux_server_mode == "multi-user" and not is_system_user(
principal.user_name
):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"{principal.user_name} is not an allowed system account on this server.",
)
return principal
def _verify(db: Session, token: str) -> Principal:
return _accept(get_backend().verify_token(db, token))
[docs]def current_user(
db: Session = Depends(get_db), token: Optional[str] = Depends(oauth2_scheme)
) -> Optional[Principal]:
"""
The authenticated API user, or None when the auth backend is "none".
"""
if get_backend().name == "none":
return None
if not token:
raise unauthorized("Not authenticated")
return _verify(db, token)
[docs]def current_superuser(user: Optional[Principal] = Depends(current_user)) -> Principal:
"""
The authenticated user, who must be a superuser.
"""
if user is None:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="This action requires a superuser, but the auth backend is 'none'.",
)
if not user.is_superuser:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="The user doesn't have enough privileges",
)
return user
def _bearer_token(request: Request) -> Optional[str]:
"""
A bearer token from the Authorization header or the access_token cookie.
The cookie is how a browser can use the web UI with backends that have
no password (oidc). Form posts are protected against cross-site requests
by flux_restful.library.csrf, so a cookie-authenticated browser cannot be made to
submit or cancel jobs from another site.
"""
header = request.headers.get("Authorization", "")
scheme, _, token = header.partition(" ")
if scheme.lower() == "bearer" and token.strip():
return token.strip()
return request.cookies.get("access_token")
[docs]def current_user_views(
request: Request,
db: Session = Depends(get_db),
credentials: Optional[HTTPBasicCredentials] = Depends(basic_scheme),
) -> Optional[Principal]:
"""
The authenticated web UI user, or None when the auth backend is "none".
A bearer token (header or access_token cookie) is accepted for every
backend. Password backends additionally accept HTTP Basic auth, which is
what browsers use.
"""
backend = get_backend()
if backend.name == "none":
return None
token = _bearer_token(request)
if token:
return _verify(db, token)
if not backend.supports_password:
raise unauthorized("Not authenticated")
if credentials is None:
raise unauthorized("Not authenticated", scheme="Basic")
principal = backend.authenticate(db, credentials.username, credentials.password)
if principal is None:
raise unauthorized("Incorrect user or password", scheme="Basic")
return _accept(principal)