feat(cloud-auth): enforce public scopes
This commit is contained in:
@@ -6,11 +6,21 @@ from hmac import compare_digest
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
|
||||
TASKS_SUBMIT_SCOPE = "tasks:submit"
|
||||
TASKS_READ_SCOPE = "tasks:read"
|
||||
POOL_READ_SCOPE = "pool:read"
|
||||
PLUGINS_READ_SCOPE = "plugins:read"
|
||||
PLUGINS_ADMIN_SCOPE = "plugins:admin"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Principal:
|
||||
id: str = "anonymous"
|
||||
scopes: frozenset[str] = field(default_factory=frozenset)
|
||||
|
||||
def has_scope(self, scope: str) -> bool:
|
||||
return "*" in self.scopes or scope in self.scopes
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class AuthProvider(Protocol):
|
||||
|
||||
@@ -12,7 +12,16 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from cloud.auth import AuthProvider, NullAuthProvider, Principal
|
||||
from cloud.auth import (
|
||||
PLUGINS_ADMIN_SCOPE,
|
||||
PLUGINS_READ_SCOPE,
|
||||
POOL_READ_SCOPE,
|
||||
TASKS_READ_SCOPE,
|
||||
TASKS_SUBMIT_SCOPE,
|
||||
AuthProvider,
|
||||
NullAuthProvider,
|
||||
Principal,
|
||||
)
|
||||
from cloud.sdk.models import (
|
||||
DeviceResponse,
|
||||
ErrorResponse,
|
||||
@@ -43,12 +52,18 @@ def create_cloud_router(
|
||||
auth = auth_provider or NullAuthProvider()
|
||||
router = APIRouter(prefix=version_prefix, tags=["cloud-platform"])
|
||||
|
||||
def _authorize(request: Request) -> Principal:
|
||||
def _authorize(request: Request, required_scope: str) -> Principal:
|
||||
principal = auth.authenticate(request)
|
||||
if principal is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="unauthorized",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
if not principal.has_scope(required_scope):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"missing required scope: {required_scope}",
|
||||
)
|
||||
return principal
|
||||
|
||||
@@ -61,7 +76,7 @@ def create_cloud_router(
|
||||
payload: TaskSubmissionRequest,
|
||||
request: Request,
|
||||
) -> TaskSubmissionResponse:
|
||||
_authorize(request)
|
||||
_authorize(request, TASKS_SUBMIT_SCOPE)
|
||||
task_constraints = _build_constraints(payload.constraints)
|
||||
try:
|
||||
task_id = scheduler.submit(
|
||||
@@ -78,7 +93,7 @@ def create_cloud_router(
|
||||
|
||||
@router.get("/tasks/{task_id}", response_model=TaskStatusResponse)
|
||||
def get_task_status(task_id: str, request: Request) -> TaskStatusResponse:
|
||||
_authorize(request)
|
||||
_authorize(request, TASKS_READ_SCOPE)
|
||||
task = scheduler.store.get_task(task_id)
|
||||
if task is None:
|
||||
raise HTTPException(
|
||||
@@ -96,7 +111,7 @@ def create_cloud_router(
|
||||
|
||||
@router.get("/devices", response_model=list[DeviceResponse])
|
||||
def list_devices(request: Request) -> list[DeviceResponse]:
|
||||
_authorize(request)
|
||||
_authorize(request, POOL_READ_SCOPE)
|
||||
return [
|
||||
DeviceResponse(
|
||||
device_id=d.device_id,
|
||||
@@ -110,7 +125,7 @@ def create_cloud_router(
|
||||
|
||||
@router.get("/hosts", response_model=list[HostResponse])
|
||||
def list_hosts(request: Request) -> list[HostResponse]:
|
||||
_authorize(request)
|
||||
_authorize(request, POOL_READ_SCOPE)
|
||||
return [
|
||||
HostResponse(
|
||||
host_id=h.host_id,
|
||||
@@ -122,7 +137,7 @@ def create_cloud_router(
|
||||
|
||||
@router.get("/plugins", response_model=list[PluginResponse])
|
||||
def list_plugins(request: Request) -> list[PluginResponse]:
|
||||
_authorize(request)
|
||||
_authorize(request, PLUGINS_READ_SCOPE)
|
||||
return [
|
||||
PluginResponse(
|
||||
name=manifest.name,
|
||||
@@ -147,7 +162,7 @@ def create_cloud_router(
|
||||
payload: PluginRegistrationRequest,
|
||||
request: Request,
|
||||
) -> PluginResponse:
|
||||
_authorize(request)
|
||||
_authorize(request, PLUGINS_ADMIN_SCOPE)
|
||||
from cloud.plugins import (
|
||||
DriverRegistryUnavailableError,
|
||||
DuplicatePluginError,
|
||||
|
||||
Reference in New Issue
Block a user